Package s3 :: Module s3roles
[frames] | no frames]

Source Code for Module s3.s3roles

   1  # -*- coding: utf-8 -*- 
   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  #import sys 
  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 
47 48 # ============================================================================= 49 -class S3RoleManager(S3Method):
50 """ REST Method to manage user roles and permission rules """ 51 52 # -------------------------------------------------------------------------
53 - def apply_method(self, r, **attr):
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": # through admin/role controller 70 71 # Only ADMIN can manipulate roles 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": # through admin/user controller 91 92 # Must have read-permission for the user record 93 # (user accounts are filtered to OU by controller) 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 # TODO implement per-target perspective 103 #elif tablename == "s3_permission": # through admin/permissions controller 104 # 105 # # View permissions for a target (page or table) 106 # r.error(501, current.ERROR.NOT_IMPLEMENTED) 107 108 else: 109 r.error(401, current.ERROR.BAD_REQUEST) 110 111 return output
112 113 # -------------------------------------------------------------------------
114 - def role_list(self, r, **attr):
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 # Check permission to read in this table 125 authorised = self._permitted() 126 if not authorised: 127 r.unauthorised() 128 129 # Validate requested format 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 # List Config 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 # Exclude hidden roles 151 resource = self.resource 152 resource.add_filter(FS("hidden") == False) 153 154 if r.interactive: 155 156 # Formkey for Ajax-actions 157 formkey = str(uuid.uuid4()) 158 current.session["_formkey[admin/rolelist]"] = formkey 159 160 # Pagination 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 # Generate Data Table 171 dt, totalrows = resource.datatable(fields = list_fields, 172 start = start, 173 limit = limit, 174 left = [], 175 orderby = default_orderby, 176 ) 177 178 # Render the Data Table 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 # Configure action buttons 190 self.role_list_actions(r) 191 192 # View 193 response.view = "admin/roles.html" 194 195 # Page actions 196 crud_button = S3CRUD.crud_button 197 page_actions = DIV(crud_button(T("Create Role"), 198 _href = r.url(method="create"), 199 ), 200 # TODO activate when implemented 201 #crud_button(T("Import Roles"), 202 # _href = r.url(method="import"), 203 # ), 204 crud_button(T("Export Roles"), 205 _href = r.url(representation="csv"), 206 ), 207 ) 208 209 # Output 210 output = {"title": T("User Roles"), 211 "items": datatable, 212 "page_actions": page_actions, 213 } 214 215 elif representation == "aadata": 216 217 # Page limits 218 start, limit = S3CRUD._limits(get_vars) 219 220 # Data Table Filter and Sorting 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 # Data Table 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 # Echo 246 draw = int(get_vars.get("draw", 0)) 247 248 # Representation 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 # -------------------------------------------------------------------------
264 - def role_list_actions(self, r):
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 # Standard actions 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 # Users 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 # Copy-button Ajax 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 # Delete-button Ajax 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 # -------------------------------------------------------------------------
333 - def role_form(self, r, **attr):
334 """ 335 Create, read, update a role 336 337 NB this function must be restricted to ADMINs (in apply_method) 338 """ 339 340 T = current.T 341 342 s3 = current.response.s3 343 settings = current.deployment_settings 344 345 output = {} 346 347 method = r.method 348 record = r.record 349 350 # Read-only? 351 readonly = False 352 if r.record: 353 if r.interactive: 354 readonly = method == "read" 355 elif r.representation == "csv": 356 return self.export_roles(r, **attr) 357 else: 358 r.error(415, current.ERROR.BAD_FORMAT) 359 360 # Form fields 361 table = r.table 362 363 # UID 364 uid = table.uuid 365 uid.label = T("UID") 366 uid.readable = True 367 uid.writable = False if record and record.system else True 368 369 # Role name 370 role = table.role 371 role.label = T("Name") 372 373 # Role description 374 description = table.description 375 description.label = T("Description") 376 description.widget = s3_comments_widget 377 378 # Permissions 379 PERMISSIONS = T("Permissions") 380 permissions = Field("permissions", 381 label = PERMISSIONS, 382 widget = S3PermissionWidget(r.id), 383 ) 384 if not current.auth.permission.use_cacls: 385 # Security policy with fixed access rules 386 permissions.readable = permissions.writable = False 387 elif record: 388 if record.uuid == "ADMIN": 389 # Administrator permissions cannot be edited 390 permissions.readable = permissions.writable = False 391 else: 392 # Populate the field with current permissions 393 record.permissions = self.get_permissions(record) 394 395 # Mark required 396 if not readonly: 397 labels, s3.has_required = s3_mark_required(table, []) 398 labels["permissions"] = "%s:" % s3_str(PERMISSIONS) 399 else: 400 labels = None 401 402 # Form buttons 403 if not readonly: 404 submit_button = INPUT(_class = "small primary button", 405 _type = "submit", 406 _value = T("Save"), 407 ) 408 cancel_button = A(T("Cancel"), 409 _class="cancel-form-btn action-lnk", 410 _href = r.url(id=""), 411 ) 412 buttons = [submit_button, cancel_button] 413 else: 414 buttons = ["submit"] 415 416 # Form style 417 crudopts = s3.crud 418 formstyle = crudopts.formstyle_read if readonly else crudopts.formstyle 419 420 # Render form 421 tablename = "auth_group" 422 form = SQLFORM.factory(uid, 423 role, 424 description, 425 permissions, 426 record = record, 427 showid = False, 428 labels = labels, 429 formstyle = formstyle, 430 table_name = tablename, 431 upload = s3.download_url, 432 readonly = readonly, 433 separator = "", 434 submit_button = settings.submit_button, 435 buttons = buttons, 436 ) 437 form.add_class("rm-form") 438 output["form"] = form 439 440 # Navigate-away confirmation 441 if crudopts.navigate_away_confirm: 442 s3.jquery_ready.append("S3EnableNavigateAwayConfirm()") 443 444 # Process form 445 response = current.response 446 formname = "%s/%s" % (tablename, record.id if record else None) 447 if form.accepts(current.request.post_vars, 448 current.session, 449 #onvalidation = self.validate, 450 formname = formname, 451 keepvalues = False, 452 hideerror = False, 453 ): 454 role_id, message = self.update_role(record, form) 455 if role_id: 456 response.confirmation = message 457 self.next = r.url(id="", method="") 458 else: 459 response.error = message 460 461 elif form.errors: 462 response.error = T("There are errors in the form, please check your input") 463 464 # Title 465 if record: 466 if readonly: 467 output["title"] = record.role 468 else: 469 output["title"] = T("Edit Role: %(role)s") % {"role": record.role} 470 else: 471 output["title"] = T("Create Role") 472 473 # View 474 response.view = "admin/role_form.html" 475 476 return output
477 478 # -------------------------------------------------------------------------
479 - def get_permissions(self, role):
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, # delete-flag 525 ]) 526 527 return json.dumps(rules, separators=SEPARATORS)
528 529 # -------------------------------------------------------------------------
530 - def update_role(self, role, form):
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 # Update permissions 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 # -------------------------------------------------------------------------
580 - def update_permissions(self, role_id, rules):
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 # Update the rule 631 db(table.id == rule_id).update(**data) 632 else: 633 # Add the rule 634 table.insert(**data) 635 636 return ""
637 638 # -------------------------------------------------------------------------
639 - def copy_role(self, r, **attr):
640 """ 641 Duplicate an existing role 642 643 NB this function must be restricted to ADMINs (in apply_method) 644 """ 645 646 # CSRF Protection 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 # Find a suitable uuid and name 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 # Create the new role 686 role_id = table.insert(uuid = uid, 687 role = name, 688 ) 689 690 # Copy permissions 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 # -------------------------------------------------------------------------
723 - def delete_role(self, r, **attr):
724 """ 725 Delete a role 726 727 NB this function must be restricted to ADMINs (in apply_method) 728 """ 729 730 # CSRF Protection 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 # -------------------------------------------------------------------------
756 - def assign_roles(self, r, **attr):
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 # Require a primary record 766 if not r.record: 767 r.error(400, current.ERRORS.BAD_RECORD) 768 769 # Require permission to create or delete group memberships 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 # Require that the target user record belongs to a managed organisation 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 # Which roles can the current user manage for this user? 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 # Page Title 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 # Should we use realms? 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 # The Ajax URL for role updates 813 ajax_url = r.url(id="[id]", representation="json") 814 815 # The form field 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 # Render form 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 #readonly = readonly, 836 separator = "", 837 submit_button = False, 838 buttons = [], 839 ) 840 form.add_class("rm-form") 841 output["form"] = form 842 843 # Show a back-button since OrgAdmins have no other obvious 844 # way to return to the list (no left menu) 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 # View 852 response = current.response 853 response.view = "admin/role_form.html" 854 855 elif r.http == "POST": 856 if r.representation == "json": 857 # Read+parse body JSON 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 # Validate 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 # Update role assignments 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 # -------------------------------------------------------------------------
903 - def assign_users(self, r, **attr):
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 # Require a primary record 913 role = r.record 914 if not role: 915 r.error(400, current.ERRORS.BAD_RECORD) 916 917 # Require permission to create or delete group memberships 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 # Require that the target role belongs to managed roles 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 # Which users can the current user manage? 931 managed_users = self.get_managed_users(role.id) 932 933 # Special rules for system roles 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 # Page Title 945 output["title"] = "%s: %s" % (T("Users with Role"), role.role) 946 947 # Should we use realms? 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 # The Ajax URL for role updates 956 ajax_url = r.url(id="[id]", representation="json") 957 958 # The form field 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 # Render form 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 #readonly = readonly, 979 separator = "", 980 submit_button = False, 981 buttons = [], 982 ) 983 form.add_class("rm-form") 984 output["form"] = form 985 986 # Default RHeader and View 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 # Process Ajax-request from S3RolesWidget 1002 1003 # Read+parse body JSON 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 # Validate 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 # Update role assignments 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
1052 - def get_managed_users(role_id):
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 # ADMINs cannot remove their own ADMIN role 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
1119 - def get_managed_roles(user_id):
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
1179 - def get_managed_realms():
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 # Look up the realms managed by the current user 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 # Only ADMIN can assign roles site-wide 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 # Get entities and types 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 # Add representations for entities and types 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 # -------------------------------------------------------------------------
1240 - def import_roles(self, r, **attr):
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 # TODO implement roles importer 1248 1249 T = current.T 1250 1251 output = {} 1252 1253 # Title 1254 output["title"] = T("Import Roles") 1255 1256 # View 1257 response = current.response 1258 response.view = "admin/import_roles.html" 1259 1260 return output
1261 1262 # if GET: 1263 # show an import form 1264 # elif POST: 1265 # import the submitted file using Bulk-importer 1266 1267 # -------------------------------------------------------------------------
1268 - def export_roles(self, r, **attr):
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 # Response headers 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
1289 # ============================================================================= 1290 -class S3PermissionWidget(object):
1291 """ 1292 Form widget to modify permissions of a role 1293 """ 1294
1295 - def __init__(self, role_id=None):
1296 """ 1297 Constructor 1298 """ 1299 1300 sr = current.auth.get_system_roles() 1301 1302 if role_id == sr.ANONYMOUS: 1303 default_roles = () 1304 elif role_id == sr.AUTHENTICATED: 1305 default_roles = (sr.ANONYMOUS,) 1306 else: 1307 default_roles = (sr.ANONYMOUS, sr.AUTHENTICATED) 1308 1309 self.default_roles = default_roles
1310 1311 # -------------------------------------------------------------------------
1312 - def __call__(self, field, value, **attributes):
1313 """ 1314 Form builder entry point 1315 1316 @param field: the Field 1317 @param value: the current (or default) value of the field 1318 @param attributes: HTML attributes for the widget 1319 """ 1320 1321 T = current.T 1322 1323 # Widget ID 1324 widget_id = attributes.get("_id") or str(field).replace(".", "_") 1325 1326 # Field name 1327 name = attributes.get("_name") or field.name 1328 1329 # Page access rules tab+pane 1330 prules_id = "%s-prules" % widget_id 1331 prules_tab = LI(A(T("Page Access"), 1332 _href = "#" + prules_id, 1333 ) 1334 ) 1335 prules_pane = DIV(_id = prules_id, 1336 _class = "rm-page-rules", 1337 ) 1338 1339 # Table access rules tab+page 1340 rules = current.auth.permission 1341 use_tacls = rules.use_tacls 1342 if use_tacls: 1343 trules_id = "%s-trules" % widget_id 1344 trules_tab = LI(A(T("Table Access"), 1345 _href = "#" + trules_id, 1346 ), 1347 ) 1348 trules_pane = DIV(_id = trules_id, 1349 _class = "rm-table-rules", 1350 ) 1351 else: 1352 trules_pane = "" 1353 trules_tab = "" 1354 1355 # Construct the widget 1356 widget = DIV(INPUT(_type = "hidden", 1357 _name = name, 1358 _value = value, 1359 _id = widget_id + "-input", 1360 ), 1361 DIV(UL(trules_tab, 1362 prules_tab, 1363 ), 1364 trules_pane, 1365 prules_pane, 1366 _class = "rm-rules hide" 1367 ), 1368 _id = widget_id, 1369 ) 1370 1371 # Module header icons 1372 rtl = current.response.s3.rtl 1373 icons = {"expanded": "fa fa-caret-down", 1374 "collapsed": "fa fa-caret-left" if rtl else "fa fa-caret-right", 1375 } 1376 1377 # Client-side widget options 1378 widget_opts = {"fRules": rules.use_facls, 1379 "tRules": use_tacls, 1380 "useRealms": rules.entity_realm, 1381 "permissions": self.get_permissions(), 1382 "defaultPermissions": self.get_default_permissions(), 1383 "modules": self.get_active_modules(), 1384 "icons": icons, 1385 } 1386 1387 if use_tacls: 1388 widget_opts["models"] = self.get_active_models() 1389 1390 # Localized strings for client-side widget 1391 i18n = {"rm_Add": T("Add"), 1392 "rm_AddRule": T("Add Rule"), 1393 "rm_AllEntities": T("All Entities"), 1394 "rm_AllRecords": T("All Records"), 1395 "rm_AssignedEntities": T("Assigned Entities"), 1396 "rm_Cancel": T("Cancel"), 1397 "rm_CollapseAll": T("Collapse All"), 1398 "rm_ConfirmDeleteRule": T("Do you want to delete this rule?"), 1399 "rm_Default": T("default"), 1400 "rm_DeleteRule": T("Delete"), 1401 "rm_ExpandAll": T("Expand All"), 1402 "rm_NoAccess": T("No access"), 1403 "rm_NoRestrictions": T("No restrictions"), 1404 "rm_Others": T("Others"), 1405 "rm_OwnedRecords": T("Owned Records"), 1406 "rm_Page": T("Page"), 1407 "rm_RestrictedTables": T("Restricted Tables"), 1408 "rm_Scope": T("Scope"), 1409 "rm_SystemTables": T("System Tables"), 1410 "rm_Table": T("Table"), 1411 "rm_UnrestrictedTables": T("Unrestricted Tables"), 1412 } 1413 1414 # Inject the client-side script 1415 self.inject_script(widget_id, widget_opts, i18n) 1416 1417 return widget
1418 1419 # -------------------------------------------------------------------------
1420 - def get_active_modules(self):
1421 """ 1422 Get a JSON-serializable dict of active modules 1423 1424 @returns: a dict {prefix: (name_nice, restricted)} 1425 """ 1426 1427 # Modules where access rules do not apply (or are hard-coded) 1428 exclude = ("appadmin", "errors") 1429 1430 # Active modules 1431 modules = current.deployment_settings.modules 1432 active= {k: (s3_str(modules[k].name_nice), modules[k].restricted) 1433 for k in modules if k not in exclude 1434 } 1435 1436 # Special controllers for dynamic models 1437 if current.auth.permission.use_facls: 1438 active["default/dt"] = (s3_str(current.T("Dynamic Models")), True) 1439 1440 return active
1441 1442 # -------------------------------------------------------------------------
1443 - def get_active_models(self):
1444 """ 1445 Get a JSON-serializable dict of active data models 1446 1447 @returns: a dict {prefix: {tablename: restricted}} 1448 """ 1449 1450 # Get all table names 1451 db_tables = current.cache.ram("permission_widget_all_tables", 1452 self.get_db_tables, 1453 time_expire = 14400, 1454 ) 1455 1456 # Count the number of restricting roles per table 1457 # @see: S3Permission.table_restricted() 1458 rtable = current.auth.permission.table 1459 query = (rtable.tablename != None) & \ 1460 (rtable.controller == None) & \ 1461 (rtable.function == None) & \ 1462 (rtable.deleted == False) 1463 numroles = rtable.group_id.count() 1464 tablename = rtable.tablename 1465 rows = current.db(query).select(tablename, 1466 numroles, 1467 groupby = tablename, 1468 ) 1469 restrictions = {row[tablename]: row[numroles] for row in rows} 1470 1471 # Sort tablenames after module and mark number of restrictions 1472 models = {} 1473 for tablename in db_tables: 1474 1475 prefix = tablename.split("_", 1)[0] 1476 1477 if prefix in ("auth", "sync", "s3", "scheduler"): 1478 prefix = "_system" 1479 1480 if prefix not in models: 1481 models[prefix] = {} 1482 1483 models[prefix][tablename] = restrictions.get(tablename, 0) 1484 1485 return models
1486 1487 # -------------------------------------------------------------------------
1488 - def get_db_tables(self):
1489 """ 1490 Return all table names in the database; in separate function 1491 to allow caching because it requires to load all models once 1492 1493 @returns: db.tables 1494 """ 1495 1496 db = current.db 1497 s3db = current.s3db 1498 1499 # Load all static models 1500 s3db.load_all_models() 1501 1502 # Load all dynamic tables (TODO: how does this make sense?) 1503 #ttable = s3db.s3_table 1504 #rows = db(ttable.deleted != True).select(ttable.name) 1505 #for row in rows: 1506 # s3db.table(row.name) 1507 1508 return db.tables
1509 1510 # -------------------------------------------------------------------------
1511 - def get_permissions(self):
1512 """ 1513 Get a JSON-serializable list of permissions 1514 1515 @returns: an ordered list of dicts: 1516 [{l: label, 1517 b: bit, 1518 o: relevant for owned records, 1519 }, 1520 ... 1521 ] 1522 """ 1523 1524 permission = current.auth.permission 1525 1526 opts = permission.PERMISSION_OPTS 1527 skip = 0x0000 1528 1529 # Hide approval-related permissions if record approval is disabled 1530 if not current.deployment_settings.get_auth_record_approval(): 1531 skip |= permission.REVIEW | permission.APPROVE 1532 1533 output = [] 1534 for bit, label in opts.items(): 1535 1536 if bit & skip: 1537 continue 1538 1539 output.append({"l": s3_str(label), 1540 "b": bit, 1541 "o": bit != permission.CREATE, 1542 }) 1543 return output
1544 1545 # -------------------------------------------------------------------------
1546 - def get_default_permissions(self):
1547 """ 1548 Get default permissions, i.e. those granted by roles the user 1549 has by default 1550 1551 @returns: a dict {tablename: (uACL, oACL)} 1552 """ 1553 1554 permissions = current.auth.permission 1555 table = permissions.table 1556 1557 default_roles = self.default_roles 1558 default_permissions = {} 1559 1560 if table and default_roles: 1561 query = (table.group_id.belongs(default_roles)) 1562 if not permissions.use_facls: 1563 query &= (table.function == None) 1564 if not permissions.use_tacls: 1565 query &= (table.tablename == None) 1566 query &= (table.deleted == False) 1567 rows = current.db(query).select(table.controller, 1568 table.function, 1569 table.tablename, 1570 table.uacl, 1571 table.oacl, 1572 ) 1573 for row in rows: 1574 target = row.tablename 1575 if not target: 1576 c = row.controller 1577 if c: 1578 target = "%s/%s" % (c, row.function or "*") 1579 else: 1580 continue 1581 rules = default_permissions.get(target) 1582 if rules: 1583 default_permissions[target] = (rules[0] | row.uacl, 1584 rules[1] | row.oacl, 1585 ) 1586 else: 1587 default_permissions[target] = (row.uacl, row.oacl) 1588 1589 return default_permissions
1590 1591 # -------------------------------------------------------------------------
1592 - def inject_script(self, widget_id, options, i18n):
1593 """ 1594 Inject the necessary JavaScript for the widget 1595 1596 @param widget_id: the widget ID 1597 (=element ID of the person_id field) 1598 @param options: JSON-serializable dict of widget options 1599 @param i18n: translations of screen messages rendered by 1600 the client-side script, 1601 a dict {messageKey: translation} 1602 """ 1603 1604 s3 = current.response.s3 1605 1606 # Static script 1607 if s3.debug: 1608 script = "/%s/static/scripts/S3/s3.ui.permissions.js" % \ 1609 current.request.application 1610 else: 1611 script = "/%s/static/scripts/S3/s3.ui.permissions.min.js" % \ 1612 current.request.application 1613 scripts = s3.scripts 1614 if script not in scripts: 1615 scripts.append(script) 1616 self.inject_i18n(i18n) 1617 1618 # Widget options 1619 opts = {} 1620 if options: 1621 opts.update(options) 1622 1623 # Widget instantiation 1624 script = '''$('#%(widget_id)s').permissionEdit(%(options)s)''' % \ 1625 {"widget_id": widget_id, 1626 "options": json.dumps(opts, separators=SEPARATORS), 1627 } 1628 jquery_ready = s3.jquery_ready 1629 if script not in jquery_ready: 1630 jquery_ready.append(script)
1631 1632 # -------------------------------------------------------------------------
1633 - def inject_i18n(self, labels):
1634 """ 1635 Inject translations for screen messages rendered by the 1636 client-side script 1637 1638 @param labels: dict of translations {messageKey: translation} 1639 """ 1640 1641 strings = ['''i18n.%s="%s"''' % (k, s3_str(v)) 1642 for k, v in labels.items()] 1643 current.response.s3.js_global.append("\n".join(strings))
1644
1645 # ============================================================================= 1646 -class S3RolesWidget(object):
1647 """ 1648 Form widget to assign roles to users 1649 """ 1650
1651 - def __init__(self, 1652 mode="roles", 1653 items=None, 1654 use_realms=False, 1655 realm_types=None, 1656 realms=None, 1657 ajax_url=None, 1658 ):
1659 """ 1660 Constructor 1661 1662 @param mode: what to assign ("roles"|"users") 1663 @param items: the assignable items (roles or users), dict, 1664 structure see get_managed_roles/get_managed_users 1665 @param use_realms: boolean, whether to use realms 1666 @param realm_types: the realm types and their labels, tuple, 1667 format see get_managed_realms 1668 @param realms: the realms, dict, structure see get_managed_realms 1669 @param ajax_url: the URL for Ajax modification of assignments 1670 """ 1671 1672 self.mode = mode 1673 1674 self.items = items 1675 1676 self.use_realms = use_realms 1677 self.realm_types = realm_types 1678 self.realms = realms 1679 1680 self.ajax_url = ajax_url
1681 1682 # -------------------------------------------------------------------------
1683 - def __call__(self, field, value, **attributes):
1684 """ 1685 Form builder entry point 1686 1687 @param field: the Field 1688 @param value: the current (or default) value of the field 1689 @param attributes: HTML attributes for the widget 1690 """ 1691 1692 T = current.T 1693 1694 # Widget ID 1695 widget_id = attributes.get("_id") or str(field).replace(".", "_") 1696 1697 # Field name 1698 name = attributes.get("_name") or field.name 1699 1700 # Extract the current assignments 1701 if value: 1702 assignments = self.get_current_assignments(value) 1703 else: 1704 assignments = [] 1705 1706 # Construct the widget 1707 widget = DIV(INPUT(_type = "hidden", 1708 _name = name, 1709 _value = value, 1710 _id = widget_id + "-id", 1711 ), 1712 INPUT(_type = "hidden", 1713 _name = "assigned", 1714 _value = json.dumps(assignments, separators=SEPARATORS), 1715 _id = widget_id + "-data", 1716 ), 1717 _id = widget_id, 1718 _class = "rm-assign-widget", 1719 ) 1720 1721 # Client-side widget options 1722 widget_opts = {"mode": self.mode, 1723 "ajaxURL": self.ajax_url, 1724 "items": self.items, 1725 "useRealms": self.use_realms, 1726 "realms": self.realms, 1727 "realmTypes": self.realm_types, 1728 } 1729 1730 # Localized strings for client-side widget 1731 if self.mode == "roles": 1732 CONFIRM = T("Do you want to remove the %(role)s role?") 1733 else: 1734 CONFIRM = T("Do you want to remove %(user)s from this role?") 1735 i18n = {"rm_Add": T("Add"), 1736 "rm_Cancel": T("Cancel"), 1737 "rm_ConfirmDeleteAssignment": CONFIRM, 1738 "rm_Delete": T("Delete"), 1739 "rm_DeletionFailed": T("Deletion Failed"), 1740 "rm_ForEntity": T("For Entity"), 1741 "rm_Roles": T("Roles"), 1742 "rm_SubmissionFailed": T("Submission Failed"), 1743 "rm_Users": T("Users"), 1744 } 1745 1746 # Inject the client-side script 1747 self.inject_script(widget_id, widget_opts, i18n) 1748 1749 return widget
1750 1751 # -------------------------------------------------------------------------
1752 - def get_current_assignments(self, record_id):
1753 """ 1754 Get the current assignments for the user/role 1755 1756 @param record_id: the user or role ID 1757 1758 @returns: a list of tuples (roleID|userID, realmID) 1759 """ 1760 1761 auth = current.auth 1762 table = auth.settings.table_membership 1763 1764 if self.mode == "roles": 1765 query = (table.user_id == record_id) & \ 1766 (table.group_id.belongs(self.items.keys())) 1767 field = table.group_id 1768 else: 1769 query = (table.group_id == record_id) & \ 1770 (table.user_id.belongs(self.items.keys())) 1771 field = table.user_id 1772 1773 use_realms = self.use_realms 1774 if use_realms and \ 1775 not auth.s3_has_role(auth.get_system_roles().ADMIN): 1776 managed_realms = set(self.realms.keys()) 1777 none = None in managed_realms 1778 managed_realms.discard(None) 1779 q = (table.pe_id.belongs(managed_realms)) if managed_realms else None 1780 if none: 1781 n = (table.pe_id == None) 1782 q = q | n if q else n 1783 if q: 1784 query &= q 1785 1786 query &= (table.deleted == False) 1787 rows = current.db(query).select(field, table.pe_id) 1788 1789 assignments = set() 1790 for row in rows: 1791 pe_id = row.pe_id if use_realms else None 1792 assignments.add((row[field], pe_id)) 1793 1794 return list(assignments)
1795 1796 # -------------------------------------------------------------------------
1797 - def inject_script(self, widget_id, options, i18n):
1798 """ 1799 Inject the necessary JavaScript for the widget 1800 1801 @param widget_id: the widget ID 1802 (=element ID of the person_id field) 1803 @param options: JSON-serializable dict of widget options 1804 @param i18n: translations of screen messages rendered by 1805 the client-side script, 1806 a dict {messageKey: translation} 1807 """ 1808 1809 s3 = current.response.s3 1810 1811 # Static script 1812 if s3.debug: 1813 script = "/%s/static/scripts/S3/s3.ui.roles.js" % \ 1814 current.request.application 1815 else: 1816 script = "/%s/static/scripts/S3/s3.ui.roles.min.js" % \ 1817 current.request.application 1818 scripts = s3.scripts 1819 if script not in scripts: 1820 scripts.append(script) 1821 self.inject_i18n(i18n) 1822 1823 # Widget options 1824 opts = {} 1825 if options: 1826 opts.update(options) 1827 1828 # Widget instantiation 1829 script = '''$('#%(widget_id)s').roleManager(%(options)s)''' % \ 1830 {"widget_id": widget_id, 1831 "options": json.dumps(opts, separators=SEPARATORS), 1832 } 1833 jquery_ready = s3.jquery_ready 1834 if script not in jquery_ready: 1835 jquery_ready.append(script)
1836 1837 # -------------------------------------------------------------------------
1838 - def inject_i18n(self, labels):
1839 """ 1840 Inject translations for screen messages rendered by the 1841 client-side script 1842 1843 @param labels: dict of translations {messageKey: translation} 1844 """ 1845 1846 strings = ['''i18n.%s="%s"''' % (k, s3_str(v)) 1847 for k, v in labels.items()] 1848 current.response.s3.js_global.append("\n".join(strings))
1849
1850 # ============================================================================= 1851 -class S3RolesExport(object):
1852 """ 1853 Roles Exporter 1854 """ 1855
1856 - def __init__(self, resource):
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 # Optional columns 1868 self.col_hidden = False 1869 self.col_protected = False 1870 self.col_entity = False 1871 1872 # Look up the roles 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 # Generate roles dict 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 # Look up all rules, ordered by UID, controller, function, table 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 # Look up all org entities 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 # -------------------------------------------------------------------------
1935 - def as_csv(self):
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 # Faster, where available 1946 except ImportError: 1947 from StringIO import StringIO 1948 1949 # Optional columns 1950 col_protected = self.col_protected 1951 col_hidden = self.col_hidden 1952 col_entity = self.col_entity 1953 1954 # Role fields 1955 fieldnames = ["uid", "role", "description"] 1956 if col_hidden: 1957 fieldnames.append("hidden") 1958 if col_protected: 1959 fieldnames.append("protected") 1960 1961 # Rule fields 1962 fieldnames.extend(["controller", "function", "table", "uacl", "oacl"]) 1963 if col_entity: 1964 fieldnames.extend("entity") 1965 1966 # Helper to get the role UID for a rule 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 # Sort the rules 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 # Create the CSV 1981 f = StringIO() 1982 writer = csv.DictWriter(f, fieldnames=fieldnames) 1983 writer.writeheader() 1984 1985 # Write the rules to the CSV 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 # The entity column (optional) 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 # The target columns (controller, function, table) 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 # The permission columns (uacl, oacl) 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 # Add role columns 2027 rule_dict.update(role_dict) 2028 2029 # Write the rule 2030 writer.writerow(rule_dict) 2031 2032 f.seek(0) 2033 return f
2034 2035 # -------------------------------------------------------------------------
2036 - def encode_permissions(self, permissions, explicit_none=False):
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 # END ========================================================================= 2061