1
2
3 """ S3 User Roles Management
4
5 @copyright: 2018-2019 (c) Sahana Software Foundation
6 @license: MIT
7
8 Permission is hereby granted, free of charge, to any person
9 obtaining a copy of this software and associated documentation
10 files (the "Software"), to deal in the Software without
11 restriction, including without limitation the rights to use,
12 copy, modify, merge, publish, distribute, sublicense, and/or sell
13 copies of the Software, and to permit persons to whom the
14 Software is furnished to do so, subject to the following
15 conditions:
16
17 The above copyright notice and this permission notice shall be
18 included in all copies or substantial portions of the Software.
19
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27 OTHER DEALINGS IN THE SOFTWARE.
28 """
29
30 __all__ = ("S3RoleManager",
31 )
32
33 import uuid
34 import json
35
36
37 from gluon import current, URL, DIV, SQLFORM, INPUT, A, LI, UL
38
39 from s3dal import Field
40 from s3crud import S3CRUD
41 from s3rest import S3Method
42 from s3query import FS
43 from s3utils import s3_str, s3_mark_required
44 from s3validators import JSONERRORS
45 from s3widgets import s3_comments_widget
46 from s3xml import SEPARATORS
50 """ REST Method to manage user roles and permission rules """
51
52
54 """
55 Entry point for REST interface.
56
57 @param r: the S3Request instance
58 @param attr: controller attributes
59 """
60
61 method = self.method
62 tablename = self.tablename
63
64 auth = current.auth
65 sr = auth.get_system_roles()
66
67 output = {}
68
69 if tablename == "auth_group":
70
71
72 if not auth.s3_has_role(sr.ADMIN):
73 r.unauthorised()
74
75 if method == "list":
76 output = self.role_list(r, **attr)
77 elif method in ("read", "create", "update"):
78 output = self.role_form(r, **attr)
79 elif method == "copy":
80 output = self.copy_role(r, **attr)
81 elif method == "delete":
82 output = self.delete_role(r, **attr)
83 elif method == "users":
84 output = self.assign_users(r, **attr)
85 elif method == "import":
86 output = self.import_roles(r, **attr)
87 else:
88 r.error(405, current.ERROR.BAD_METHOD)
89
90 elif tablename == "auth_user":
91
92
93
94 if not self._permitted():
95 r.unauthorised()
96
97 if method == "roles":
98 output = self.assign_roles(r, **attr)
99 else:
100 r.error(405, current.ERROR.BAD_METHOD)
101
102
103
104
105
106
107
108 else:
109 r.error(401, current.ERROR.BAD_REQUEST)
110
111 return output
112
113
115 """
116 List or export roles
117
118 @param r: the S3Request instance
119 @param attr: controller attributes
120
121 NB this function must be restricted to ADMINs (in apply_method)
122 """
123
124
125 authorised = self._permitted()
126 if not authorised:
127 r.unauthorised()
128
129
130 representation = r.representation
131 if representation == "csv":
132 return self.export_roles(r, **attr)
133
134 T = current.T
135 response = current.response
136 s3 = response.s3
137
138 get_vars = self.request.get_vars
139
140
141 list_id = "roles"
142 list_fields = ["id",
143 "role",
144 (T("UID"), "uuid"),
145 "description",
146 ]
147 default_orderby = "auth_group.role"
148 s3.no_formats = True
149
150
151 resource = self.resource
152 resource.add_filter(FS("hidden") == False)
153
154 if r.interactive:
155
156
157 formkey = str(uuid.uuid4())
158 current.session["_formkey[admin/rolelist]"] = formkey
159
160
161 display_length = s3.dataTable_pageLength or 25
162 start = None
163 if s3.no_sspag:
164 dt_pagination = "false"
165 limit = None
166 else:
167 dt_pagination = "true"
168 limit = 2 * display_length
169
170
171 dt, totalrows = resource.datatable(fields = list_fields,
172 start = start,
173 limit = limit,
174 left = [],
175 orderby = default_orderby,
176 )
177
178
179 datatable = dt.html(totalrows,
180 totalrows,
181 id = list_id,
182 dt_pagination = dt_pagination,
183 dt_pageLength = display_length,
184 dt_base_url = r.url(method="", vars={}),
185 dt_permalink = r.url(),
186 dt_formkey = formkey,
187 )
188
189
190 self.role_list_actions(r)
191
192
193 response.view = "admin/roles.html"
194
195
196 crud_button = S3CRUD.crud_button
197 page_actions = DIV(crud_button(T("Create Role"),
198 _href = r.url(method="create"),
199 ),
200
201
202
203
204 crud_button(T("Export Roles"),
205 _href = r.url(representation="csv"),
206 ),
207 )
208
209
210 output = {"title": T("User Roles"),
211 "items": datatable,
212 "page_actions": page_actions,
213 }
214
215 elif representation == "aadata":
216
217
218 start, limit = S3CRUD._limits(get_vars)
219
220
221 searchq, orderby, left = resource.datatable_filter(list_fields,
222 get_vars,
223 )
224 if searchq is not None:
225 totalrows = resource.count()
226 resource.add_filter(searchq)
227 else:
228 totalrows = None
229 if orderby is None:
230 orderby = default_orderby
231
232
233 if totalrows != 0:
234 dt, displayrows = resource.datatable(fields = list_fields,
235 start = start,
236 limit = limit,
237 left = left,
238 orderby = orderby,
239 )
240 else:
241 dt, displayrows = None, 0
242 if totalrows is None:
243 totalrows = displayrows
244
245
246 draw = int(get_vars.get("draw", 0))
247
248
249 if dt is not None:
250 output = dt.json(totalrows, displayrows, list_id, draw)
251 else:
252 output = '{"recordsTotal":%s,' \
253 '"recordsFiltered":0,' \
254 '"dataTable_id":"%s",' \
255 '"draw":%s,' \
256 '"data":[]}' % (totalrows, list_id, draw)
257
258 else:
259 r.error(415, current.ERROR.BAD_FORMAT)
260
261 return output
262
263
265 """
266 Configure action buttons for role list
267
268 @param r: the S3Request
269 """
270
271 T = current.T
272 s3 = current.response.s3
273 sr = current.auth.get_system_roles()
274
275 table = self.table
276
277
278 s3.actions = None
279 s3.crud_labels.UPDATE = T("Edit")
280 S3CRUD.action_buttons(r, deletable=False)
281
282 action_button = S3CRUD.action_button
283
284
285 label = T("Users")
286 excluded = [str(sr.AUTHENTICATED), str(sr.ANONYMOUS)]
287 action_button(label, URL(args=["[id]", "users"]),
288 exclude = excluded,
289 _title = s3_str(T("Assign this role to users")),
290 )
291 action_button(label, None,
292 restrict = excluded,
293 _disabled = "disabled",
294 _title = s3_str(T("This role is assigned automatically")),
295 )
296
297
298 label = T("Copy")
299 excluded = [str(sr.ADMIN)]
300 action_button(label, None,
301 _ajaxurl = URL(args=["[id]", "copy.json"]),
302 exclude = excluded,
303 _title = s3_str(T("Copy this role to create a new role")),
304 _class = "action-btn copy-role-btn",
305 )
306 action_button(label, None,
307 restrict = excluded,
308 _disabled = "disabled",
309 _title = s3_str(T("This role cannot be copied")),
310 )
311 question = T("Create a copy of this role?")
312 script = '''var dt=$('#roles');dt.on('click','.copy-role-btn',dt.dataTableS3('ajaxAction','%s'));''' % question
313 s3.jquery_ready.append(script)
314
315
316 label = T("Delete")
317 query = (table.deleted == False) & \
318 ((table.system == True) | (table.protected == True))
319 protected_roles = current.db(query).select(table.id)
320 excluded = [str(role.id) for role in protected_roles]
321 action_button(label, None,
322 _ajaxurl = URL(args=["[id]", "delete.json"]),
323 _class = "delete-btn-ajax action-btn dt-ajax-delete",
324 exclude = excluded,
325 )
326 action_button(label, None,
327 restrict = excluded,
328 _disabled = "disabled",
329 _title = s3_str(T("This role cannot be deleted")),
330 )
331
332
477
478
480 """
481 Extract the permission rules for a role
482
483 @param role: the role (Row)
484
485 @returns: the permission rules as JSON string
486 """
487
488 permissions = current.auth.permission
489
490 rules = []
491
492 table = permissions.table
493 if table:
494 query = (table.group_id == role.id) & \
495 (table.deleted == False)
496
497 if not permissions.use_facls:
498 query &= (table.function == None)
499 if not permissions.use_tacls:
500 query &= (table.tablename == None)
501
502 rows = current.db(query).select(table.id,
503 table.controller,
504 table.function,
505 table.tablename,
506 table.uacl,
507 table.oacl,
508 table.entity,
509 table.unrestricted,
510 )
511
512 for row in rows:
513 if row.unrestricted:
514 entity = "any"
515 else:
516 entity = row.entity
517 rules.append([row.id,
518 row.controller,
519 row.function,
520 row.tablename,
521 row.uacl,
522 row.oacl,
523 entity,
524 False,
525 ])
526
527 return json.dumps(rules, separators=SEPARATORS)
528
529
531 """
532 Create or update a role from a role form
533
534 @param role: the role (Row)
535 @param form: the form
536
537 @returns: tuple (role ID, confirmation message)
538 """
539
540 T = current.T
541 auth = current.auth
542
543 formvars = form.vars
544 rolename = formvars.role
545
546 uid = formvars.uuid
547 if role:
548 role_id = role.id
549 data = {"role": rolename,
550 "description": formvars.description,
551 }
552 if uid is not None:
553 data["uuid"] = uid
554 role.update_record(**data)
555 else:
556 data = {"role": rolename}
557 role_id = auth.s3_create_role(rolename,
558 description = formvars.description,
559 uid = uid,
560 )
561
562 if role_id:
563
564 permissions = formvars.permissions
565 if permissions:
566 self.update_permissions(role_id, permissions)
567 if not role:
568 message = T("Role %(role)s created") % data
569 else:
570 message = T("Role %(role)s updated") % data
571 else:
572 if not role:
573 message = T("Failed to create role %(role)s") % data
574 else:
575 message = T("Failed to update role %(role)s") % data
576
577 return role_id, message
578
579
581 """
582 Update the permission rules for a role
583
584 @param role_id: the role record ID (auth_group.id)
585 @param rules: the rules as JSON string
586 """
587
588 table = current.auth.permission.table
589 if table:
590
591 db = current.db
592
593 rules = json.loads(rules)
594 for rule in rules:
595
596 rule_id = rule[0]
597 deleted = rule[7]
598
599 if rule_id is None:
600 continue
601 if not any(rule[i] for i in (1, 2, 3)):
602 continue
603
604 if rule_id and deleted:
605 db(table.id == rule_id).update(deleted=True)
606
607 else:
608 entity = rule[6]
609 if entity == "any":
610 unrestricted = True
611 entity = None
612 else:
613 unrestricted = False
614 try:
615 entity = long(entity) if entity else None
616 except (ValueError, TypeError):
617 entity = None
618
619 data = {"group_id": role_id,
620 "controller": rule[1],
621 "function": rule[2],
622 "tablename": rule[3],
623 "uacl": rule[4],
624 "oacl": rule[5],
625 "entity": entity,
626 "unrestricted": unrestricted,
627 }
628
629 if rule_id:
630
631 db(table.id == rule_id).update(**data)
632 else:
633
634 table.insert(**data)
635
636 return ""
637
638
640 """
641 Duplicate an existing role
642
643 NB this function must be restricted to ADMINs (in apply_method)
644 """
645
646
647 key = current.session["_formkey[admin/rolelist]"]
648 if not key or r.post_vars.get("_formkey") != key:
649 r.error(403, current.ERROR.NOT_PERMITTED)
650
651 if r.http == "POST":
652
653 db = current.db
654
655 role = r.record
656 if not role:
657 r.error(400, current.ERROR.BAD_RECORD)
658
659
660 table = r.table
661 query = ((table.uuid.like("%s%%" % role.uuid)) | \
662 (table.role.like("%s%%" % role.role)))
663 rows = db(query).select(table.uuid,
664 table.role,
665 )
666 uids = set(row.uuid for row in rows)
667 names = set(row.role for row in rows)
668 uid = name = None
669 for i in range(2, 1000):
670 if not uid:
671 uid = "%s%s" % (role.uuid, i)
672 if uid in uids:
673 uid = None
674 if not name:
675 name = "%s-%s" % (role.role, i)
676 if name in names:
677 name = None
678 if uid and name:
679 break
680 if not uid:
681 uid = str(uuid.uuid4())
682 if not name:
683 name = str(uuid.uuid4())
684
685
686 role_id = table.insert(uuid = uid,
687 role = name,
688 )
689
690
691 ptable = current.auth.permission.table
692 if ptable:
693 query = (ptable.group_id == role.id) & \
694 (ptable.deleted == False)
695 rules = db(query).select(ptable.controller,
696 ptable.function,
697 ptable.tablename,
698 ptable.record,
699 ptable.oacl,
700 ptable.uacl,
701 ptable.entity,
702 ptable.unrestricted,
703 )
704 for rule in rules:
705 ptable.insert(group_id = role_id,
706 controller = rule.controller,
707 function = rule.function,
708 tablename = rule.tablename,
709 record = rule.record,
710 oacl = rule.oacl,
711 uacl = rule.uacl,
712 entity = rule.entity,
713 unrestricted = rule.unrestricted,
714 )
715
716 message = current.T("New Role %(role)s created") % {"role": name}
717 return current.xml.json_message(message=message)
718
719 else:
720 r.error(405, current.ERROR.BAD_METHOD)
721
722
724 """
725 Delete a role
726
727 NB this function must be restricted to ADMINs (in apply_method)
728 """
729
730
731 key = current.session["_formkey[admin/rolelist]"]
732 if not key or r.post_vars.get("_formkey") != key:
733 r.error(403, current.ERROR.NOT_PERMITTED)
734
735 if r.http in ("POST", "DELETE"):
736
737 role = r.record
738 if not role:
739 r.error(400, current.ERROR.BAD_RECORD)
740
741 if role.protected or role.system:
742 r.error(403, current.ERROR.NOT_PERMITTED)
743
744 auth = current.auth
745 auth.s3_delete_role(role.id)
746 auth.s3_set_roles()
747
748 message = current.T("Role %(role)s deleted") % {"role": role.role}
749
750 return current.xml.json_message(message=message)
751
752 else:
753 r.error(405, current.ERROR.BAD_METHOD)
754
755
757 """
758 Assign/unassign roles to a user
759
760 NB this function is accessible for non-ADMINs (e.g. ORG_ADMIN)
761 """
762
763 auth = current.auth
764
765
766 if not r.record:
767 r.error(400, current.ERRORS.BAD_RECORD)
768
769
770 mtable = auth.settings.table_membership
771 permitted = auth.s3_has_permission
772 if not permitted("create", mtable) and not permitted("delete", mtable):
773 r.unauthorised()
774
775
776 pe_ids = auth.get_managed_orgs()
777 if not pe_ids:
778 r.unauthorised()
779 elif pe_ids is not True:
780 otable = current.s3db.org_organisation
781 utable = auth.settings.table_user
782 query = (utable.id == r.id) & \
783 (otable.id == utable.organisation_id) & \
784 (otable.pe_id.belongs(pe_ids))
785 row = current.db(query).select(utable.id, limitby=(0, 1)).first()
786 if not row:
787 r.unauthorised()
788
789 s3 = current.response.s3
790
791
792 managed_roles = self.get_managed_roles(r.id)
793
794 output = {}
795
796 if r.http == "GET":
797
798 T = current.T
799
800
801 userfield = auth.settings.login_userfield
802 user_name = r.record[userfield]
803 output["title"] = "%s: %s" % (T("Roles of User"), user_name)
804
805
806 use_realms = auth.permission.entity_realm
807 if use_realms:
808 realm_types, realms = self.get_managed_realms()
809 else:
810 realm_types, realms = None, None
811
812
813 ajax_url = r.url(id="[id]", representation="json")
814
815
816 field = mtable.user_id
817 field.readable = field.writable = True
818 field.widget = S3RolesWidget(mode="roles",
819 items = managed_roles,
820 use_realms = use_realms,
821 realm_types = realm_types,
822 realms = realms,
823 ajax_url = ajax_url,
824 )
825
826
827 tablename = str(mtable)
828 form = SQLFORM.factory(field,
829 record = {"id": None, "user_id": r.id},
830 showid = False,
831 labels = {field.name: ""},
832 formstyle = s3.crud.formstyle,
833 table_name = tablename,
834 upload = s3.download_url,
835
836 separator = "",
837 submit_button = False,
838 buttons = [],
839 )
840 form.add_class("rm-form")
841 output["form"] = form
842
843
844
845 crud_button = S3CRUD.crud_button
846 output["list_btn"] = crud_button(T("Back to User List"),
847 icon = "return",
848 _href = r.url(id="", method=""),
849 )
850
851
852 response = current.response
853 response.view = "admin/role_form.html"
854
855 elif r.http == "POST":
856 if r.representation == "json":
857
858 s = r.body
859 s.seek(0)
860 try:
861 options = json.load(s)
862 except JSONERRORS:
863 options = None
864 if not isinstance(options, dict):
865 r.error(400, "Invalid request options")
866
867 user_id = r.record.id
868 added = options.get("add")
869 removed = options.get("remove")
870
871
872 if added:
873 for group_id, pe_id in added:
874 role = managed_roles.get(group_id)
875 if not role or role.get("a") is False:
876 r.error(403, current.ERROR.NOT_PERMITTED)
877 if removed:
878 for group_id, pe_id in removed:
879 role = managed_roles.get(group_id)
880 if not role or role.get("r") is False:
881 r.error(403, current.ERROR.NOT_PERMITTED)
882
883
884 if added:
885 add_role = auth.s3_assign_role
886 for group_id, pe_id in added:
887 add_role(user_id, group_id, for_pe=pe_id)
888 if removed:
889 remove_role = auth.s3_withdraw_role
890 for group_id, pe_id in removed:
891 remove_role(user_id, group_id, for_pe=pe_id)
892
893 output = current.xml.json_message(options=options)
894
895 else:
896 r.error(415, current.ERROR.BAD_FORMAT)
897 else:
898 r.error(405, current.ERROR.BAD_METHOD)
899
900 return output
901
902
904 """
905 Assign/unassign users to a role
906
907 NB this function could be accessible for non-ADMINs (e.g. ORG_ADMIN)
908 """
909
910 auth = current.auth
911
912
913 role = r.record
914 if not role:
915 r.error(400, current.ERRORS.BAD_RECORD)
916
917
918 mtable = auth.settings.table_membership
919 permitted = auth.s3_has_permission
920 if not permitted("create", mtable) and not permitted("delete", mtable):
921 r.unauthorised()
922
923
924 managed_roles = self.get_managed_roles(None)
925 if role.id not in managed_roles:
926 r.unauthorised()
927
928 s3 = current.response.s3
929
930
931 managed_users = self.get_managed_users(role.id)
932
933
934 sr = auth.get_system_roles()
935 unrestrictable = (sr.ADMIN, sr.AUTHENTICATED, sr.ANONYMOUS)
936 unassignable = (sr.AUTHENTICATED, sr.ANONYMOUS)
937
938 output = {}
939
940 if r.http == "GET":
941
942 T = current.T
943
944
945 output["title"] = "%s: %s" % (T("Users with Role"), role.role)
946
947
948 use_realms = auth.permission.entity_realm and \
949 role.id not in unrestrictable
950 if use_realms:
951 realm_types, realms = self.get_managed_realms()
952 else:
953 realm_types, realms = None, None
954
955
956 ajax_url = r.url(id="[id]", representation="json")
957
958
959 field = mtable.group_id
960 field.readable = field.writable = True
961 field.widget = S3RolesWidget(mode="users",
962 items = managed_users,
963 use_realms = use_realms,
964 realm_types = realm_types,
965 realms = realms,
966 ajax_url = ajax_url,
967 )
968
969
970 tablename = str(mtable)
971 form = SQLFORM.factory(field,
972 record = {"id": None, "group_id": role.id},
973 showid = False,
974 labels = {field.name: ""},
975 formstyle = s3.crud.formstyle,
976 table_name = tablename,
977 upload = s3.download_url,
978
979 separator = "",
980 submit_button = False,
981 buttons = [],
982 )
983 form.add_class("rm-form")
984 output["form"] = form
985
986
987 if "rheader" not in attr:
988 return_btn = S3CRUD.crud_button("Back to Roles List",
989 icon = "return",
990 _href=r.url(id="", method=""),
991 )
992 output["rheader"] = DIV(return_btn,
993 _class="rheader",
994 )
995
996 response = current.response
997 response.view = "admin/role_form.html"
998
999 elif r.http == "POST":
1000 if r.representation == "json":
1001
1002
1003
1004 s = r.body
1005 s.seek(0)
1006 try:
1007 options = json.load(s)
1008 except JSONERRORS:
1009 options = None
1010 if not isinstance(options, dict):
1011 r.error(400, "Invalid request options")
1012
1013 added = options.get("add")
1014 removed = options.get("remove")
1015
1016
1017 group_id = role.id
1018 if group_id in unassignable:
1019 r.error(403, current.ERROR.NOT_PERMITTED)
1020 if added:
1021 for user_id, pe_id in added:
1022 user = managed_users.get(user_id)
1023 if not user or user.get("a") is False:
1024 r.error(403, current.ERROR.NOT_PERMITTED)
1025 if removed:
1026 for user_id, pe_id in removed:
1027 user = managed_users.get(user_id)
1028 if not user or user.get("r") is False:
1029 r.error(403, current.ERROR.NOT_PERMITTED)
1030
1031
1032 if added:
1033 add_role = auth.s3_assign_role
1034 for user_id, pe_id in added:
1035 add_role(user_id, group_id, for_pe=pe_id)
1036 if removed:
1037 remove_role = auth.s3_withdraw_role
1038 for user_id, pe_id in removed:
1039 remove_role(user_id, group_id, for_pe=pe_id)
1040
1041 output = current.xml.json_message(options=options)
1042
1043 else:
1044 r.error(415, current.ERROR.BAD_FORMAT)
1045 else:
1046 r.error(405, current.ERROR.BAD_METHOD)
1047
1048 return output
1049
1050
1051 @staticmethod
1053 """
1054 Get a dict of users the current user can assign to roles
1055
1056 @param role_id: the target role ID
1057
1058 @returns: a dict {user_id: {l:label,
1059 t:title,
1060 a:assignable,
1061 r:removable,
1062 u:unrestrictable,
1063 }, ...}
1064 NB a, r and u attributes only added if non-default
1065 """
1066
1067 auth = current.auth
1068 auth_settings = auth.settings
1069
1070 sr = auth.get_system_roles()
1071 admin_role = role_id == sr.ADMIN
1072 unassignable = role_id in (sr.AUTHENTICATED, sr.ANONYMOUS)
1073 unrestrictable = role_id in (sr.ADMIN, sr.AUTHENTICATED, sr.ANONYMOUS)
1074
1075 current_user = auth.user.id if auth.user else None
1076
1077 users = {}
1078
1079 pe_ids = auth.get_managed_orgs()
1080 if pe_ids:
1081 utable = auth_settings.table_user
1082 query = (utable.deleted == False)
1083
1084 if pe_ids is not True:
1085 otable = current.s3db.org_organisation
1086 query &= (otable.id == utable.organisation_id) & \
1087 (otable.pe_id.belongs(pe_ids))
1088
1089 userfield = auth_settings.login_userfield
1090
1091 rows = current.db(query).select(utable.id,
1092 utable.first_name,
1093 utable.last_name,
1094 utable[userfield],
1095 )
1096 for row in rows:
1097
1098 user_id = row.id
1099 user = {"l": row[userfield],
1100 "t": "%s %s" % (row.first_name,
1101 row.last_name,
1102 ),
1103 }
1104
1105 if unrestrictable:
1106 user["u"] = True
1107 if admin_role and user_id == current_user:
1108
1109 user["r"] = False
1110 if unassignable:
1111 user["a"] = user["r"] = False
1112
1113 users[user_id] = user
1114
1115 return users
1116
1117
1118 @staticmethod
1120 """
1121 Get a dict of roles the current user can manage
1122
1123 @returns: a dict {role_id: {l:label,
1124 a:assignable,
1125 r:removable,
1126 u:unrestrictable,
1127 }, ...},
1128 NB a, r and u attributes only added if non-default
1129 """
1130
1131 auth = current.auth
1132 sr = auth.get_system_roles()
1133
1134 AUTO = (sr.AUTHENTICATED, sr.ANONYMOUS)
1135 ADMINS = (sr.ADMIN, sr.ORG_ADMIN, sr.ORG_GROUP_ADMIN)
1136 UNRESTRICTABLE = (sr.ADMIN, sr.AUTHENTICATED, sr.ANONYMOUS)
1137
1138
1139 table = auth.settings.table_group
1140 query = (table.hidden == False) & \
1141 (table.deleted == False)
1142 rows = current.db(query).select(table.id,
1143 table.uuid,
1144 table.role,
1145 )
1146
1147 has_role = auth.s3_has_role
1148
1149 roles = {}
1150 for row in rows:
1151
1152 role = {"l": row.role or row.uuid}
1153
1154 role_id = row.id
1155
1156 if role_id in ADMINS:
1157 assignable = has_role(role_id)
1158 else:
1159 assignable = role_id not in AUTO
1160
1161 if role_id == sr.ADMIN and auth.user.id == user_id:
1162 removable = False
1163 else:
1164 removable = assignable
1165
1166 if not assignable:
1167 role["a"] = False
1168 if not removable:
1169 role["r"] = False
1170 if role_id in UNRESTRICTABLE:
1171 role["u"] = True
1172
1173 roles[role_id] = role
1174
1175 return roles
1176
1177
1178 @staticmethod
1180 """
1181 Get a dict of realms managed by the current user
1182
1183 @returns: tuple (realm_types, realms):
1184 - realm_types = [(instance_type, label), ...]
1185 - realms = {pe_id: {l:label, t:type}, ...}
1186 """
1187
1188 T = current.T
1189 t_ = lambda v: s3_str(T(v))
1190
1191 realm_types = [(None, t_("Multiple"))]
1192 realms = {None: {"l": t_("Default Realm"), "t": None},
1193 }
1194
1195
1196 pe_ids = []
1197
1198 auth = current.auth
1199 sr = auth.get_system_roles()
1200 has_role = auth.s3_has_role
1201
1202 is_admin = has_role(sr.ADMIN)
1203 if is_admin:
1204
1205 realms[0] = {"l": t_("All Entities"), "t": None}
1206 else:
1207 if has_role(sr.ORG_GROUP_ADMIN):
1208 role_realms = auth.user.realms[sr.ORG_GROUP_ADMIN]
1209 if role_realms:
1210 pe_ids.extend(role_realms)
1211 if has_role(sr.ORG_ADMIN):
1212 role_realms = auth.user.realms[sr.ORG_ADMIN]
1213 if role_realms:
1214 pe_ids.extend(role_realms)
1215
1216
1217 s3db = current.s3db
1218 types = current.deployment_settings.get_auth_realm_entity_types()
1219 entities = s3db.pr_get_entities(pe_ids = pe_ids,
1220 types = types,
1221 group = True,
1222 show_instance_type = False,
1223 )
1224
1225
1226 instance_type_nice = s3db.pr_pentity.instance_type.represent
1227 for instance_type in types:
1228 entity_group = entities.get(instance_type)
1229 if not entity_group:
1230 continue
1231 realm_types.append((instance_type,
1232 s3_str(instance_type_nice(instance_type)),
1233 ))
1234 for pe_id, name in entity_group.items():
1235 realms[pe_id] = {"l": s3_str(name), "t": instance_type}
1236
1237 return realm_types, realms
1238
1239
1241 """
1242 Interactive import of roles (auth_roles.csv format)
1243
1244 NB this function must be restricted to ADMINs (in apply_method)
1245 """
1246
1247
1248
1249 T = current.T
1250
1251 output = {}
1252
1253
1254 output["title"] = T("Import Roles")
1255
1256
1257 response = current.response
1258 response.view = "admin/import_roles.html"
1259
1260 return output
1261
1262
1263
1264
1265
1266
1267
1269 """
1270 Export of roles (auth_roles.csv format)
1271
1272 NB this function must be restricted to ADMINs (in apply_method)
1273 """
1274
1275 output = S3RolesExport(r.resource).as_csv()
1276
1277
1278 from gluon.contenttype import contenttype
1279
1280 filename = "auth_roles.csv"
1281 disposition = "attachment; filename=\"%s\"" % filename
1282
1283 response = current.response
1284 response.headers["Content-Type"] = contenttype(".csv")
1285 response.headers["Content-disposition"] = disposition
1286
1287 return output.read()
1288
1644
1849
1852 """
1853 Roles Exporter
1854 """
1855
1857 """
1858 Constructor
1859
1860 @param resource: the role resource (auth_group) with REST
1861 filters; or None to export all groups
1862 """
1863
1864 db = current.db
1865 auth = current.auth
1866
1867
1868 self.col_hidden = False
1869 self.col_protected = False
1870 self.col_entity = False
1871
1872
1873 gtable = auth.settings.table_group
1874 fields = ("id",
1875 "uuid",
1876 "role",
1877 "description",
1878 "hidden",
1879 "protected",
1880 "system",
1881 )
1882 if resource and resource.tablename == str(gtable):
1883 roles = resource.select(fields, as_rows=True)
1884 else:
1885 query = (gtable.deleted == False)
1886 roles = db(query).select(*fields)
1887
1888
1889 role_dicts = {}
1890 for role in roles:
1891 role_dict = {"uid": role.uuid,
1892 "role": role.role,
1893 "description": role.description,
1894 }
1895 if role.hidden:
1896 self.col_hidden = True
1897 role_dict["hidden"] = "true"
1898 if role.protected and not role.system:
1899 self.col_protected = True
1900 role_dict["protected"] = "true"
1901 role_dicts[role.id] = role_dict
1902 self.roles = role_dicts
1903
1904
1905 rtable = auth.permission.table
1906 query = (rtable.group_id.belongs(role_dicts.keys())) & \
1907 (rtable.deleted == False)
1908 rules = db(query).select(rtable.id,
1909 rtable.group_id,
1910 rtable.controller,
1911 rtable.function,
1912 rtable.tablename,
1913 rtable.uacl,
1914 rtable.oacl,
1915 rtable.entity,
1916 )
1917 self.rules = rules
1918
1919
1920 entities = set()
1921 for rule in rules:
1922 entity = rule.entity
1923 if entity is not None:
1924 self.col_entity = True
1925 entities.add(entity)
1926
1927 otable = current.s3db.org_organisation
1928 query = (otable.pe_id.belongs(entities)) & \
1929 (otable.deleted == False)
1930 self.orgs = db(query).select(otable.pe_id,
1931 otable.name,
1932 ).as_dict(key="pe_id")
1933
1934
1936 """
1937 Export the current roles and permissions as CSV,
1938 suitable for prepop (see S3BulkImporter.import_role)
1939
1940 @returns: a StringIO containing the CSV
1941 """
1942
1943 import csv
1944 try:
1945 from cStringIO import StringIO
1946 except ImportError:
1947 from StringIO import StringIO
1948
1949
1950 col_protected = self.col_protected
1951 col_hidden = self.col_hidden
1952 col_entity = self.col_entity
1953
1954
1955 fieldnames = ["uid", "role", "description"]
1956 if col_hidden:
1957 fieldnames.append("hidden")
1958 if col_protected:
1959 fieldnames.append("protected")
1960
1961
1962 fieldnames.extend(["controller", "function", "table", "uacl", "oacl"])
1963 if col_entity:
1964 fieldnames.extend("entity")
1965
1966
1967 role_dicts = self.roles
1968 def get_uid(group_id):
1969 role_dict = role_dicts.get(group_id)
1970 return role_dict.get("uid") if role_dict else None
1971
1972
1973 rules = sorted(self.rules,
1974 key = lambda rule: (get_uid(rule.group_id),
1975 rule.controller or "zzzzzz",
1976 rule.function,
1977 rule.tablename,
1978 ))
1979
1980
1981 f = StringIO()
1982 writer = csv.DictWriter(f, fieldnames=fieldnames)
1983 writer.writeheader()
1984
1985
1986 orgs = self.orgs
1987 encode_permissions = self.encode_permissions
1988 for rule in rules:
1989
1990 role_dict = role_dicts.get(rule.group_id)
1991 if not role_dict:
1992 continue
1993
1994 rule_dict = {}
1995
1996
1997 if col_entity:
1998 entity = rule.entity
1999 if entity is not None:
2000 if entity == 0:
2001 rule_dict["entity"] = "any"
2002 else:
2003 org = orgs.get(entity)
2004 if org:
2005 rule_dict["entity"] = org
2006 else:
2007 continue
2008
2009
2010 if rule.tablename:
2011 rule_dict["table"] = rule.tablename
2012 else:
2013 if rule.controller:
2014 rule_dict["controller"] = rule.controller
2015 if rule.function:
2016 rule_dict["function"] = rule.function
2017
2018
2019 uacl = encode_permissions(rule.uacl, explicit_none=True)
2020 if uacl:
2021 rule_dict["uacl"] = uacl
2022 oacl = encode_permissions(rule.oacl & ~(rule.uacl))
2023 if oacl:
2024 rule_dict["oacl"] = oacl
2025
2026
2027 rule_dict.update(role_dict)
2028
2029
2030 writer.writerow(rule_dict)
2031
2032 f.seek(0)
2033 return f
2034
2035
2037 """
2038 Encodes a permission bitmap as string, using the permission
2039 labels from S3Permission.PERMISSION_OPTS
2040
2041 @param permissions: the permission bitmap
2042 @param explicit_none: return "NONE" if no permission bit set
2043 (otherwise returns None)
2044 """
2045
2046 if not permissions:
2047 if explicit_none:
2048 return "NONE"
2049 else:
2050 return None
2051
2052 opts = current.auth.permission.PERMISSION_OPTS
2053 labels = []
2054 for bit in opts:
2055 if permissions & bit:
2056 labels.append(opts[bit])
2057
2058 return "|".join(labels)
2059
2060
2061