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

Source Code for Module s3.s3aaa

   1  # -*- coding: utf-8 -*- 
   2   
   3  """ Authentication, Authorization, Accounting 
   4   
   5      @requires: U{B{I{gluon}} <http://web2py.com>} 
   6   
   7      @copyright: (c) 2010-2019 Sahana Software Foundation 
   8      @license: MIT 
   9   
  10      Permission is hereby granted, free of charge, to any person 
  11      obtaining a copy of this software and associated documentation 
  12      files (the "Software"), to deal in the Software without 
  13      restriction, including without limitation the rights to use, 
  14      copy, modify, merge, publish, distribute, sublicense, and/or sell 
  15      copies of the Software, and to permit persons to whom the 
  16      Software is furnished to do so, subject to the following 
  17      conditions: 
  18   
  19      The above copyright notice and this permission notice shall be 
  20      included in all copies or substantial portions of the Software. 
  21   
  22      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  23      EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  24      OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  25      NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
  26      HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
  27      WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
  28      FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 
  29      OTHER DEALINGS IN THE SOFTWARE. 
  30  """ 
  31   
  32  __all__ = ("AuthS3", 
  33             "S3Permission", 
  34             "S3Audit", 
  35             "S3OrgRoleManager", 
  36             "S3PersonRoleManager", 
  37             ) 
  38   
  39  import datetime 
  40  import json 
  41  #import re 
  42  import time 
  43   
  44  from collections import OrderedDict 
  45  from uuid import uuid4 
  46   
  47  #from gluon import * 
  48  from gluon import current, redirect, CRYPT, DAL, HTTP, SQLFORM, URL, \ 
  49                    A, DIV, INPUT, LABEL, OPTGROUP, OPTION, SELECT, SPAN, XML, \ 
  50                    IS_EMAIL, IS_EMPTY_OR, IS_EXPR, IS_IN_DB, IS_IN_SET, \ 
  51                    IS_LOWER, IS_NOT_EMPTY, IS_NOT_IN_DB 
  52   
  53  from gluon.sqlhtml import OptionsWidget 
  54  from gluon.storage import Storage 
  55  from gluon.tools import Auth, callback, DEFAULT, replace_id 
  56  from gluon.utils import web2py_uuid 
  57   
  58  from s3dal import Row, Rows, Query, Table, Field, original_tablename 
  59  from s3datetime import S3DateTime 
  60  from s3error import S3PermissionError 
  61  from s3fields import S3MetaFields, S3Represent, s3_comments 
  62  from s3rest import S3Method, S3Request 
  63  from s3track import S3Tracker 
  64  from s3utils import s3_addrow, s3_get_extension, s3_mark_required, s3_str 
  65  from s3validators import IS_ISO639_2_LANGUAGE_CODE 
66 67 # ============================================================================= 68 -class AuthS3(Auth):
69 70 """ 71 S3 extensions of the gluon.tools.Auth class 72 73 - override: 74 - __init__ 75 - define_tables 76 - login_bare 77 - set_cookie 78 - login 79 - register 80 - email_reset_password 81 - verify_email 82 - profile 83 - has_membership 84 - requires_membership 85 86 - S3 extension for user registration: 87 - s3_register_validation 88 - s3_user_register_onaccept 89 90 - S3 extension for user administration: 91 - configure_user_fields 92 - s3_verify_user 93 - s3_approve_user 94 - s3_link_user 95 - s3_user_profile_onaccept 96 - s3_link_to_person 97 - s3_link_to_organisation 98 - s3_link_to_human_resource 99 - s3_link_to_member 100 - s3_approver 101 102 - S3 custom authentication methods: 103 - s3_impersonate 104 - s3_logged_in 105 106 - S3 user role management: 107 - get_system_roles 108 - s3_set_roles 109 - s3_create_role 110 - s3_delete_role 111 - s3_assign_role 112 - s3_withdraw_role 113 - s3_has_role 114 - s3_group_members 115 116 - S3 ACL management: 117 - s3_update_acls 118 119 - S3 user identification helpers: 120 - s3_get_user_id 121 - s3_user_pe_id 122 - s3_logged_in_person 123 - s3_logged_in_human_resource 124 125 - S3 core authorization methods: 126 - s3_has_permission 127 - s3_accessible_query 128 129 - S3 variants of web2py authorization methods: 130 - s3_has_membership 131 - s3_requires_membership 132 133 - S3 record ownership methods: 134 - s3_make_session_owner 135 - s3_session_owns 136 - s3_set_record_owner 137 """ 138 139 # Configuration of UIDs for system roles 140 S3_SYSTEM_ROLES = Storage(ADMIN = "ADMIN", 141 AUTHENTICATED = "AUTHENTICATED", 142 ANONYMOUS = "ANONYMOUS", 143 EDITOR = "EDITOR", 144 MAP_ADMIN = "MAP_ADMIN", 145 ORG_ADMIN = "ORG_ADMIN", 146 ORG_GROUP_ADMIN = "ORG_GROUP_ADMIN", 147 ) 148
149 - def __init__(self):
150 151 """ Initialise parent class & make any necessary modifications """ 152 153 Auth.__init__(self, current.db) 154 155 self.settings.lock_keys = False 156 self.settings.login_userfield = "email" 157 self.settings.lock_keys = True 158 159 messages = self.messages 160 messages.lock_keys = False 161 162 # @ToDo Move these to deployment_settings 163 messages.approve_user = \ 164 """Your action is required to approve a New User for %(system_name)s: 165 %(first_name)s %(last_name)s 166 %(email)s 167 Please go to %(url)s to approve this user.""" 168 messages.email_approver_failed = "Failed to send mail to Approver - see if you can notify them manually!" 169 messages.email_sent = "Verification Email sent - please check your email to validate. If you do not receive this email please check you junk email or spam filters" 170 messages.email_verification_failed = "Unable to send verification email - either your email is invalid or our email server is down" 171 messages.email_verified = "Email verified - you can now login" 172 messages.duplicate_email = "This email address is already in use" 173 messages.help_utc_offset = "The time difference between UTC and your timezone, specify as +HHMM for eastern or -HHMM for western timezones." 174 messages.help_mobile_phone = "Entering a phone number is optional, but doing so allows you to subscribe to receive SMS messages." 175 messages.help_organisation = "Entering an Organization is optional, but doing so directs you to the appropriate approver & means you automatically get the appropriate permissions." 176 messages.help_image = "You can either use %(gravatar)s or else upload a picture here. The picture will be resized to 50x50." 177 messages.label_image = "Profile Image" 178 messages.label_organisation_id = "Organization" 179 messages.label_org_group_id = "Coalition" 180 messages.label_remember_me = "Remember Me" 181 messages.label_utc_offset = "UTC Offset" 182 #messages.logged_in = "Signed In" 183 #messages.logged_out = "Signed Out" 184 #messages.submit_button = "Signed In" 185 messages.new_user = \ 186 """A New User has registered for %(system_name)s: 187 %(first_name)s %(last_name)s 188 %(email)s 189 No action is required.""" 190 messages.password_reset_button = "Request password reset" 191 messages.profile_save_button = "Apply changes" 192 messages.registration_disabled = "Registration Disabled!" 193 messages.registration_verifying = "You haven't yet Verified your account - please check your email" 194 messages.reset_password = "Click on the link %(url)s to reset your password" 195 messages.verify_email = "Click on the link %(url)s to verify your email" 196 messages.verify_email_subject = "%(system_name)s - Verify Email" 197 messages.welcome_email_subject = "Welcome to %(system_name)s" 198 messages.welcome_email = \ 199 """Welcome to %(system_name)s 200 - You can start using %(system_name)s at: %(url)s 201 - To edit your profile go to: %(url)s%(profile)s 202 Thank you""" 203 messages.lock_keys = True 204 205 # S3Permission 206 self.permission = S3Permission(self) 207 208 # Set to True to override any authorization 209 self.override = False 210 211 # Set to True to indicate that all current transactions 212 # are to be rolled back (e.g. trial phase of interactive imports) 213 self.rollback = False 214 215 # Site types (for OrgAuth) 216 T = current.T 217 if current.deployment_settings.get_ui_label_camp(): 218 shelter = T("Camp") 219 else: 220 shelter = T("Shelter") 221 self.org_site_types = Storage(transport_airport = T("Airport"), 222 msg_basestation = T("Cell Tower"), 223 cr_shelter = shelter, 224 org_facility = T("Facility"), # @ToDo: Use deployment setting for label 225 org_office = T("Office"), 226 transport_heliport = T("Heliport"), 227 hms_hospital = T("Hospital"), 228 fire_station = T("Fire Station"), 229 dvi_morgue = T("Morgue"), 230 police_station = T("Police Station"), 231 edu_school = T("School"), 232 transport_seaport = T("Seaport"), 233 inv_warehouse = T("Warehouse"), 234 ) 235 236 # Name prefixes of tables which must not be manipulated from remote, 237 # CLI can override with auth.override=True 238 self.PROTECTED = ("admin",)
239 240 # -------------------------------------------------------------------------
241 - def define_tables(self, migrate=True, fake_migrate=False):
242 """ 243 to be called unless tables are defined manually 244 245 usages:: 246 247 # defines all needed tables and table files 248 # UUID + "_auth_user.table", ... 249 auth.define_tables() 250 251 # defines all needed tables and table files 252 # "myprefix_auth_user.table", ... 253 auth.define_tables(migrate="myprefix_") 254 255 # defines all needed tables without migration/table files 256 auth.define_tables(migrate=False) 257 """ 258 259 db = current.db 260 settings = self.settings 261 messages = self.messages 262 deployment_settings = current.deployment_settings 263 define_table = db.define_table 264 265 # User table 266 utable = settings.table_user 267 uname = settings.table_user_name 268 if not utable: 269 utable_fields = [ 270 Field("first_name", length=128, notnull=True, 271 default="", 272 requires = \ 273 IS_NOT_EMPTY(error_message=messages.is_empty), 274 ), 275 Field("last_name", length=128, 276 default=""), 277 Field("email", length=255, unique=True, 278 default=""), 279 # Used For chat in default deployment config 280 Field("username", length=255, default="", 281 readable=False, writable=False), 282 Field("language", length=16, 283 default = deployment_settings.get_L10n_default_language()), 284 Field("utc_offset", length=16, 285 readable=False, writable=False), 286 Field("organisation_id", "integer", 287 readable=False, writable=False), 288 Field("org_group_id", "integer", 289 readable=False, writable=False), 290 Field("site_id", "integer", 291 readable=False, writable=False), 292 Field("link_user_to", "list:string", 293 readable=False, writable=False), 294 Field("registration_key", length=512, 295 default="", 296 readable=False, writable=False), 297 Field("reset_password_key", length=512, 298 default="", 299 readable=False, writable=False), 300 Field("deleted", "boolean", 301 default=False, 302 readable=False, writable=False), 303 Field("timestmp", "datetime", 304 default="", 305 readable=False, writable=False), 306 s3_comments(readable=False, writable=False), 307 # Additional meta fields required for sync: 308 S3MetaFields.uuid(), 309 #S3MetaFields.mci(), 310 S3MetaFields.created_on(), 311 S3MetaFields.modified_on(), 312 ] 313 314 userfield = settings.login_userfield 315 if userfield != "email": 316 # Use username (not used by default in Sahana) 317 utable_fields.insert(2, Field(userfield, length=128, 318 default="", 319 unique=True)) 320 321 # Insert password field after either email or username 322 passfield = settings.password_field 323 utable_fields.insert(3, Field(passfield, "password", length=512, 324 requires=CRYPT(key=settings.hmac_key, 325 min_length=deployment_settings.get_auth_password_min_length(), 326 digest_alg="sha512"), 327 readable=False, 328 label=messages.label_password)) 329 330 define_table(uname, 331 migrate = migrate, 332 fake_migrate=fake_migrate, 333 *utable_fields) 334 utable = settings.table_user = db[uname] 335 336 # Fields configured in configure_user_fields 337 338 # Temporary User Table 339 # for storing User Data that will be used to create records for 340 # the user once they are approved 341 define_table("auth_user_temp", 342 Field("user_id", utable), 343 Field("home"), 344 Field("mobile"), 345 Field("image", "upload", 346 length = current.MAX_FILENAME_LENGTH, 347 ), 348 S3MetaFields.uuid(), 349 S3MetaFields.created_on(), 350 S3MetaFields.modified_on(), 351 ) 352 353 # Group table (roles) 354 gtable = settings.table_group 355 gname = settings.table_group_name 356 if not gtable: 357 define_table(gname, 358 # Group unique ID, must be notnull+unique: 359 Field("uuid", length=64, notnull=True, unique=True, 360 readable=False, writable=False), 361 # Group does not appear in the Role Manager: 362 # (can neither assign, nor modify, nor delete) 363 Field("hidden", "boolean", 364 readable=False, writable=False, 365 default=False), 366 # Group cannot be modified in the Role Manager: 367 # (can assign, but neither modify nor delete) 368 Field("system", "boolean", 369 readable=False, writable=False, 370 default=False), 371 # Group cannot be deleted in the Role Manager: 372 # (can assign and modify, but not delete) 373 Field("protected", "boolean", 374 readable=False, writable=False, 375 default=False), 376 # Role name: 377 Field("role", length=255, unique=True, 378 default="", 379 requires = IS_NOT_IN_DB(db, "%s.role" % gname), 380 label=messages.label_role), 381 Field("description", "text", 382 label=messages.label_description), 383 # Additional meta fields required for sync: 384 S3MetaFields.created_on(), 385 S3MetaFields.modified_on(), 386 S3MetaFields.deleted(), 387 #S3MetaFields.deleted_fk(), 388 #S3MetaFields.deleted_rb(), 389 migrate = migrate, 390 fake_migrate=fake_migrate, 391 ) 392 gtable = settings.table_group = db[gname] 393 394 # Group membership table (user<->role) 395 if not settings.table_membership: 396 define_table( 397 settings.table_membership_name, 398 Field("user_id", utable, 399 requires = IS_IN_DB(db, "%s.id" % uname, 400 "%(id)s: %(first_name)s %(last_name)s"), 401 label=messages.label_user_id), 402 Field("group_id", gtable, 403 requires = IS_IN_DB(db, "%s.id" % gname, 404 "%(id)s: %(role)s"), 405 represent = S3Represent(lookup=gname, fields=["role"]), 406 label=messages.label_group_id), 407 # Realm 408 Field("pe_id", "integer"), 409 migrate = migrate, 410 fake_migrate=fake_migrate, 411 *S3MetaFields.sync_meta_fields()) 412 settings.table_membership = db[settings.table_membership_name] 413 414 # Define Eden permission table 415 self.permission.define_table(migrate=migrate, 416 fake_migrate=fake_migrate) 417 418 #security_policy = deployment_settings.get_security_policy() 419 #if security_policy not in (1, 2, 3, 4, 5, 6, 7, 8) and \ 420 # not settings.table_permission: 421 # # Permissions table (group<->permission) 422 # # NB This Web2Py table is deprecated / replaced in Eden by S3Permission 423 # settings.table_permission = define_table( 424 # settings.table_permission_name, 425 # Field("group_id", gtable, 426 # requires = IS_IN_DB(db, "%s.id" % gname, 427 # "%(id)s: %(role)s"), 428 # label=messages.label_group_id), 429 # Field("name", default="default", length=512, 430 # requires = IS_NOT_EMPTY(), 431 # label=messages.label_name), 432 # Field("table_name", length=512, 433 # # Needs to be defined after all tables created 434 # #requires = IS_IN_SET(db.tables), 435 # label=messages.label_table_name), 436 # Field("record_id", "integer", 437 # requires = IS_INT_IN_RANGE(0, 10 ** 9), 438 # label=messages.label_record_id), 439 # migrate = migrate, 440 # fake_migrate=fake_migrate) 441 442 # Event table (auth_event) 443 # Records Logins & ? 444 # @ToDo: Deprecate? At least make it configurable? 445 if not settings.table_event: 446 request = current.request 447 define_table( 448 settings.table_event_name, 449 Field("time_stamp", "datetime", 450 default=request.utcnow, 451 #label=messages.label_time_stamp 452 ), 453 Field("client_ip", 454 default=request.client, 455 #label=messages.label_client_ip 456 ), 457 Field("user_id", utable, default=None, 458 requires = IS_IN_DB(db, "%s.id" % uname, 459 "%(id)s: %(first_name)s %(last_name)s"), 460 #label=messages.label_user_id 461 ), 462 Field("origin", default="auth", length=512, 463 #label=messages.label_origin, 464 requires = IS_NOT_EMPTY()), 465 Field("description", "text", default="", 466 #label=messages.label_description, 467 requires = IS_NOT_EMPTY()), 468 migrate = migrate, 469 fake_migrate=fake_migrate, 470 *S3MetaFields.sync_meta_fields()) 471 settings.table_event = db[settings.table_event_name]
472 473 # -------------------------------------------------------------------------
474 - def login_bare(self, username, password):
475 """ 476 Logs user in 477 - extended to understand session.s3.roles 478 """ 479 480 settings = self.settings 481 utable = settings.table_user 482 userfield = settings.login_userfield 483 passfield = settings.password_field 484 query = (utable[userfield] == username) 485 user = current.db(query).select(limitby=(0, 1)).first() 486 password = utable[passfield].validate(password)[0] 487 if user: 488 if not user.registration_key and user[passfield] == password: 489 user = Storage(utable._filter_fields(user, id=True)) 490 current.session.auth = Storage(user=user, 491 last_visit=current.request.now, 492 expiration=settings.expiration) 493 self.user = user 494 self.s3_set_roles() 495 return user 496 return False
497 498 # ------------------------------------------------------------------------- 511 512 # -------------------------------------------------------------------------
513 - def login(self, 514 next = DEFAULT, 515 onvalidation = DEFAULT, 516 onaccept = DEFAULT, 517 log = DEFAULT, 518 inline = False, # Set to True to use an 'inline' variant of the style 519 lost_pw_link = None, 520 register_link = True, 521 ):
522 """ 523 Overrides Web2Py's login() to use custom flash styles & utcnow 524 525 @return: a login form 526 """ 527 528 T = current.T 529 db = current.db 530 messages = self.messages 531 request = current.request 532 response = current.response 533 session = current.session 534 settings = self.settings 535 deployment_settings = current.deployment_settings 536 537 utable = settings.table_user 538 userfield = settings.login_userfield 539 old_requires = utable[userfield].requires 540 utable[userfield].requires = [IS_NOT_EMPTY(), IS_LOWER()] 541 passfield = settings.password_field 542 try: 543 utable[passfield].requires[-1].min_length = 0 544 except: 545 pass 546 if onvalidation is DEFAULT: 547 onvalidation = settings.login_onvalidation 548 if onaccept is DEFAULT: 549 onaccept = settings.login_onaccept 550 if log is DEFAULT: 551 log = messages.login_log 552 553 user = None # default 554 555 response.title = T("Login") 556 557 # Do we use our own login form, or from a central source? 558 if settings.login_form == self: 559 560 if inline: 561 formstyle = deployment_settings.get_ui_inline_formstyle() 562 else: 563 formstyle = deployment_settings.get_ui_formstyle() 564 565 buttons = [] 566 567 # Self-registration action link 568 self_registration = deployment_settings.get_security_registration_visible() 569 if self_registration and register_link: 570 if self_registration == "index": 571 # Custom Registration page 572 controller = "index" 573 else: 574 # Default Registration page 575 controller = "user" 576 register_link = A(T("Register for Account"), 577 _href=URL(f=controller, args="register"), 578 _id="register-btn", 579 _class="action-lnk", 580 ) 581 buttons.append(register_link) 582 583 # Lost-password action link 584 if deployment_settings.get_auth_password_retrieval(): 585 if lost_pw_link is None: 586 lost_pw_link = deployment_settings.get_auth_password_changes() 587 if lost_pw_link: 588 lost_pw_link = A(T("Lost Password"), 589 _href=URL(f="user", args="retrieve_password"), 590 _class="action-lnk", 591 ) 592 buttons.append(lost_pw_link) 593 594 # Add submit button 595 #if buttons: 596 submit_button = INPUT(_type="submit", _value=T("Login")) 597 buttons.insert(0, submit_button) 598 599 form = SQLFORM(utable, 600 fields = [userfield, passfield], 601 hidden = dict(_next=request.vars._next), 602 showid = settings.showid, 603 submit_button = T("Login"), 604 delete_label = messages.delete_label, 605 formstyle = formstyle, 606 separator = settings.label_separator, 607 buttons = buttons, 608 ) 609 610 # Identify form for CSS 611 form.add_class("auth_login") 612 613 if settings.remember_me_form: 614 # Add a new input checkbox "remember me for longer" 615 s3_addrow(form, 616 "", 617 DIV(INPUT(_type="checkbox", 618 _class="checkbox", 619 _id="auth_user_remember", 620 _name="remember", 621 ), 622 LABEL(messages.label_remember_me, 623 _for="auth_user_remember", 624 ), 625 ), 626 "", 627 formstyle, 628 "auth_user_remember__row", 629 ) 630 631 if deployment_settings.get_auth_set_presence_on_login(): 632 s3_addrow(form, 633 "", 634 INPUT(_id="auth_user_clientlocation", 635 _name="auth_user_clientlocation", 636 _style="display:none", 637 ), 638 "", 639 formstyle, 640 "auth_user_client_location", 641 ) 642 response.s3.jquery_ready.append('''S3.getClientLocation($('#auth_user_clientlocation'))''') 643 644 captcha = settings.login_captcha or \ 645 (settings.login_captcha != False and settings.captcha) 646 if captcha: 647 s3_addrow(form, 648 captcha.label, 649 captcha, 650 captcha.comment, 651 formstyle, 652 "captcha__row", 653 ) 654 655 accepted_form = False 656 if form.accepts(request.post_vars, session, 657 formname="login", dbio=False, 658 onvalidation=onvalidation): 659 accepted_form = True 660 if userfield == "email": 661 # Check for Domains which can use Google's SMTP server for passwords 662 # @ToDo: an equivalent email_domains for other email providers 663 gmail_domains = deployment_settings.get_auth_gmail_domains() 664 office365_domains = deployment_settings.get_auth_office365_domains() 665 if gmail_domains or office365_domains: 666 from gluon.contrib.login_methods.email_auth import email_auth 667 domain = form.vars[userfield].split("@")[1] 668 if domain in gmail_domains: 669 settings.login_methods.append( 670 email_auth("smtp.gmail.com:587", "@%s" % domain)) 671 elif domain in office365_domains: 672 settings.login_methods.append( 673 email_auth("smtp.office365.com:587", "@%s" % domain)) 674 675 # Check for username in db 676 query = (utable[userfield] == form.vars[userfield]) 677 user = db(query).select(limitby=(0, 1)).first() 678 if user: 679 # User in db, check if registration pending or disabled 680 temp_user = user 681 if temp_user.registration_key == "pending": 682 response.warning = deployment_settings.get_auth_registration_pending() 683 return form 684 elif temp_user.registration_key in ("disabled", "blocked"): 685 response.error = messages.login_disabled 686 return form 687 elif not temp_user.registration_key is None and \ 688 temp_user.registration_key.strip(): 689 response.warning = \ 690 messages.registration_verifying 691 return form 692 # Try alternate logins 1st as these have the 693 # current version of the password 694 user = None 695 for login_method in settings.login_methods: 696 if login_method != self and \ 697 login_method(request.vars[userfield], 698 request.vars[passfield]): 699 if not self in settings.login_methods: 700 # do not store password in db 701 form.vars[passfield] = None 702 user = self.get_or_create_user(form.vars) 703 break 704 if not user: 705 # Alternates have failed, maybe because service inaccessible 706 if settings.login_methods[0] == self: 707 # Try logging in locally using cached credentials 708 if temp_user[passfield] == form.vars.get(passfield, ""): 709 # Success 710 user = temp_user 711 else: 712 # User not in db 713 if not settings.alternate_requires_registration: 714 # We're allowed to auto-register users from external systems 715 for login_method in settings.login_methods: 716 if login_method != self and \ 717 login_method(request.vars[userfield], 718 request.vars[passfield]): 719 if not self in settings.login_methods: 720 # Do not store password in db 721 form.vars[passfield] = None 722 # Ensure new users go through their post registration tasks 723 register_onaccept = settings.register_onaccept 724 if register_onaccept: 725 settings.register_onaccept = \ 726 [self.s3_register_onaccept, 727 register_onaccept, # Used by DRRPP 728 ] 729 else: 730 settings.register_onaccept = self.s3_register_onaccept 731 user = self.get_or_create_user(form.vars) 732 break 733 if not user: 734 self.log_event(settings.login_failed_log, 735 request.post_vars) 736 # Invalid login 737 session.error = messages.invalid_login 738 if inline: 739 # If inline, stay on the same page 740 next_url = URL(args=request.args, 741 vars=request.get_vars) 742 else: 743 # If not inline, return to configured login page 744 next_url = self.url(args=request.args, 745 vars=request.get_vars) 746 redirect(next_url) 747 else: 748 # Use a central authentication server 749 cas = settings.login_form 750 cas_user = cas.get_user() 751 if cas_user: 752 cas_user[passfield] = None 753 # Ensure new users go through their post registration tasks 754 register_onaccept = settings.register_onaccept 755 if register_onaccept: 756 settings.register_onaccept = \ 757 [self.s3_register_onaccept, 758 register_onaccept, # Used by DRRPP 759 ] 760 else: 761 settings.register_onaccept = self.s3_register_onaccept 762 user = self.get_or_create_user(utable._filter_fields(cas_user)) 763 elif hasattr(cas, "login_form"): 764 return cas.login_form() 765 else: 766 # We need to pass through login again before going on 767 if next is DEFAULT: 768 next = request.vars._next or settings.login_next 769 next = "%s?_next=%s" % (URL(r=request), next) 770 redirect(cas.login_url(next)) 771 772 # Process authenticated users 773 if user: 774 user = Storage(utable._filter_fields(user, id=True)) 775 self.login_user(user) 776 if log and self.user: 777 self.log_event(log, self.user) 778 779 # How to continue 780 if next is DEFAULT: 781 if deployment_settings.has_module("setup") and deployment_settings.get_setup_wizard_questions() and self.s3_has_role("ADMIN"): 782 itable = current.s3db.setup_instance 783 instance = db(itable.url == "https://%s" % request.env.HTTP_HOST).select(itable.id, 784 itable.deployment_id, 785 itable.configured, 786 limitby = (0, 1) 787 ).first() 788 if instance and not instance.configured: 789 # Run Configuration Wizard 790 next = URL(c="setup", f="deployment", 791 args = [instance.deployment_id, "instance", instance.id, "wizard"]) 792 if next is DEFAULT: 793 next = request.vars._next or settings.login_next 794 if settings.login_form == self: 795 if accepted_form: 796 if onaccept: 797 onaccept(form) 798 if isinstance(next, (list, tuple)): 799 # fix issue with 2.6 800 next = next[0] 801 if next and not next[0] == "/" and next[:4] != "http": 802 next = self.url(next.replace("[id]", str(form.vars.id))) 803 redirect(next) 804 utable[userfield].requires = old_requires 805 return form 806 else: 807 redirect(next)
808 809 # -------------------------------------------------------------------------
810 - def change_password(self, 811 next=DEFAULT, 812 onvalidation=DEFAULT, 813 onaccept=DEFAULT, 814 log=DEFAULT, 815 ):
816 """ 817 Returns a form that lets the user change password 818 """ 819 820 if not self.is_logged_in(): 821 redirect(self.settings.login_url, 822 client_side=self.settings.client_side) 823 824 messages = self.messages 825 settings = self.settings 826 utable = settings.table_user 827 s = self.db(utable.id == self.user.id) 828 829 request = current.request 830 session = current.session 831 if next is DEFAULT: 832 next = self.get_vars_next() or settings.change_password_next 833 if onvalidation is DEFAULT: 834 onvalidation = settings.change_password_onvalidation 835 if onaccept is DEFAULT: 836 onaccept = settings.change_password_onaccept 837 if log is DEFAULT: 838 log = messages["change_password_log"] 839 passfield = settings.password_field 840 form = SQLFORM.factory( 841 Field("old_password", "password", 842 label=messages.old_password, 843 requires=utable[passfield].requires), 844 Field("new_password", "password", 845 label=messages.new_password, 846 requires=utable[passfield].requires), 847 Field("new_password2", "password", 848 label=messages.verify_password, 849 requires=[IS_EXPR( 850 "value==%s" % repr(request.vars.new_password), 851 messages.mismatched_password)]), 852 submit_button=messages.password_change_button, 853 hidden=dict(_next=next), 854 formstyle=current.deployment_settings.get_ui_formstyle(), 855 separator=settings.label_separator 856 ) 857 form.add_class("auth_change_password") 858 859 if form.accepts(request, session, 860 formname="change_password", 861 onvalidation=onvalidation, 862 hideerror=settings.hideerror): 863 864 if not form.vars["old_password"] == s.select(limitby=(0,1), orderby_on_limitby=False).first()[passfield]: 865 form.errors["old_password"] = messages.invalid_password 866 else: 867 d = {passfield: str(form.vars.new_password)} 868 s.update(**d) 869 session.confirmation = messages.password_changed 870 self.log_event(log, self.user) 871 callback(onaccept, form) 872 if not next: 873 next = self.url(args=request.args) 874 else: 875 next = replace_id(next, form) 876 redirect(next, client_side=settings.client_side) 877 878 return form
879 880 # -------------------------------------------------------------------------
881 - def reset_password(self, 882 next=DEFAULT, 883 onvalidation=DEFAULT, 884 onaccept=DEFAULT, 885 log=DEFAULT, 886 ):
887 """ 888 Returns a form to reset the user password, overrides web2py's 889 version of the method to not swallow the _next var. 890 """ 891 892 table_user = self.table_user() 893 request = current.request 894 session = current.session 895 896 messages = self.messages 897 settings = self.settings 898 899 if next is DEFAULT: 900 next = self.get_vars_next() or settings.reset_password_next 901 902 if settings.prevent_password_reset_attacks: 903 key = request.vars.key 904 if key: 905 session._reset_password_key = key 906 session._reset_password_next = next 907 redirect(self.url(args = "reset_password")) 908 else: 909 key = session._reset_password_key 910 next = session._reset_password_next 911 else: 912 key = request.vars.key 913 914 try: 915 t0 = int(key.split('-')[0]) 916 if time.time() - t0 > 60 * 60 * 24: 917 raise Exception 918 user = table_user(reset_password_key=key) 919 if not user: 920 raise Exception 921 except Exception: 922 session.flash = messages.invalid_reset_password 923 redirect(next, client_side=settings.client_side) 924 925 key = user.registration_key 926 if key in ("pending", "disabled", "blocked") or (key or "").startswith("pending"): 927 session.flash = messages.registration_pending 928 redirect(next, client_side=settings.client_side) 929 930 if onvalidation is DEFAULT: 931 onvalidation = settings.reset_password_onvalidation 932 if onaccept is DEFAULT: 933 onaccept = settings.reset_password_onaccept 934 935 passfield = settings.password_field 936 form = SQLFORM.factory( 937 Field("new_password", "password", 938 label = messages.new_password, 939 requires = table_user[passfield].requires, 940 ), 941 Field("new_password2", "password", 942 label = messages.verify_password, 943 requires = [IS_EXPR("value==%s" % repr(request.vars.new_password), 944 messages.mismatched_password) 945 ], 946 ), 947 submit_button = messages.password_reset_button, 948 hidden = dict(_next=next), 949 formstyle = settings.formstyle, 950 separator = settings.label_separator 951 ) 952 if form.accepts(request, session, onvalidation=onvalidation, 953 hideerror=settings.hideerror): 954 user.update_record( 955 **{passfield: str(form.vars.new_password), 956 "registration_key": "", 957 "reset_password_key": "", 958 }) 959 session.flash = messages.password_changed 960 if settings.login_after_password_change: 961 self.login_user(user) 962 callback(onaccept, form) 963 redirect(next, client_side=settings.client_side) 964 return form
965 966 # -------------------------------------------------------------------------
967 - def request_reset_password(self, 968 next=DEFAULT, 969 onvalidation=DEFAULT, 970 onaccept=DEFAULT, 971 log=DEFAULT, 972 ):
973 """ 974 Returns a form to reset the user password, overrides web2py's 975 version of the method to apply Eden formstyles. 976 977 @param next: URL to redirect to after successful form submission 978 @param onvalidation: callback to validate password reset form 979 @param onaccept: callback to post-process password reset request 980 @param log: event description for the log (string) 981 """ 982 983 messages = self.messages 984 settings = self.settings 985 if not settings.mailer: 986 current.response.error = messages.function_disabled 987 return "" 988 989 utable = settings.table_user 990 request = current.request 991 session = current.session 992 captcha = settings.retrieve_password_captcha or \ 993 (settings.retrieve_password_captcha != False and settings.captcha) 994 995 if next is DEFAULT: 996 next = self.get_vars_next() or settings.request_reset_password_next 997 if onvalidation is DEFAULT: 998 onvalidation = settings.reset_password_onvalidation 999 if onaccept is DEFAULT: 1000 onaccept = settings.reset_password_onaccept 1001 if log is DEFAULT: 1002 log = messages["reset_password_log"] 1003 userfield = settings.login_userfield 1004 if userfield == "email": 1005 utable.email.requires = [ 1006 IS_EMAIL(error_message=messages.invalid_email), 1007 IS_IN_DB(self.db, utable.email, 1008 error_message=messages.invalid_email)] 1009 else: 1010 utable[userfield].requires = [ 1011 IS_IN_DB(self.db, utable[userfield], 1012 error_message=messages.invalid_username)] 1013 form = SQLFORM(utable, 1014 fields = [userfield], 1015 hidden = dict(_next=next), 1016 showid = settings.showid, 1017 submit_button = messages.password_reset_button, 1018 delete_label = messages.delete_label, 1019 formstyle = current.deployment_settings.get_ui_formstyle(), 1020 separator = settings.label_separator 1021 ) 1022 form.add_class("auth_reset_password") 1023 if captcha: 1024 s3_addrow(form, captcha.label, captcha, 1025 captcha.comment, settings.formstyle, "captcha__row") 1026 if form.accepts(request, session if self.csrf_prevention else None, 1027 formname="reset_password", dbio=False, 1028 onvalidation=onvalidation, 1029 hideerror=settings.hideerror): 1030 user = utable(**{userfield:form.vars.get(userfield)}) 1031 if not user: 1032 session.error = messages["invalid_%s" % userfield] 1033 redirect(self.url(args=request.args), 1034 client_side=settings.client_side) 1035 elif user.registration_key in ("pending", "disabled", "blocked"): 1036 session.warning = messages.registration_pending 1037 redirect(self.url(args=request.args), 1038 client_side=settings.client_side) 1039 if self.email_reset_password(user): 1040 session.confirmation = messages.email_sent 1041 else: 1042 session.error = messages.unable_to_send_email 1043 self.log_event(log, user) 1044 callback(onaccept, form) 1045 if not next: 1046 next = self.url(args=request.args) 1047 else: 1048 next = replace_id(next, form) 1049 redirect(next, client_side=settings.client_side) 1050 # old_requires = utable.email.requires 1051 return form
1052 1053 # -------------------------------------------------------------------------
1054 - def login_user(self, user):
1055 """ 1056 Log the user in 1057 - common function called by login() & register() 1058 """ 1059 1060 db = current.db 1061 deployment_settings = current.deployment_settings 1062 request = current.request 1063 session = current.session 1064 settings = self.settings 1065 req_vars = request.vars 1066 1067 # If the user hasn't set a personal UTC offset, 1068 # then read the UTC offset from the form: 1069 if not user.utc_offset: 1070 user.utc_offset = session.s3.utc_offset 1071 1072 session.auth = Storage( 1073 user=user, 1074 last_visit=request.now, 1075 expiration = req_vars.get("remember", False) and \ 1076 settings.long_expiration or settings.expiration, 1077 remember = req_vars.has_key("remember"), 1078 hmac_key = web2py_uuid() 1079 ) 1080 self.user = user 1081 self.s3_set_roles() 1082 1083 # Set a Cookie to present user with login box by default 1084 self.set_cookie() 1085 1086 # Read their language from the Profile 1087 language = user.language 1088 current.T.force(language) 1089 session.s3.language = language 1090 1091 session.confirmation = self.messages.logged_in 1092 1093 # Update the timestamp of the User so we know when they last logged-in 1094 utable = settings.table_user 1095 db(utable.id == self.user.id).update(timestmp = request.utcnow) 1096 1097 # Set user's position 1098 # @ToDo: Per-User settings 1099 if deployment_settings.get_auth_set_presence_on_login() and \ 1100 req_vars.has_key("auth_user_clientlocation") and \ 1101 req_vars.get("auth_user_clientlocation"): 1102 position = req_vars.get("auth_user_clientlocation").split("|", 3) 1103 userlat = float(position[0]) 1104 userlon = float(position[1]) 1105 accuracy = float(position[2]) / 1000 # Ensures accuracy is in km 1106 closestpoint = 0 1107 closestdistance = 0 1108 gis = current.gis 1109 # @ToDo: Filter to just Sites & Home Addresses? 1110 locations = gis.get_features_in_radius(userlat, userlon, accuracy) 1111 1112 ignore_levels_for_presence = deployment_settings.get_auth_ignore_levels_for_presence() 1113 greatCircleDistance = gis.greatCircleDistance 1114 for location in locations: 1115 if location.level not in ignore_levels_for_presence: 1116 if closestpoint != 0: 1117 currentdistance = greatCircleDistance(closestpoint.lat, 1118 closestpoint.lon, 1119 location.lat, 1120 location.lon) 1121 if currentdistance < closestdistance: 1122 closestpoint = location 1123 closestdistance = currentdistance 1124 else: 1125 closestpoint = location 1126 1127 s3tracker = S3Tracker() 1128 person_id = self.s3_logged_in_person() 1129 if closestpoint == 0 and deployment_settings.get_auth_create_unknown_locations(): 1130 # There wasn't any near-by location, so create one 1131 newpoint = {"lat": userlat, 1132 "lon": userlon, 1133 "name": "Waypoint" 1134 } 1135 closestpoint = current.s3db.gis_location.insert(**newpoint) 1136 s3tracker(db.pr_person, 1137 person_id).set_location(closestpoint, 1138 timestmp=request.utcnow) 1139 elif closestpoint != 0: 1140 s3tracker(db.pr_person, 1141 person_id).set_location(closestpoint, 1142 timestmp=request.utcnow)
1143 1144 # -------------------------------------------------------------------------
1145 - def register(self, 1146 next = DEFAULT, 1147 onvalidation = DEFAULT, 1148 onaccept = DEFAULT, 1149 log = DEFAULT, 1150 js_validation = True, # Set to False if using custom validation 1151 ):
1152 """ 1153 Overrides Web2Py's register() to add new functionality: 1154 - Checks whether registration is permitted 1155 - Custom Flash styles 1156 - Allow form to be embedded in other pages 1157 - Optional addition of Mobile Phone field to the Register form 1158 - Optional addition of Organisation field to the Register form 1159 1160 - Lookup Domains/Organisations to check for Whitelists 1161 &/or custom Approver 1162 1163 @return: a registration form 1164 """ 1165 1166 db = current.db 1167 settings = self.settings 1168 messages = self.messages 1169 request = current.request 1170 session = current.session 1171 deployment_settings = current.deployment_settings 1172 T = current.T 1173 1174 # Customise the resource 1175 customise = deployment_settings.customise_resource("auth_user") 1176 if customise: 1177 customise(request, "auth_user") 1178 1179 utable = self.settings.table_user 1180 utablename = utable._tablename 1181 passfield = settings.password_field 1182 1183 # S3: Don't allow registration if disabled 1184 if not deployment_settings.get_security_self_registration(): 1185 session.error = messages.registration_disabled 1186 redirect(URL(args=["login"])) 1187 1188 if self.is_logged_in() and request.function != "index": 1189 redirect(settings.logged_url) 1190 1191 if next == DEFAULT: 1192 next = request.vars._next or settings.register_next 1193 if onvalidation == DEFAULT: 1194 onvalidation = settings.register_onvalidation 1195 if onaccept == DEFAULT: 1196 onaccept = settings.register_onaccept 1197 if log == DEFAULT: 1198 log = messages.register_log 1199 1200 labels = s3_mark_required(utable)[0] 1201 1202 formstyle = deployment_settings.get_ui_formstyle() 1203 REGISTER = T("Register") 1204 buttons = [INPUT(_type="submit", _value=REGISTER), 1205 A(T("Login"), 1206 _href=URL(f="user", args="login"), 1207 _id="login-btn", 1208 _class="action-lnk", 1209 ), 1210 ] 1211 current.response.form_label_separator = "" 1212 form = SQLFORM(utable, 1213 hidden = dict(_next=request.vars._next), 1214 labels = labels, 1215 separator = "", 1216 showid = settings.showid, 1217 submit_button = REGISTER, 1218 delete_label = messages.delete_label, 1219 formstyle = formstyle, 1220 buttons = buttons, 1221 ) 1222 1223 # Identify form for CSS & JS Validation 1224 form.add_class("auth_register") 1225 1226 if js_validation: 1227 # Client-side Validation 1228 self.s3_register_validation() 1229 1230 # Insert a Password-confirmation field 1231 for i, row in enumerate(form[0].components): 1232 item = row.element("input", _name=passfield) 1233 if item: 1234 field_id = "%s_password_two" % utablename 1235 s3_addrow(form, 1236 LABEL(DIV("%s:" % messages.verify_password, 1237 SPAN("*", _class="req"), 1238 _for="password_two", 1239 _id=field_id + SQLFORM.ID_LABEL_SUFFIX, 1240 ), 1241 ), 1242 INPUT(_name="password_two", 1243 _id=field_id, 1244 _type="password", 1245 requires=IS_EXPR("value==%s" % \ 1246 repr(request.vars.get(passfield, None)), 1247 error_message=messages.mismatched_password) 1248 ), 1249 "", 1250 formstyle, 1251 field_id + SQLFORM.ID_ROW_SUFFIX, 1252 position = i + 1, 1253 ) 1254 1255 # Add an opt in clause to receive emails depending on the deployment settings 1256 if deployment_settings.get_auth_opt_in_to_email(): 1257 field_id = "%s_opt_in" % utablename 1258 comment = DIV(DIV(_class="tooltip", 1259 _title="%s|%s" % (T("Mailing list"), 1260 T("By selecting this you agree that we may contact you.")))) 1261 checked = deployment_settings.get_auth_opt_in_default() and "selected" 1262 s3_addrow(form, 1263 LABEL("%s:" % T("Receive updates"), 1264 _for="opt_in", 1265 _id=field_id + SQLFORM.ID_LABEL_SUFFIX, 1266 ), 1267 INPUT(_name="opt_in", _id=field_id, _type="checkbox", _checked=checked), 1268 comment, 1269 formstyle, 1270 field_id + SQLFORM.ID_ROW_SUFFIX, 1271 ) 1272 1273 # S3: Insert Home phone field into form 1274 if deployment_settings.get_auth_registration_requests_home_phone(): 1275 for i, row in enumerate(form[0].components): 1276 item = row.element("input", _name="email") 1277 if item: 1278 field_id = "%s_home" % utablename 1279 s3_addrow(form, 1280 LABEL("%s:" % T("Home Phone"), 1281 _for="home", 1282 _id=field_id + SQLFORM.ID_LABEL_SUFFIX, 1283 ), 1284 INPUT(_name="home", _id=field_id), 1285 "", 1286 formstyle, 1287 field_id + SQLFORM.ID_ROW_SUFFIX, 1288 position = i + 1, 1289 ) 1290 1291 # S3: Insert Mobile phone field into form 1292 if deployment_settings.get_auth_registration_requests_mobile_phone(): 1293 for i, row in enumerate(form[0].components): 1294 item = row.element("input", _name="email") 1295 if item: 1296 field_id = "%s_mobile" % utablename 1297 if deployment_settings.get_auth_registration_mobile_phone_mandatory(): 1298 mandatory = SPAN("*", _class="req") 1299 comment = "" 1300 else: 1301 mandatory = "" 1302 comment = DIV(_class="tooltip", 1303 _title="%s|%s" % (deployment_settings.get_ui_label_mobile_phone(), 1304 messages.help_mobile_phone)) 1305 s3_addrow(form, 1306 LABEL("%s:" % deployment_settings.get_ui_label_mobile_phone(), 1307 mandatory, 1308 _for="mobile", 1309 _id=field_id + SQLFORM.ID_LABEL_SUFFIX, 1310 ), 1311 INPUT(_name="mobile", _id=field_id), 1312 comment, 1313 formstyle, 1314 field_id + SQLFORM.ID_ROW_SUFFIX, 1315 position = i + 1, 1316 ) 1317 1318 # S3: Insert Photo widget into form 1319 if deployment_settings.get_auth_registration_requests_image(): 1320 label = self.messages.label_image 1321 comment = DIV(_class="stickytip", 1322 _title="%s|%s" % (label, 1323 self.messages.help_image % \ 1324 dict(gravatar = A("Gravatar", 1325 _target="top", 1326 _href="http://gravatar.com")))) 1327 field_id = "%s_image" % utablename 1328 widget = SQLFORM.widgets["upload"].widget(current.s3db.pr_image.image, None) 1329 s3_addrow(form, 1330 LABEL("%s:" % label, 1331 _for="image", 1332 _id=field_id + SQLFORM.ID_LABEL_SUFFIX, 1333 ), 1334 widget, 1335 comment, 1336 formstyle, 1337 field_id + SQLFORM.ID_ROW_SUFFIX, 1338 ) 1339 1340 if deployment_settings.get_auth_terms_of_service(): 1341 field_id = "%s_tos" % utablename 1342 label = T("I agree to the %(terms_of_service)s") % \ 1343 dict(terms_of_service=A(T("Terms of Service"), 1344 _href=URL(c="default", f="tos"), 1345 _target="_blank", 1346 )) 1347 label = XML("%s:" % label) 1348 s3_addrow(form, 1349 LABEL(label, 1350 _for="tos", 1351 _id=field_id + SQLFORM.ID_LABEL_SUFFIX, 1352 ), 1353 INPUT(_name="tos", 1354 _id=field_id, 1355 _type="checkbox", 1356 ), 1357 "", 1358 formstyle, 1359 field_id + SQLFORM.ID_ROW_SUFFIX, 1360 ) 1361 1362 if settings.captcha != None: 1363 form[0].insert(-1, DIV("", settings.captcha, "")) 1364 1365 utable.registration_key.default = key = str(uuid4()) 1366 1367 if form.accepts(request.vars, session, formname="register", 1368 onvalidation=onvalidation): 1369 1370 # Save temporary user fields 1371 self.s3_user_register_onaccept(form) 1372 1373 users = db(utable.id > 0).select(utable.id, 1374 limitby=(0, 2)) 1375 if len(users) == 1: 1376 # 1st user to register doesn't need verification/approval 1377 self.s3_approve_user(form.vars) 1378 current.session.confirmation = self.messages.registration_successful 1379 1380 # 1st user gets Admin rights 1381 admin_group_id = 1 1382 self.add_membership(admin_group_id, users.first().id) 1383 1384 # Log them in 1385 if "language" not in form.vars: 1386 # Was missing from login form 1387 form.vars.language = T.accepted_language 1388 user = Storage(utable._filter_fields(form.vars, id=True)) 1389 self.login_user(user) 1390 1391 self.s3_send_welcome_email(form.vars) 1392 1393 elif settings.registration_requires_verification: 1394 # Send the Verification email 1395 if not settings.mailer or \ 1396 not settings.mailer.settings.server or \ 1397 not settings.mailer.send(to=form.vars.email, 1398 subject=messages.verify_email_subject % \ 1399 dict(system_name=deployment_settings.get_system_name()), 1400 message=messages.verify_email % \ 1401 dict(url="%s/default/user/verify_email/%s" % \ 1402 (current.response.s3.base_url, key))): 1403 current.response.error = messages.email_verification_failed 1404 return form 1405 # @ToDo: Deployment Setting? 1406 #session.confirmation = messages.email_sent 1407 next = URL(c="default", f="message", 1408 args = ["verify_email_sent"], 1409 vars = {"email": form.vars.email}) 1410 1411 else: 1412 # Does the user need to be approved? 1413 approved = self.s3_verify_user(form.vars) 1414 1415 if approved: 1416 # Log them in 1417 if "language" not in form.vars: 1418 # Was missing from login form 1419 form.vars.language = T.accepted_language 1420 user = Storage(utable._filter_fields(form.vars, id=True)) 1421 self.login_user(user) 1422 1423 # Set a Cookie to present user with login box by default 1424 self.set_cookie() 1425 1426 if log: 1427 self.log_event(log, form.vars) 1428 if onaccept: 1429 onaccept(form) 1430 if not next: 1431 next = self.url(args = request.args) 1432 elif isinstance(next, (list, tuple)): 1433 # fix issue with 2.6 1434 next = next[0] 1435 elif next and not next[0] == "/" and next[:4] != "http": 1436 next = self.url(next.replace("[id]", str(form.vars.id))) 1437 redirect(next) 1438 1439 return form
1440 1441 # -------------------------------------------------------------------------
1442 - def email_reset_password(self, user):
1443 """ 1444 Overrides Web2Py's email_reset_password() to modify the message 1445 structure 1446 1447 @param user: the auth_user record (Row) 1448 """ 1449 1450 mailer = self.settings.mailer 1451 if not mailer or not mailer.settings.server: 1452 return False 1453 1454 reset_password_key = str(int(time.time())) + '-' + web2py_uuid() 1455 reset_password_url = "%s/default/user/reset_password?key=%s" % \ 1456 (current.response.s3.base_url, reset_password_key) 1457 1458 message = self.messages.reset_password % dict(url=reset_password_url) 1459 if mailer.send(to=user.email, 1460 subject=self.messages.reset_password_subject, 1461 message=message): 1462 user.update_record(reset_password_key=reset_password_key) 1463 return True 1464 1465 return False
1466 1467 # -------------------------------------------------------------------------
1468 - def add_membership(self, group_id=None, user_id=None, role=None, 1469 entity=None):
1470 """ 1471 gives user_id membership of group_id or role 1472 if user is None than user_id is that of current logged in user 1473 S3: extended to support Entities 1474 """ 1475 1476 group_id = group_id or self.id_group(role) 1477 try: 1478 group_id = int(group_id) 1479 except: 1480 group_id = self.id_group(group_id) # interpret group_id as a role 1481 if not user_id and self.user: 1482 user_id = self.user.id 1483 membership = self.settings.table_membership 1484 record = membership(user_id=user_id, group_id=group_id, pe_id=entity) 1485 if record: 1486 return record.id 1487 else: 1488 membership_id = membership.insert(group_id=group_id, 1489 user_id=user_id, 1490 pe_id=entity) 1491 self.update_groups() 1492 self.log_event(self.messages.add_membership_log, 1493 dict(user_id=user_id, group_id=group_id)) 1494 return membership_id
1495 1496 # -------------------------------------------------------------------------
1497 - def verify_email(self, 1498 next=DEFAULT, 1499 log=DEFAULT):
1500 """ 1501 action user to verify the registration email, XXXXXXXXXXXXXXXX 1502 1503 .. method:: Auth.verify_email([next=DEFAULT [, onvalidation=DEFAULT 1504 [, log=DEFAULT]]]) 1505 """ 1506 1507 settings = self.settings 1508 request = current.request 1509 1510 # Customise the resource 1511 customise = current.deployment_settings.customise_resource("auth_user") 1512 if customise: 1513 customise(request, "auth_user") 1514 1515 key = request.args[-1] 1516 utable = settings.table_user 1517 query = (utable.registration_key == key) 1518 user = current.db(query).select(limitby=(0, 1)).first() 1519 if not user: 1520 redirect(settings.verify_email_next) 1521 1522 if log == DEFAULT: 1523 log = self.messages.verify_email_log 1524 if next == DEFAULT: 1525 next = settings.verify_email_next 1526 1527 approved = self.s3_verify_user(user) 1528 1529 if approved: 1530 # Log them in 1531 user = Storage(utable._filter_fields(user, id=True)) 1532 self.login_user(user) 1533 1534 if log: 1535 self.log_event(log, user) 1536 1537 redirect(next)
1538 1539 # -------------------------------------------------------------------------
1540 - def profile(self, 1541 next=DEFAULT, 1542 onvalidation=DEFAULT, 1543 onaccept=DEFAULT, 1544 log=DEFAULT, 1545 ):
1546 """ 1547 returns a form that lets the user change his/her profile 1548 1549 .. method:: Auth.profile([next=DEFAULT [, onvalidation=DEFAULT 1550 [, onaccept=DEFAULT [, log=DEFAULT]]]]) 1551 1552 Patched for S3 to use s3_mark_required and handle opt_in mailing lists 1553 """ 1554 1555 if not self.is_logged_in(): 1556 redirect(self.settings.login_url) 1557 1558 messages = self.messages 1559 settings = self.settings 1560 utable = settings.table_user 1561 1562 passfield = settings.password_field 1563 utable[passfield].writable = False 1564 1565 request = current.request 1566 session = current.session 1567 deployment_settings = current.deployment_settings 1568 1569 if deployment_settings.get_auth_show_utc_offset(): 1570 utable.utc_offset.readable = True 1571 utable.utc_offset.writable = True 1572 1573 # Users should not be able to change their Org affiliation 1574 # - also hide popup-link to create a new Org (makes 1575 # no sense here if the field is read-only anyway) 1576 utable.organisation_id.writable = False 1577 utable.organisation_id.comment = None 1578 1579 ## Only allowed to select Orgs that the user has update access to 1580 #utable.organisation_id.requires = \ 1581 # current.s3db.org_organisation_requires(updateable = True) 1582 1583 if next == DEFAULT: 1584 next = request.get_vars._next \ 1585 or request.post_vars._next \ 1586 or settings.profile_next 1587 if onvalidation == DEFAULT: 1588 onvalidation = settings.profile_onvalidation 1589 if onaccept == DEFAULT: 1590 onaccept = settings.profile_onaccept 1591 if log == DEFAULT: 1592 log = messages.profile_log 1593 labels = s3_mark_required(utable)[0] 1594 1595 # If we have an opt_in and some post_vars then update the opt_in value 1596 opt_in_to_email = deployment_settings.get_auth_opt_in_to_email() 1597 if opt_in_to_email: 1598 team_list = deployment_settings.get_auth_opt_in_team_list() 1599 if request.post_vars: 1600 removed = [] 1601 selected = [] 1602 for opt_in in team_list: 1603 if opt_in in request.post_vars: 1604 selected.append(opt_in) 1605 else: 1606 removed.append(opt_in) 1607 db = current.db 1608 s3db = current.s3db 1609 ptable = s3db.pr_person 1610 putable = s3db.pr_person_user 1611 query = (putable.user_id == request.post_vars.id) & \ 1612 (putable.pe_id == ptable.pe_id) 1613 person_id = db(query).select(ptable.id, limitby=(0, 1)).first().id 1614 db(ptable.id == person_id).update(opt_in = selected) 1615 1616 g_table = s3db["pr_group"] 1617 gm_table = s3db["pr_group_membership"] 1618 # Remove them from any team they are a member of in the removed list 1619 for team in removed: 1620 query = (g_table.name == team) & \ 1621 (gm_table.group_id == g_table.id) & \ 1622 (gm_table.person_id == person_id) 1623 gm_rec = db(query).select(g_table.id, limitby=(0, 1)).first() 1624 if gm_rec: 1625 db(gm_table.id == gm_rec.id).delete() 1626 # Add them to the team (if they are not already a team member) 1627 for team in selected: 1628 query = (g_table.name == team) & \ 1629 (gm_table.group_id == g_table.id) & \ 1630 (gm_table.person_id == person_id) 1631 gm_rec = db(query).select(g_table.id, limitby=(0, 1)).first() 1632 if not gm_rec: 1633 query = (g_table.name == team) 1634 team_rec = db(query).select(g_table.id, 1635 limitby=(0, 1)).first() 1636 # if the team doesn't exist then add it 1637 if team_rec == None: 1638 team_id = g_table.insert(name=team, group_type=5) 1639 else: 1640 team_id = team_rec.id 1641 gm_table.insert(group_id = team_id, 1642 person_id = person_id) 1643 1644 formstyle = deployment_settings.get_ui_formstyle() 1645 current.response.form_label_separator = "" 1646 form = SQLFORM(utable, 1647 self.user.id, 1648 fields = settings.profile_fields, 1649 labels = labels, 1650 hidden = dict(_next=next), 1651 showid = settings.showid, 1652 submit_button = messages.profile_save_button, 1653 delete_label = messages.delete_label, 1654 upload = settings.download_url, 1655 formstyle = formstyle, 1656 separator = "" 1657 ) 1658 1659 form.add_class("auth_profile") 1660 1661 if deployment_settings.get_auth_openid(): 1662 from gluon.contrib.login_methods.openid_auth import OpenIDAuth 1663 openid_login_form = OpenIDAuth(self) 1664 form = DIV(form, openid_login_form.list_user_openids()) 1665 if form.accepts(request, session, 1666 formname="profile", 1667 onvalidation=onvalidation, 1668 hideerror=settings.hideerror): 1669 self.auth_user_onaccept(form.vars.email, self.user.id) 1670 self.user.update(utable._filter_fields(form.vars)) 1671 session.flash = messages.profile_updated 1672 if log: 1673 self.log_event(log, self.user) 1674 callback(onaccept, form) 1675 if not next: 1676 next = self.url(args=request.args) 1677 elif isinstance(next, (list, tuple)): ### fix issue with 2.6 1678 next = next[0] 1679 elif next and not next[0] == "/" and next[:4] != "http": 1680 next = self.url(next.replace("[id]", str(form.vars.id))) 1681 redirect(next) 1682 1683 if opt_in_to_email: 1684 T = current.T 1685 ptable = s3db.pr_person 1686 ltable = s3db.pr_person_user 1687 team_list = deployment_settings.get_auth_opt_in_team_list() 1688 query = (ltable.user_id == form.record.id) & \ 1689 (ltable.pe_id == ptable.pe_id) 1690 db_opt_in_list = db(query).select(ptable.opt_in, 1691 limitby=(0, 1)).first().opt_in 1692 for opt_in in team_list: 1693 field_id = "%s_opt_in_%s" % (utable, team_list) 1694 if opt_in in db_opt_in_list: 1695 checked = "selected" 1696 else: 1697 checked = None 1698 s3_addrow(form, 1699 LABEL(T("Receive %(opt_in)s updates:") % \ 1700 dict(opt_in=opt_in), 1701 _for="opt_in", 1702 _id=field_id + SQLFORM.ID_LABEL_SUFFIX), 1703 INPUT(_name=opt_in, _id=field_id, 1704 _type="checkbox", _checked=checked), 1705 "", 1706 formstyle, 1707 field_id + SQLFORM.ID_ROW_SUFFIX, 1708 ) 1709 return form
1710 1711 # -------------------------------------------------------------------------
1712 - def configure_user_fields(self, pe_ids=None):
1713 """ 1714 Configure User Fields - for registration & user administration 1715 1716 pe_ids: an optional list of pe_ids for the Org Filter 1717 i.e. org_admin coming from admin.py/user() 1718 """ 1719 1720 from s3validators import IS_ONE_OF 1721 1722 T = current.T 1723 db = current.db 1724 s3db = current.s3db 1725 request = current.request 1726 messages = self.messages 1727 cmessages = current.messages 1728 settings = self.settings 1729 deployment_settings = current.deployment_settings 1730 1731 if deployment_settings.get_ui_multiselect_widget(): 1732 from s3widgets import S3MultiSelectWidget 1733 multiselect_widget = True 1734 else: 1735 multiselect_widget = False 1736 1737 utable = self.settings.table_user 1738 1739 utable.password.label = T("Password") #messages.label_password 1740 1741 first_name = utable.first_name 1742 first_name.label = T("First Name") #messages.label_first_name 1743 first_name.requires = IS_NOT_EMPTY(error_message=messages.is_empty), 1744 1745 last_name = utable.last_name 1746 last_name.label = T("Last Name") #messages.label_last_name 1747 if deployment_settings.get_L10n_mandatory_lastname(): 1748 last_name.notnull = True 1749 last_name.requires = IS_NOT_EMPTY(error_message=messages.is_empty) 1750 1751 userfield = settings.login_userfield 1752 if userfield != "email": 1753 utable[userfield].requires = \ 1754 IS_NOT_IN_DB(db, "%s.%s" % (utable._tablename, 1755 userfield)) 1756 1757 email = utable.email 1758 email.label = T("Email") #messages.label_email 1759 email.requires = [IS_EMAIL(error_message=messages.invalid_email), 1760 IS_LOWER(), 1761 IS_NOT_IN_DB(db, 1762 "%s.email" % utable._tablename, 1763 error_message=messages.duplicate_email) 1764 ] 1765 1766 language = utable.language 1767 languages = deployment_settings.get_L10n_languages() 1768 if len(languages) > 1: 1769 language.label = T("Language") 1770 language.comment = DIV(_class="tooltip", 1771 _title="%s|%s" % (T("Language"), 1772 T("The language you wish the site to be displayed in."))) 1773 requires = IS_ISO639_2_LANGUAGE_CODE(sort = True, 1774 translate = True, 1775 zero = None, 1776 ) 1777 language.represent = requires.represent 1778 language.requires = requires 1779 # Default the profile language to the one currently active 1780 language.default = T.accepted_language 1781 if multiselect_widget: 1782 language.widget = S3MultiSelectWidget(multiple=False) 1783 else: 1784 language.default = languages.keys()[0] 1785 language.readable = language.writable = False 1786 1787 utc_offset = utable.utc_offset 1788 utc_offset.label = messages.label_utc_offset 1789 utc_offset.comment = DIV(_class="tooltip", 1790 _title="%s|%s" % (messages.label_utc_offset, 1791 messages.help_utc_offset) 1792 ) 1793 try: 1794 from s3validators import IS_UTC_OFFSET 1795 utc_offset.requires = IS_EMPTY_OR(IS_UTC_OFFSET()) 1796 except: 1797 pass 1798 1799 utable.registration_key.label = messages.label_registration_key 1800 #utable.reset_password_key.label = messages.label_registration_key 1801 1802 # Organisation 1803 is_admin = self.s3_has_role("ADMIN") 1804 if is_admin: 1805 show_org = deployment_settings.get_auth_admin_sees_organisation() 1806 else: 1807 show_org = deployment_settings.get_auth_registration_requests_organisation() 1808 1809 if show_org: 1810 if pe_ids and not is_admin: 1811 # Filter orgs to just those belonging to the Org Admin's Org 1812 # & Descendants (or realms for which they are Org Admin): 1813 filterby = "pe_id" 1814 filter_opts = pe_ids 1815 # If the current user can only register users for certain orgs, 1816 # then they must not leave this field empty: 1817 org_required = True 1818 else: 1819 filterby = None 1820 filter_opts = None 1821 org_required = deployment_settings.get_auth_registration_organisation_required() 1822 1823 organisation_id = utable.organisation_id 1824 organisation_id.label = messages.label_organisation_id 1825 organisation_id.readable = organisation_id.writable = True 1826 organisation_id.default = deployment_settings.get_auth_registration_organisation_default() 1827 org_represent = s3db.org_organisation_represent 1828 organisation_id.represent = org_represent 1829 1830 requires = IS_ONE_OF(db, "org_organisation.id", 1831 org_represent, 1832 filterby = filterby, 1833 filter_opts = filter_opts, 1834 orderby = "org_organisation.name", 1835 sort = True, 1836 ) 1837 1838 if org_required: 1839 organisation_id.requires = requires 1840 else: 1841 organisation_id.requires = IS_EMPTY_OR(requires) 1842 1843 if deployment_settings.get_auth_registration_organisation_link_create(): 1844 from s3layouts import S3PopupLink 1845 org_crud_strings = s3db.crud_strings["org_organisation"] 1846 organisation_id.comment = S3PopupLink(c = "org", 1847 f = "organisation", 1848 label = org_crud_strings.label_create, 1849 title = org_crud_strings.title_list, 1850 ) 1851 #from s3widgets import S3OrganisationAutocompleteWidget 1852 #organisation_id.widget = S3OrganisationAutocompleteWidget() 1853 #organisation_id.comment = DIV(_class="tooltip", 1854 # _title="%s|%s" % (T("Organization"), 1855 # cmessages.AUTOCOMPLETE_HELP)) 1856 if multiselect_widget: 1857 organisation_id.widget = S3MultiSelectWidget(multiple=False) 1858 1859 # Organisation Group 1860 if deployment_settings.get_auth_registration_requests_organisation_group(): 1861 org_group_id = utable.org_group_id 1862 org_group_id.label = messages.label_org_group_id 1863 org_group_id.readable = org_group_id.writable = True 1864 org_group_represent = s3db.org_group_represent 1865 org_group_id.represent = org_group_represent 1866 requires = IS_ONE_OF(db, "org_group.id", 1867 org_group_represent, 1868 # @ToDo: Filter org groups to just those belonging to the Org Admin's Org 1869 # @ToDo: Dynamically filter groups to just those that the selected Org is a member of 1870 #filterby=filterby, 1871 #filter_opts=filter_opts, 1872 orderby="org_group.name", 1873 sort=True) 1874 if deployment_settings.get_auth_registration_organisation_group_required(): 1875 org_group_id.requires = requires 1876 else: 1877 org_group_id.requires = IS_EMPTY_OR(requires) 1878 #from s3layouts import S3PopupLink 1879 #ogroup_crud_strings = s3db.crud_strings["org_group"] 1880 #org_group_id.comment = S3PopupLink(c = "org", 1881 # f = "group", 1882 # label = ogroup_crud_strings.label_create, 1883 # title = ogroup_crud_strings.title_list, 1884 # ) 1885 if multiselect_widget: 1886 org_group_id.widget = S3MultiSelectWidget(multiple=False) 1887 1888 # Site 1889 if deployment_settings.get_auth_registration_requests_site(): 1890 site_id = request.get_vars.get("site_id", None) 1891 field = utable.site_id 1892 field.label = deployment_settings.get_org_site_label() 1893 site_represent = s3db.org_site_represent 1894 field.represent = site_represent 1895 if site_id: 1896 field.default = site_id 1897 field.readable = True 1898 else: 1899 field.readable = field.writable = True 1900 #field.default = deployment_settings.get_auth_registration_site_id_default() 1901 site_required = deployment_settings.get_auth_registration_site_required() 1902 if show_org: 1903 from s3validators import IS_ONE_OF_EMPTY 1904 requires = IS_ONE_OF_EMPTY(db, "org_site.site_id", 1905 site_represent, 1906 orderby="org_site.name", 1907 sort=True) 1908 if site_required: 1909 site_optional = "" 1910 else: 1911 site_optional = ''', 1912 'optional': true''' 1913 current.response.s3.jquery_ready.append(''' 1914 $.filterOptionsS3({ 1915 'trigger':'organisation_id', 1916 'target':'site_id', 1917 'lookupField':'site_id', 1918 'lookupResource':'site', 1919 'lookupURL':S3.Ap.concat('/org/sites_for_org.json/')%s 1920 })''' % site_optional) 1921 else: 1922 requires = IS_ONE_OF(db, "org_site.site_id", 1923 site_represent, 1924 orderby="org_site.name", 1925 sort=True) 1926 #from s3widgets import S3SiteAutocompleteWidget 1927 #field.widget = S3SiteAutocompleteWidget() 1928 field.comment = DIV(_class="tooltip", 1929 _title="%s|%s" % (T("Facility"), 1930 T("Select the default site."))) 1931 if site_required: 1932 field.requires = requires 1933 else: 1934 field.requires = IS_EMPTY_OR(requires) 1935 1936 # Link User to Organisation (as staff, volunteer, or member) 1937 if any(m in request.args for m in ("profile", "user_profile")): 1938 # Irrelevant in personal profile 1939 link_user_to_opts = False 1940 else: 1941 link_user_to_opts = deployment_settings.get_auth_registration_link_user_to() 1942 1943 if link_user_to_opts: 1944 link_user_to = utable.link_user_to 1945 link_user_to_default = deployment_settings.get_auth_registration_link_user_to_default() 1946 req_vars = request.vars 1947 for hrtype in ["staff", "volunteer", "member"]: 1948 if "link_user_to_%s" % hrtype in req_vars: 1949 link_user_to_default.append(hrtype) 1950 if link_user_to_default: 1951 link_user_to.default = link_user_to_default 1952 else: 1953 link_user_to.readable = link_user_to.writable = True 1954 link_user_to.label = T("Register As") 1955 link_user_to.requires = IS_IN_SET(link_user_to_opts, 1956 multiple = True 1957 ) 1958 link_user_to.represent = lambda ids: \ 1959 ids and ", ".join([str(link_user_to_opts[id]) for id in ids]) or cmessages["NONE"] 1960 #if multiselect_widget: 1961 # link_user_to.widget = S3MultiSelectWidget() 1962 #else: 1963 link_user_to.widget = SQLFORM.widgets.checkboxes.widget 1964 link_user_to.comment = DIV(_class="tooltip", 1965 _title="%s|%s" % (link_user_to.label, 1966 T("Will create and link your user account to the following records")))
1967 1968 # -------------------------------------------------------------------------
1969 - def s3_import_prep(self, data):
1970 """ 1971 Called when users are imported from CSV 1972 1973 Lookups Pseudo-reference Integer fields from Names 1974 e.g.: 1975 auth_membership.pe_id from organisation.name=<Org Name> 1976 """ 1977 1978 db = current.db 1979 s3db = current.s3db 1980 set_record_owner = self.s3_set_record_owner 1981 update_super = s3db.update_super 1982 otable = s3db.org_organisation 1983 btable = s3db.org_organisation_branch 1984 1985 tree = data[1] 1986 1987 ORG_ADMIN = not self.s3_has_role("ADMIN") 1988 TRANSLATE = current.deployment_settings.get_L10n_translate_org_organisation() 1989 if TRANSLATE: 1990 ltable = s3db.org_organisation_name 1991 def add_org(name, parent=None): 1992 """ Helper to add a New Organisation """ 1993 organisation_id = otable.insert(name=name) 1994 record = Storage(id=organisation_id) 1995 update_super(otable, record) 1996 set_record_owner(otable, organisation_id) 1997 # @ToDo: Call onaccept? 1998 if parent: 1999 records = db(otable.name == parent).select(otable.id) 2000 if len(records) == 1: 2001 # Add branch link 2002 link_id = btable.insert(organisation_id = records.first().id, 2003 branch_id = organisation_id) 2004 onaccept = s3db.get_config("org_organisation_branch", "onaccept") 2005 callback(onaccept, Storage(vars=Storage(id=link_id))) 2006 elif len(records) > 1: 2007 # Ambiguous 2008 current.log.debug("Cannot set branch link for new Organisation %s as there are multiple matches for parent %s" % (name, parent)) 2009 else: 2010 # Create Parent 2011 parent_id = otable.insert(name=parent) 2012 update_super(otable, Storage(id=parent_id)) 2013 set_record_owner(otable, parent_id) 2014 # @ToDo: Call onaccept? 2015 # Create link 2016 link_id = btable.insert(organisation_id = parent_id, 2017 branch_id = organisation_id) 2018 onaccept = s3db.get_config("org_organisation_branch", "onaccept") 2019 callback(onaccept, Storage(vars=Storage(id=link_id))) 2020 return (organisation_id, record.pe_id)
2021 2022 def org_lookup(org_full): 2023 """ Helper to lookup an Organisation """ 2024 if "+BRANCH+" in org_full: 2025 parent, org = org_full.split("+BRANCH+") 2026 else: 2027 parent = None 2028 org = org_full 2029 2030 query = (otable.name.lower() == org.lower()) & \ 2031 (otable.deleted != True) 2032 if parent: 2033 btable = s3db.org_organisation_branch 2034 ptable = db.org_organisation.with_alias("org_parent_organisation") 2035 query &= (ptable.name == parent) & \ 2036 (btable.organisation_id == ptable.id) & \ 2037 (btable.branch_id == otable.id) 2038 2039 records = db(query).select(otable.id, 2040 otable.pe_id, 2041 limitby = (0, 2)) 2042 if len(records) == 1: 2043 record = records.first() 2044 organisation_id = record.id 2045 pe_id = record.pe_id 2046 elif len(records) > 1: 2047 # Ambiguous 2048 current.log.debug("Cannot set Organisation %s for user as there are multiple matches" % org) 2049 organisation_id = "" 2050 pe_id = "" 2051 elif TRANSLATE: 2052 # Search by local name 2053 query = (ltable.name_l10n.lower() == org.lower()) & \ 2054 (ltable.organisation_id == otable.id) & \ 2055 (ltable.deleted != True) 2056 records = db(query).select(otable.id, 2057 otable.pe_id, 2058 limitby = (0, 2)) 2059 if len(records) == 1: 2060 record = records.first() 2061 organisation_id = record.id 2062 pe_id = record.pe_id 2063 elif len(records) > 1: 2064 # Ambiguous 2065 current.log.debug("Cannot set Organisation %s for user as there are multiple matches" % org) 2066 organisation_id = "" 2067 pe_id = "" 2068 elif ORG_ADMIN: 2069 # NB ORG_ADMIN has the list of permitted pe_ids already in filter_opts 2070 current.log.debug("Cannot create new Organisation %s as ORG_ADMIN cannot create new Orgs during User Imports" % org) 2071 organisation_id = "" 2072 pe_id = "" 2073 else: 2074 # Add a new record 2075 (organisation_id, pe_id) = add_org(org, parent) 2076 2077 elif ORG_ADMIN: 2078 # NB ORG_ADMIN has the list of permitted pe_ids already in filter_opts 2079 current.log.debug("Cannot create new Organisation %s as ORG_ADMIN cannot create new Orgs during User Imports" % org) 2080 organisation_id = "" 2081 pe_id = "" 2082 else: 2083 # Add a new record 2084 (organisation_id, pe_id) = add_org(org, parent) 2085 2086 return (organisation_id, pe_id)
2087 2088 # Memberships 2089 elements = tree.getroot().xpath("/s3xml//resource[@name='auth_membership']/data[@field='pe_id']") 2090 looked_up = dict(org_organisation = {}) 2091 for element in elements: 2092 pe_string = element.text 2093 2094 if pe_string and "=" in pe_string: 2095 pe_type, pe_value = pe_string.split("=") 2096 pe_tablename, pe_field = pe_type.split(".") 2097 if pe_tablename in looked_up and \ 2098 pe_value in looked_up[pe_tablename]: 2099 # Replace string with pe_id 2100 element.text = looked_up[pe_tablename][pe_value]["pe_id"] 2101 # Don't check again 2102 continue 2103 2104 if pe_tablename == "org_organisation" and pe_field == "name": 2105 # This is a non-integer, so must be 1st or only phase 2106 (record_id, pe_id) = org_lookup(pe_value) 2107 else: 2108 table = s3db[pe_tablename] 2109 if pe_tablename not in looked_up: 2110 looked_up[pe_tablename] = {} 2111 record = db(table[pe_field] == pe_value).select(table.id, # Stored for Org/Groups later 2112 table.pe_id, 2113 limitby=(0, 1) 2114 ).first() 2115 if record: 2116 record_id = record.id 2117 else: 2118 # Add a new record 2119 record_id = table.insert(**{pe_field: pe_value}) 2120 record = Storage(id=record_id) 2121 update_super(table, record) 2122 set_record_owner(table, record_id) 2123 pe_id = record.pe_id 2124 2125 new_value = str(pe_id) 2126 # Replace string with pe_id 2127 element.text = new_value 2128 # Store in case we get called again with same value 2129 looked_up[pe_tablename][pe_value] = dict(pe_id=new_value, 2130 id=str(record_id), 2131 ) 2132 2133 # No longer required since we can use references in the import CSV 2134 # Organisations 2135 #elements = tree.getroot().xpath("/s3xml//resource[@name='auth_user']/data[@field='organisation_id']") 2136 #if elements: 2137 # orgs = looked_up["org_organisation"] 2138 # for element in elements: 2139 # org_full = element.text 2140 # if org_full in orgs: 2141 # # Replace string with id 2142 # element.text = orgs[org_full]["id"] 2143 # # Don't check again 2144 # continue 2145 # try: 2146 # # Is this the 2nd phase of a 2-phase import & hence values have already been replaced? 2147 # int(org_full) 2148 # except ValueError: 2149 # # This is a non-integer, so must be 1st or only phase 2150 # (organisation_id, pe_id) = org_lookup(org_full) 2151 2152 # # Replace string with id 2153 # organisation_id = str(organisation_id) 2154 # element.text = organisation_id 2155 # # Store in case we get called again with same value 2156 # orgs[org_full] = dict(id=organisation_id) 2157 # else: 2158 # # Store in case we get called again with same value 2159 # orgs[org_full] = dict(id=org_full) 2160 2161 # Organisation Groups 2162 #elements = tree.getroot().xpath("/s3xml//resource[@name='auth_user']/data[@field='org_group_id']") 2163 #if elements: 2164 # gtable = s3db.org_group 2165 # org_groups = looked_up.get("org_organisation_group", {}) 2166 # for element in elements: 2167 # name = element.text 2168 # if name in org_groups: 2169 # # Replace string with id 2170 # element.text = org_groups[name]["id"] 2171 # # Don't check again 2172 # continue 2173 2174 # try: 2175 # # Is this the 2nd phase of a 2-phase import & hence values have already been replaced? 2176 # int(name) 2177 # except ValueError: 2178 # # This is a non-integer, so must be 1st or only phase 2179 # record = db(gtable.name == name).select(gtable.id, 2180 # limitby=(0, 1) 2181 # ).first() 2182 # if record: 2183 # org_group_id = record.id 2184 # else: 2185 # # Add a new record 2186 # org_group_id = gtable.insert(name=name) 2187 # update_super(gtable, Storage(id=org_group_id)) 2188 # # Replace string with id 2189 # org_group_id = str(org_group_id) 2190 # element.text = org_group_id 2191 # # Store in case we get called again with same value 2192 # org_groups[name] = dict(id=org_group_id) 2193 # else: 2194 # # Store in case we get called again with same value 2195 # org_groups[name] = dict(id=name) 2196 2197 # ------------------------------------------------------------------------- 2198 @staticmethod
2199 - def s3_register_validation():
2200 """ 2201 JavaScript client-side validation for Registration / User profile 2202 - needed to check for passwords being same, etc 2203 """ 2204 2205 T = current.T 2206 request = current.request 2207 appname = request.application 2208 settings = current.deployment_settings 2209 s3 = current.response.s3 2210 2211 # Static Scripts 2212 scripts_append = s3.scripts.append 2213 if s3.debug: 2214 scripts_append("/%s/static/scripts/jquery.validate.js" % appname) 2215 scripts_append("/%s/static/scripts/jquery.pstrength.2.1.0.js" % appname) 2216 scripts_append("/%s/static/scripts/S3/s3.register_validation.js" % appname) 2217 else: 2218 scripts_append("/%s/static/scripts/jquery.validate.min.js" % appname) 2219 scripts_append("/%s/static/scripts/jquery.pstrength.2.1.0.min.js" % appname) 2220 scripts_append("/%s/static/scripts/S3/s3.register_validation.min.js" % appname) 2221 2222 # Configuration 2223 js_global = [] 2224 js_append = js_global.append 2225 2226 if settings.get_auth_registration_mobile_phone_mandatory(): 2227 js_append('''S3.auth_registration_mobile_phone_mandatory=1''') 2228 2229 if settings.get_auth_registration_organisation_required(): 2230 js_append('''S3.auth_registration_organisation_required=1''') 2231 js_append('''i18n.enter_your_organisation="%s"''' % T("Enter your organization")) 2232 2233 if settings.get_auth_terms_of_service(): 2234 js_append('''S3.auth_terms_of_service=1''') 2235 js_append('''i18n.tos_required="%s"''' % T("You must agree to the Terms of Service")) 2236 2237 if request.controller != "admin": 2238 if settings.get_auth_registration_organisation_hidden(): 2239 js_append('''S3.auth_registration_hide_organisation=1''') 2240 2241 # Check for Whitelists 2242 table = current.s3db.auth_organisation 2243 query = (table.organisation_id != None) & \ 2244 (table.domain != None) 2245 whitelists = current.db(query).select(table.organisation_id, 2246 table.domain) 2247 if whitelists: 2248 domains = [] 2249 domains_append = domains.append 2250 for whitelist in whitelists: 2251 domains_append("'%s':%s" % (whitelist.domain, 2252 whitelist.organisation_id)) 2253 domains = ''','''.join(domains) 2254 domains = '''S3.whitelists={%s}''' % domains 2255 js_append(domains) 2256 2257 js_append('''i18n.enter_first_name="%s"''' % T("Enter your first name")) 2258 js_append('''i18n.provide_password="%s"''' % T("Provide a password")) 2259 js_append('''i18n.repeat_your_password="%s"''' % T("Repeat your password")) 2260 js_append('''i18n.enter_same_password="%s"''' % T("Enter the same password as above")) 2261 js_append('''i18n.please_enter_valid_email="%s"''' % T("Please enter a valid email address")) 2262 2263 js_append('''S3.password_min_length=%i''' % settings.get_auth_password_min_length()) 2264 js_append('''i18n.password_min_chars="%s"''' % T("You must enter a minimum of %d characters")) 2265 js_append('''i18n.weak="%s"''' % T("Weak")) 2266 js_append('''i18n.normal="%s"''' % T("Normal")) 2267 js_append('''i18n.medium="%s"''' % T("Medium")) 2268 js_append('''i18n.strong="%s"''' % T("Strong")) 2269 js_append('''i18n.very_strong="%s"''' % T("Very Strong")) 2270 2271 script = '''\n'''.join(js_global) 2272 s3.js_global.append(script) 2273 2274 # Call script after Global config done 2275 s3.jquery_ready.append('''s3_register_validation()''')
2276 2277 # -------------------------------------------------------------------------
2278 - def auth_user_onaccept(self, email, user_id):
2279 db = current.db 2280 if self.settings.login_userfield != "username": 2281 deployment_settings = current.deployment_settings 2282 chat_username = email.replace("@", "_") 2283 db(db.auth_user.id == user_id).update(username = chat_username) 2284 chat_server = deployment_settings.get_chat_server() 2285 if chat_server: 2286 chatdb = DAL(deployment_settings.get_chatdb_string(), migrate=False) 2287 # Using RawSQL as table not created in web2py 2288 sql_query="insert into ofGroupUser values (\'%s\',\'%s\' ,0);" % (chat_server["groupname"], chat_username) 2289 chatdb.executesql(sql_query)
2290 2291 # -------------------------------------------------------------------------
2292 - def s3_register_onaccept(self, form):
2293 """ 2294 S3 framework function 2295 2296 Designed to be called when a user is created through: 2297 - registration via OAuth, LDAP, etc 2298 2299 Does the following: 2300 - Sets session.auth.user for authorstamp, etc 2301 - Approves user (to set registration groups, such as AUTHENTICATED, link to Person) 2302 """ 2303 2304 user = form.vars 2305 current.session.auth = Storage(user=user) 2306 self.s3_approve_user(user)
2307 2308 # -------------------------------------------------------------------------
2309 - def s3_user_register_onaccept(self, form):
2310 """ 2311 S3 framework function 2312 2313 Designed to be called when a user is created through: 2314 - registration 2315 2316 Does the following: 2317 - Stores the user's email & profile image in auth_user_temp 2318 to be added to their person record when created on approval 2319 2320 @ToDo: If these fields are implemented with the InlineForms functionality, 2321 this function may become redundant 2322 """ 2323 2324 db = current.db 2325 s3db = current.s3db 2326 session = current.session 2327 2328 utable = self.settings.table_user 2329 temptable = s3db.auth_user_temp 2330 2331 form_vars = form.vars 2332 user_id = form_vars.id 2333 2334 if not user_id: 2335 return None 2336 2337 # If the user hasn't set a personal UTC offset, 2338 # then read the UTC offset from the form: 2339 if not form_vars.utc_offset: 2340 db(utable.id == user_id).update(utc_offset = session.s3.utc_offset) 2341 2342 record = dict(user_id = user_id) 2343 2344 # Add the home_phone to pr_contact 2345 home = form_vars.home 2346 if home: 2347 record["home"] = home 2348 2349 # Add the mobile to pr_contact 2350 mobile = form_vars.mobile 2351 if mobile: 2352 record["mobile"] = mobile 2353 2354 # Insert the profile picture 2355 image = form_vars.image 2356 if image != None and hasattr(image, "file"): 2357 # @ToDo: DEBUG!!! 2358 source_file = image.file 2359 original_filename = image.filename 2360 2361 field = temptable.image 2362 newfilename = field.store(source_file, 2363 original_filename, 2364 field.uploadfolder) 2365 if isinstance(field.uploadfield, str): 2366 form_vars[field.uploadfield] = source_file.read() 2367 record["image"] = newfilename 2368 2369 if len(record) > 1: 2370 temptable.update_or_insert(**record)
2371 2372 # -------------------------------------------------------------------------
2373 - def s3_verify_user(self, user):
2374 """" 2375 Designed to be called when a user is verified through: 2376 - responding to their verification email 2377 - if verification isn't required 2378 2379 Does the following: 2380 - Sends a message to the approver to notify them if a user needs approval 2381 - If deployment_settings.auth.always_notify_approver = True, 2382 send them notification regardless 2383 - If approval isn't required - calls s3_approve_user 2384 2385 @returns boolean - if the user has been approved 2386 """ 2387 2388 db = current.db 2389 deployment_settings = current.deployment_settings 2390 session = current.session 2391 utable = self.settings.table_user 2392 2393 # Lookup the Approver 2394 approver, organisation_id = self.s3_approver(user) 2395 2396 if deployment_settings.get_auth_registration_requires_approval() and approver: 2397 approved = False 2398 db(utable.id == user.id).update(registration_key = "pending") 2399 2400 if user.registration_key: 2401 # User has just been verified 2402 session.information = deployment_settings.get_auth_registration_pending_approval() 2403 else: 2404 # No Verification needed 2405 session.information = deployment_settings.get_auth_registration_pending() 2406 message = "approve_user" 2407 2408 else: 2409 approved = True 2410 if organisation_id and not user.get("organisation_id", None): 2411 # Use the whitelist 2412 user["organisation_id"] = organisation_id 2413 db(utable.id == user.id).update(organisation_id = organisation_id) 2414 link_user_to = deployment_settings.get_auth_registration_link_user_to_default() 2415 if link_user_to and not user.get("link_user_to", None): 2416 user["link_user_to"] = link_user_to 2417 self.s3_link_user(user) 2418 self.s3_approve_user(user) 2419 session.confirmation = self.messages.email_verified 2420 session.flash = self.messages.registration_successful 2421 2422 if not deployment_settings.get_auth_always_notify_approver(): 2423 return True 2424 message = "new_user" 2425 2426 # Ensure that we send out the mails in the language that the approver(s) want 2427 if "@" in approver: 2428 # Look up language of the user 2429 record = db(utable.email == approver).select(utable.language, 2430 limitby=(0, 1) 2431 ).first() 2432 if record: 2433 language = record.language 2434 else: 2435 language = deployment_settings.get_L10n_default_language() 2436 approvers = [{"email": approver, 2437 "language": language, 2438 }] 2439 languages = [language] 2440 else: 2441 approvers = [] 2442 aappend = approvers.append 2443 languages = [] 2444 for each_approver in approver: 2445 language = each_approver["language"] 2446 if language not in languages: 2447 languages.append(language) 2448 aappend(each_approver) 2449 2450 T = current.T 2451 auth_messages = self.messages 2452 subjects = {} 2453 messages = {} 2454 first_name = user.first_name 2455 last_name = user.last_name 2456 email = user.email 2457 user_id = user.id 2458 base_url = current.response.s3.base_url 2459 system_name = deployment_settings.get_system_name() 2460 for language in languages: 2461 T.force(language) 2462 if message == "approve_user": 2463 subjects[language] = \ 2464 s3_str(T("%(system_name)s - New User Registration Approval Pending") % \ 2465 {"system_name": system_name}) 2466 messages[language] = s3_str(auth_messages.approve_user % \ 2467 dict(system_name = system_name, 2468 first_name = first_name, 2469 last_name = last_name, 2470 email = email, 2471 url = "%(base_url)s/admin/user/%(id)s" % \ 2472 dict(base_url=base_url, 2473 id=user_id))) 2474 elif message == "new_user": 2475 subjects[language] = \ 2476 s3_str(T("%(system_name)s - New User Registered") % \ 2477 {"system_name": system_name}) 2478 messages[language] = \ 2479 s3_str(auth_messages.new_user % dict(system_name = system_name, 2480 first_name = first_name, 2481 last_name = last_name, 2482 email = email)) 2483 2484 # Restore language for UI 2485 T.force(session.s3.language) 2486 2487 mailer = self.settings.mailer 2488 if mailer.settings.server: 2489 for approver in approvers: 2490 language = approver["language"] 2491 result = mailer.send(to = approver["email"], 2492 subject = subjects[language], 2493 message = messages[language]) 2494 else: 2495 # Email system not configured (yet) 2496 result = None 2497 if not result: 2498 # Don't prevent registration just because email not configured 2499 #db.rollback() 2500 current.response.error = self.messages.email_send_failed 2501 return False 2502 2503 return approved
2504 2505 # -------------------------------------------------------------------------
2506 - def s3_approve_user(self, user, password=None):
2507 """ 2508 S3 framework function 2509 2510 Designed to be called when a user is created through: 2511 - prepop 2512 - approved automatically during registration 2513 - approved by admin 2514 - added by admin 2515 - updated by admin 2516 2517 Does the following: 2518 - Adds user to the 'Authenticated' role 2519 - Adds any default roles for the user 2520 - @ToDo: adds them to the Org_x Access role 2521 2522 @param user: the user Storage() or Row 2523 @param password: optional password to include in a custom welcome_email 2524 """ 2525 2526 user_id = user.id 2527 if not user_id: 2528 return None 2529 2530 db = current.db 2531 s3db = current.s3db 2532 deployment_settings = current.deployment_settings 2533 settings = self.settings 2534 2535 utable = settings.table_user 2536 2537 # Add to 'Authenticated' role 2538 authenticated = self.id_group("Authenticated") 2539 add_membership = self.add_membership 2540 add_membership(authenticated, user_id) 2541 2542 organisation_id = user.organisation_id 2543 2544 # Add User to required registration roles 2545 entity_roles = deployment_settings.get_auth_registration_roles() 2546 link_user_to = user.link_user_to or utable.link_user_to.default or [] 2547 if entity_roles: 2548 gtable = settings.table_group 2549 get_pe_id = s3db.pr_get_pe_id 2550 for entity, roles in entity_roles.items(): 2551 2552 if entity is None and \ 2553 not organisation_id or "staff" not in link_user_to: 2554 # No default realm => do not assign default realm roles 2555 continue 2556 2557 # Get User's Organisation or Site pe_id 2558 if entity in ("organisation_id", "org_group_id", "site_id"): 2559 tablename = "org_%s" % entity.split("_")[0] 2560 entity = get_pe_id(tablename, user[entity]) 2561 if not entity: 2562 continue 2563 2564 rows = db(gtable.uuid.belongs(roles)).select(gtable.id) 2565 for role in rows: 2566 add_membership(role.id, user_id, entity=entity) 2567 2568 if organisation_id and \ 2569 deployment_settings.get_auth_org_admin_to_first(): 2570 # If this is the 1st user to register for an Org, give them ORG_ADMIN for that Org 2571 entity = s3db.pr_get_pe_id("org_organisation", organisation_id) 2572 gtable = settings.table_group 2573 ORG_ADMIN = db(gtable.uuid == "ORG_ADMIN").select(gtable.id, 2574 limitby=(0, 1) 2575 ).first().id 2576 mtable = settings.table_membership 2577 query = (mtable.group_id == ORG_ADMIN) & \ 2578 (mtable.pe_id == entity) 2579 exists = db(query).select(mtable.id, 2580 limitby=(0, 1)) 2581 if not exists: 2582 add_membership(ORG_ADMIN, user_id, entity=entity) 2583 2584 if deployment_settings.has_module("delphi"): 2585 # Add user as a participant of the default problem group 2586 table = s3db.delphi_group 2587 group = db(table.uuid == "DEFAULT").select(table.id, 2588 limitby=(0, 1)).first() 2589 if group: 2590 table = s3db.delphi_membership 2591 table.insert(group_id = group.id, 2592 user_id = user_id, 2593 status = 3, 2594 ) 2595 2596 self.s3_link_user(user) 2597 2598 if current.response.s3.bulk is True: 2599 # Non-interactive imports should stop here 2600 user_email = db(utable.id == user_id).select(utable.email, 2601 ).first().email 2602 self.auth_user_onaccept(user_email, user_id) 2603 return 2604 2605 # Allow them to login 2606 db(utable.id == user_id).update(registration_key = "") 2607 2608 # Approve User's Organisation 2609 if organisation_id and \ 2610 "org_organisation" in \ 2611 deployment_settings.get_auth_record_approval_required_for(): 2612 org_resource = s3db.resource("org_organisation", 2613 organisation_id, 2614 # Do not re-approve (would 2615 # overwrite original approver) 2616 approved = False, 2617 unapproved = True, 2618 ) 2619 approved = org_resource.approve() 2620 if not approved: 2621 # User is verifying their email and is not yet 2622 # logged-in, so approve by system authority 2623 org_resource.approve(approved_by=0) 2624 2625 user_email = db(utable.id == user_id).select(utable.email, 2626 ).first().email 2627 self.auth_user_onaccept(user_email, user_id) 2628 # Send Welcome mail 2629 self.s3_send_welcome_email(user, password)
2630 2631 # ------------------------------------------------------------------------- 2676 2677 # ------------------------------------------------------------------------- 2678 @staticmethod
2679 - def s3_user_profile_onaccept(form):
2680 """ Update the UI locale from user profile """ 2681 2682 if form.vars.language: 2683 current.session.s3.language = form.vars.language
2684 2685 # ------------------------------------------------------------------------- 3011 3012 # ------------------------------------------------------------------------- 3085 3086 # ------------------------------------------------------------------------- 3128 3129 # ------------------------------------------------------------------------- 3168 3169 # Determine the site ID 3170 site_id = user.site_id if hr_type == 1 else None 3171 3172 # Get existing active HR record for this user 3173 ptable = s3db.pr_person 3174 ltable = s3db.pr_person_user 3175 query = (ltable.user_id == user_id) & \ 3176 (ptable.pe_id == ltable.pe_id) & \ 3177 (htable.person_id == ptable.id) & \ 3178 (htable.type == hr_type) & \ 3179 (htable.status == 1) & \ 3180 (htable.deleted == False) 3181 rows = db(query).select(htable.id, limitby=(0, 2)) 3182 3183 accepted = None 3184 if len(rows) == 1: 3185 # Single active HR record of this type 3186 # => update organisation and site 3187 record = rows.first() 3188 hr_id = record.id 3189 3190 # Update the record 3191 customise(hr_id) 3192 db(htable.id == hr_id).update(organisation_id = organisation_id, 3193 site_id = site_id, 3194 ) 3195 accepted = "update" 3196 3197 # Update or create site link 3198 hstable = s3db.hrm_human_resource_site 3199 query = (hstable.human_resource_id == hr_id) 3200 hstable.update_or_insert(query, 3201 site_id = site_id, 3202 human_resource_id = hr_id, 3203 owned_by_user = user_id, 3204 ) 3205 else: 3206 # Multiple or no HR records of this type 3207 3208 if rows: 3209 # Multiple records 3210 # => check if there is one for this organisation and site 3211 if type(person_id) is list: 3212 person_id = person_id[0] 3213 query = (htable.person_id == person_id) & \ 3214 (htable.organisation_id == organisation_id) & \ 3215 (htable.type == hr_type) & \ 3216 (htable.site_id == site_id) & \ 3217 (htable.deleted == False) 3218 row = db(query).select(htable.id, limitby=(0, 1)).first() 3219 else: 3220 # No HR record exists at all 3221 row = None 3222 3223 if row: 3224 # At least one record for this organisation and site exists 3225 # => pass 3226 hr_id = row.id 3227 3228 else: 3229 # Create new HR record 3230 customise(hr_id = None) 3231 record = Storage(person_id=person_id, 3232 organisation_id=organisation_id, 3233 site_id = site_id, 3234 type=hr_type, 3235 owned_by_user=user_id, 3236 ) 3237 hr_id = htable.insert(**record) 3238 record["id"] = hr_id 3239 accepted = "create" 3240 3241 if hr_id and accepted: 3242 3243 # Update any super-records 3244 s3db.update_super(htable, record) 3245 3246 # Set or update the record owner and realm entity 3247 # (enforce update to change realm if organisation changed) 3248 self.s3_set_record_owner(htable, hr_id, force_update=True) 3249 3250 # Run onaccept 3251 s3db.onaccept(htablename, record, method=accepted) 3252 3253 return hr_id 3254 3255 # ------------------------------------------------------------------------- 3323 3324 # -------------------------------------------------------------------------
3325 - def s3_approver(self, user):
3326 """ 3327 Returns the Approver for a new Registration & 3328 the organisation_id field 3329 3330 @param: user - the user record (form.vars when done direct) 3331 @ToDo: Support multiple approvers per Org - via Org Admin (or specific Role?) 3332 Split into separate functions to returning approver & finding users' org from auth_organisations 3333 3334 @returns approver, organisation_id - if approver = False, user is automatically approved by whitelist 3335 """ 3336 3337 db = current.db 3338 3339 approver = None 3340 organisation_id = user.get("organisation_id") 3341 3342 table = current.s3db.auth_organisation 3343 if organisation_id: 3344 # Check for an Organisation-specific Approver 3345 query = (table.organisation_id == organisation_id) & \ 3346 (table.deleted == False) 3347 record = db(query).select(table.approver, 3348 limitby=(0, 1)).first() 3349 elif "email" in user and user["email"] and "@" in user["email"]: 3350 # Check for Domain: Whitelist or specific Approver 3351 domain = user.email.split("@", 1)[-1] 3352 query = (table.domain == domain) & \ 3353 (table.deleted == False) 3354 record = db(query).select(table.organisation_id, 3355 table.approver, 3356 limitby=(0, 1)).first() 3357 else: 3358 record = None 3359 3360 if record: 3361 if not organisation_id: 3362 organisation_id = record.organisation_id 3363 approver = record.approver 3364 3365 if not approver: 3366 # Default Approver 3367 approver = current.deployment_settings.get_mail_approver() 3368 if "@" not in approver: 3369 # Must be the UUID of a Group 3370 utable = db.auth_user 3371 mtable = db.auth_membership 3372 gtable = db.auth_group 3373 query = (gtable.uuid == approver) & \ 3374 (gtable.id == mtable.group_id) & \ 3375 (mtable.user_id == utable.id) 3376 rows = db(query).select(utable.email, 3377 utable.language, 3378 distinct=True) 3379 approver = rows.as_list() 3380 3381 return approver, organisation_id
3382 3383 # -------------------------------------------------------------------------
3384 - def s3_send_welcome_email(self, user, password=None):
3385 """ 3386 Send a welcome mail to newly-registered users 3387 - especially suitable for users from Facebook/Google who don't 3388 verify their emails 3389 3390 @param user: the user dict, must contain "email", and can 3391 contain "language" for translation of the message 3392 @param password: optional password to include in a custom welcome_email 3393 """ 3394 3395 settings = current.deployment_settings 3396 if not settings.get_auth_registration_welcome_email(): 3397 # Welcome-email disabled 3398 return 3399 3400 messages = self.messages 3401 if not settings.get_mail_sender(): 3402 current.response.error = messages.unable_send_email 3403 return 3404 3405 # Ensure that we send out the mails in the language that 3406 # the recipient wants (if we know it) 3407 T = current.T 3408 language = user.get("language") 3409 if language: 3410 T.force(language) 3411 3412 # Compose the message 3413 system_name = s3_str(settings.get_system_name()) 3414 subject = s3_str(messages.welcome_email_subject % \ 3415 {"system_name": system_name}) 3416 message = s3_str(messages.welcome_email % \ 3417 {"system_name": system_name, 3418 "url": settings.get_base_public_url(), 3419 "profile": URL("default", "user", args=["profile"]), 3420 "password": password, 3421 }) 3422 3423 # Restore language for UI 3424 T.force(current.session.s3.language) 3425 3426 recipient = user["email"] 3427 if settings.has_module("msg"): 3428 results = current.msg.send_email(recipient, 3429 subject = subject, 3430 message = message, 3431 ) 3432 else: 3433 results = current.mail.send(recipient, 3434 subject = subject, 3435 message = message, 3436 ) 3437 if not results: 3438 current.response.error = messages.unable_send_email
3439 3440 # ------------------------------------------------------------------------- 3441 # S3-specific authentication methods 3442 # -------------------------------------------------------------------------
3443 - def s3_impersonate(self, user_id):
3444 """ 3445 S3 framework function 3446 3447 Designed to be used within tasks, which are run in a separate 3448 request & hence don't have access to current.auth 3449 3450 @param user_id: auth.user.id or auth.user.email 3451 """ 3452 3453 settings = self.settings 3454 utable = settings.table_user 3455 query = None 3456 if not user_id: 3457 # Anonymous 3458 user = None 3459 elif isinstance(user_id, basestring) and not user_id.isdigit(): 3460 query = (utable[settings.login_userfield] == user_id) 3461 else: 3462 query = (utable.id == user_id) 3463 3464 if query is not None: 3465 user = current.db(query).select(limitby=(0, 1)).first() 3466 if not user: 3467 # Invalid user ID 3468 raise ValueError("User not found") 3469 else: 3470 user = Storage(utable._filter_fields(user, id=True)) 3471 3472 self.user = user 3473 session = current.session 3474 session.auth = Storage(user=user, 3475 last_visit=current.request.now, 3476 expiration=settings.expiration) 3477 self.s3_set_roles() 3478 3479 if user: 3480 # Set the language from the Profile 3481 language = user.language 3482 current.T.force(language) 3483 session.s3.language = language 3484 3485 return user
3486 3487 # -------------------------------------------------------------------------
3488 - def s3_logged_in(self):
3489 """ 3490 Check whether the user is currently logged-in 3491 - tries Basic if not 3492 """ 3493 3494 if self.override: 3495 return True 3496 3497 if not self.is_logged_in(): 3498 # @note: MUST NOT send an HTTP Auth challenge here because 3499 # otherwise, negative tests (e.g. if not auth.s3_logged_in()) 3500 # would always raise and never succeed => omit basic_auth_realm, 3501 # and send the challenge in permission.fail() instead 3502 basic = self.basic() 3503 try: 3504 return basic[2] 3505 except TypeError: 3506 # old web2py 3507 return basic 3508 except: 3509 return False 3510 3511 return True
3512 3513 # ------------------------------------------------------------------------- 3514 # Role Management 3515 # -------------------------------------------------------------------------
3516 - def get_system_roles(self):
3517 """ 3518 Get the IDs of the session roles by their UIDs, and store them 3519 in the current session, as these IDs should never change. 3520 """ 3521 3522 s3 = current.session.s3 3523 try: 3524 system_roles = s3.system_roles 3525 except: 3526 s3 = Storage() 3527 else: 3528 if system_roles: 3529 return system_roles 3530 3531 gtable = self.settings.table_group 3532 if gtable is not None: 3533 S3_SYSTEM_ROLES = self.S3_SYSTEM_ROLES 3534 query = (gtable.deleted != True) & \ 3535 gtable.uuid.belongs(S3_SYSTEM_ROLES.values()) 3536 rows = current.db(query).select(gtable.id, gtable.uuid) 3537 system_roles = Storage([(role.uuid, role.id) for role in rows]) 3538 else: 3539 system_roles = Storage([(uid, None) for uid in S3_SYSTEM_ROLES]) 3540 3541 s3.system_roles = system_roles 3542 return system_roles
3543 3544 # -------------------------------------------------------------------------
3545 - def get_managed_orgs(self):
3546 """ 3547 Get the pe_ids of all managed organisations (to authorize 3548 role assignments) 3549 3550 TODO use this in admin/user controller 3551 """ 3552 3553 user = self.user 3554 if not user: 3555 return None 3556 3557 has_role = self.s3_has_role 3558 sr = self.get_system_roles() 3559 3560 if has_role(sr.ADMIN): 3561 return True 3562 3563 elif has_role(sr.ORG_ADMIN): 3564 if not self.permission.entity_realm: 3565 organisation_id = user.organisation_id 3566 if not organisation_id: 3567 return None 3568 s3db = current.s3db 3569 table = s3db.org_organisation 3570 pe_id = current.db(table.id == organisation_id).select(table.pe_id, 3571 limitby=(0, 1), 3572 cache = s3db.cache, 3573 ).first().pe_id 3574 pe_ids = s3db.pr_get_descendants(pe_id, 3575 entity_types="org_organisation", 3576 ) 3577 pe_ids.append(pe_id) 3578 else: 3579 pe_ids = self.user.realms[sr.ORG_ADMIN] 3580 if pe_ids is None: 3581 return True 3582 return pe_ids 3583 3584 else: 3585 return None
3586 3587 # -------------------------------------------------------------------------
3588 - def s3_set_roles(self):
3589 """ Update pe_id, roles and realms for the current user """ 3590 3591 session = current.session 3592 3593 s3 = current.response.s3 3594 if "restricted_tables" in s3: 3595 del s3["restricted_tables"] 3596 3597 permission = self.permission 3598 permission.clear_cache() 3599 3600 system_roles = self.get_system_roles() 3601 ANONYMOUS = system_roles.ANONYMOUS 3602 if ANONYMOUS: 3603 session.s3.roles = [ANONYMOUS] 3604 else: 3605 session.s3.roles = [] 3606 3607 if self.user: 3608 db = current.db 3609 s3db = current.s3db 3610 3611 user_id = self.user.id 3612 3613 # Set pe_id for current user 3614 ltable = s3db.table("pr_person_user") 3615 if ltable is not None: 3616 query = (ltable.user_id == user_id) 3617 row = db(query).select(ltable.pe_id, 3618 limitby=(0, 1), 3619 cache=s3db.cache).first() 3620 if row: 3621 self.user["pe_id"] = row.pe_id 3622 else: 3623 self.user["pe_id"] = None 3624 3625 # Get all current auth_memberships of the user 3626 mtable = self.settings.table_membership 3627 query = (mtable.deleted != True) & \ 3628 (mtable.user_id == user_id) & \ 3629 (mtable.group_id != None) 3630 rows = db(query).select(mtable.group_id, mtable.pe_id, 3631 cacheable=True) 3632 3633 # Add all group_ids to session.s3.roles 3634 session.s3.roles.extend(list(set([row.group_id for row in rows]))) 3635 3636 # Realms: 3637 # Permissions of a group apply only for records owned by any of 3638 # the entities which belong to the realm of the group membership 3639 3640 if not permission.entity_realm: 3641 # Group memberships have no realms (policy 5 and below) 3642 self.user["realms"] = Storage([(row.group_id, None) for row in rows]) 3643 self.user["delegations"] = Storage() 3644 3645 else: 3646 # Group memberships are limited to realms (policy 6 and above) 3647 realms = {} 3648 delegations = {} 3649 3650 # These roles can't be realm-restricted: 3651 unrestrictable = (system_roles.ADMIN, 3652 system_roles.ANONYMOUS, 3653 system_roles.AUTHENTICATED, 3654 ) 3655 3656 default_realm = s3db.pr_realm(self.user["pe_id"]) 3657 3658 # Store the realms: 3659 for row in rows: 3660 group_id = row.group_id 3661 if group_id in realms and realms[group_id] is None: 3662 continue 3663 if group_id in unrestrictable: 3664 realms[group_id] = None 3665 continue 3666 if group_id not in realms: 3667 realms[group_id] = [] 3668 realm = realms[group_id] 3669 pe_id = row.pe_id 3670 if pe_id is None: 3671 if default_realm: 3672 realm.extend([e for e in default_realm 3673 if e not in realm]) 3674 if not realm: 3675 del realms[group_id] 3676 elif pe_id == 0: 3677 # Site-wide 3678 realms[group_id] = None 3679 elif pe_id not in realm: 3680 realms[group_id].append(pe_id) 3681 3682 if permission.entity_hierarchy: 3683 # Realms include subsidiaries of the realm entities 3684 3685 # Get all entities in realms 3686 all_entities = [] 3687 append = all_entities.append 3688 for realm in realms.values(): 3689 if realm is not None: 3690 for entity in realm: 3691 if entity not in all_entities: 3692 append(entity) 3693 3694 # Lookup all delegations to any OU ancestor of the user 3695 if permission.delegations and self.user.pe_id: 3696 3697 ancestors = s3db.pr_get_ancestors(self.user.pe_id) 3698 3699 dtable = s3db.pr_delegation 3700 rtable = s3db.pr_role 3701 atable = s3db.pr_affiliation 3702 3703 dn = dtable._tablename 3704 rn = rtable._tablename 3705 an = atable._tablename 3706 3707 query = (dtable.deleted != True) & \ 3708 (atable.role_id == dtable.role_id) & \ 3709 (atable.pe_id.belongs(ancestors)) & \ 3710 (rtable.id == dtable.role_id) 3711 rows = db(query).select(rtable.pe_id, 3712 dtable.group_id, 3713 atable.pe_id, 3714 cacheable=True) 3715 3716 extensions = [] 3717 partners = [] 3718 for row in rows: 3719 extensions.append(row[rn].pe_id) 3720 partners.append(row[an].pe_id) 3721 else: 3722 rows = [] 3723 extensions = [] 3724 partners = [] 3725 3726 # Lookup the subsidiaries of all realms and extensions 3727 entities = all_entities + extensions + partners 3728 descendants = s3db.pr_descendants(entities) 3729 3730 pmap = {} 3731 for p in partners: 3732 if p in all_entities: 3733 pmap[p] = [p] 3734 elif p in descendants: 3735 d = descendants[p] 3736 pmap[p] = [e for e in all_entities if e in d] or [p] 3737 3738 # Add the subsidiaries to the realms 3739 for group_id in realms: 3740 realm = realms[group_id] 3741 if realm is None: 3742 continue 3743 append = realm.append 3744 for entity in list(realm): 3745 if entity in descendants: 3746 for subsidiary in descendants[entity]: 3747 if subsidiary not in realm: 3748 append(subsidiary) 3749 3750 # Process the delegations 3751 if permission.delegations: 3752 for row in rows: 3753 3754 # owner == delegates group_id to ==> partner 3755 owner = row[rn].pe_id 3756 partner = row[an].pe_id 3757 group_id = row[dn].group_id 3758 3759 if group_id in delegations and \ 3760 owner in delegations[group_id]: 3761 # Duplicate 3762 continue 3763 if partner not in pmap: 3764 continue 3765 3766 # Find the realm 3767 if group_id not in delegations: 3768 delegations[group_id] = Storage() 3769 groups = delegations[group_id] 3770 3771 r = [owner] 3772 if owner in descendants: 3773 r.extend(descendants[owner]) 3774 3775 for p in pmap[partner]: 3776 if p not in groups: 3777 groups[p] = [] 3778 realm = groups[p] 3779 realm.extend(r) 3780 3781 self.user["realms"] = realms 3782 self.user["delegations"] = delegations 3783 3784 if ANONYMOUS: 3785 # Anonymous role has no realm 3786 self.user["realms"][ANONYMOUS] = None
3787 3788 # -------------------------------------------------------------------------
3789 - def s3_create_role(self, role, description=None, *acls, **args):
3790 """ 3791 Back-end method to create roles with ACLs 3792 3793 @param role: display name for the role 3794 @param description: description of the role (optional) 3795 @param acls: list of initial ACLs to assign to this role 3796 @param args: keyword arguments (see below) 3797 @keyword name: a unique name for the role 3798 @keyword hidden: hide this role completely from the RoleManager 3799 @keyword system: role can be assigned, but neither modified nor 3800 deleted in the RoleManager 3801 @keyword protected: role can be assigned and edited, but not 3802 deleted in the RoleManager 3803 """ 3804 3805 table = self.settings.table_group 3806 3807 hidden = args.get("hidden") 3808 system = args.get("system") 3809 protected = args.get("protected") 3810 3811 if isinstance(description, dict): 3812 acls = [description] + acls 3813 description = None 3814 3815 uid = args.get("uid", None) 3816 if uid: 3817 record = current.db(table.uuid == uid).select(table.id, 3818 limitby=(0, 1) 3819 ).first() 3820 else: 3821 record = None 3822 uid = uuid4() 3823 3824 system_data = {} 3825 if hidden is not None: 3826 system_data["hidden"] = hidden 3827 if protected is not None: 3828 system_data["protected"] = protected 3829 if system is not None: 3830 system_data["system"] = system 3831 3832 if record: 3833 role_id = record.id 3834 record.update_record(deleted=False, 3835 role=role, 3836 description=description, 3837 **system_data) 3838 else: 3839 role_id = table.insert(uuid=uid, 3840 role=role, 3841 description=description, 3842 **system_data) 3843 if role_id: 3844 update_acl = self.permission.update_acl 3845 for acl in acls: 3846 update_acl(role_id, **acl) 3847 3848 return role_id
3849 3850 # -------------------------------------------------------------------------
3851 - def s3_delete_role(self, role_id):
3852 """ 3853 Remove a role from the system. 3854 3855 @param role_id: the ID or UID of the role 3856 3857 @note: protected roles cannot be deleted with this function, 3858 need to reset the protected-flag first to override 3859 """ 3860 3861 db = current.db 3862 table = self.settings.table_group 3863 3864 if isinstance(role_id, str) and not role_id.isdigit(): 3865 query = (table.uuid == role_id) 3866 else: 3867 role_id = int(role_id) 3868 query = (table.id == role_id) 3869 3870 role = db(query).select(table.id, 3871 table.uuid, 3872 table.protected, 3873 limitby=(0, 1), 3874 ).first() 3875 3876 if role and not role.protected: 3877 3878 group_id = role.id 3879 data = {"deleted": True, 3880 "group_id": None, 3881 "deleted_fk": '{"group_id": %s}' % group_id, 3882 } 3883 3884 # Remove all memberships for this role 3885 mtable = self.settings.table_membership 3886 db(mtable.group_id == group_id).update(**data) 3887 3888 # Remove all permission rules for this role 3889 ptable = self.permission.table 3890 db(ptable.group_id == group_id).update(**data) 3891 3892 # Remove the role 3893 deleted_uuid = "%s-deleted-%s" % (uuid4().hex[-12:], role.uuid[:40]) 3894 role.update_record(uuid = deleted_uuid, 3895 role = None, 3896 deleted = True, 3897 )
3898 3899 # -------------------------------------------------------------------------
3900 - def s3_assign_role(self, user_id, group_id, for_pe=None):
3901 """ 3902 Assigns a role to a user (add the user to a user group) 3903 3904 @param user_id: the record ID of the user account 3905 @param group_id: the record ID(s)/UID(s) of the group 3906 @param for_pe: the person entity (pe_id) to restrict the group 3907 membership to, possible values: 3908 3909 - None: use default realm (entities the user is 3910 affiliated with) 3911 - 0: site-wide realm (no entity-restriction) 3912 - X: restrict to records owned by entity X 3913 3914 @note: strings are assumed to be group UIDs 3915 @note: for_pe will be ignored for ADMIN, ANONYMOUS and AUTHENTICATED 3916 """ 3917 3918 db = current.db 3919 gtable = self.settings.table_group 3920 mtable = self.settings.table_membership 3921 3922 # Find the group IDs 3923 query = None 3924 uuids = None 3925 if isinstance(group_id, (list, tuple)): 3926 if isinstance(group_id[0], str): 3927 uuids = group_id 3928 query = (gtable.uuid.belongs(group_id)) 3929 else: 3930 group_ids = group_id 3931 elif isinstance(group_id, str) and not group_id.isdigit(): 3932 uuids = [group_id] 3933 query = (gtable.uuid == group_id) 3934 else: 3935 group_ids = [group_id] 3936 if query is not None: 3937 query = (gtable.deleted != True) & query 3938 groups = db(query).select(gtable.id, gtable.uuid) 3939 group_ids = [g.id for g in groups] 3940 missing = [uuid for uuid in uuids 3941 if uuid not in [g.uuid for g in groups]] 3942 for m in missing: 3943 group_id = self.s3_create_role(m, uid=m) 3944 if group_id: 3945 group_ids.append(group_id) 3946 3947 # Find the assigned groups 3948 query = (mtable.deleted != True) & \ 3949 (mtable.user_id == user_id) & \ 3950 (mtable.group_id.belongs(group_ids) & \ 3951 (mtable.pe_id == for_pe)) 3952 assigned = db(query).select(mtable.group_id) 3953 assigned_groups = [g.group_id for g in assigned] 3954 3955 # Add missing memberships 3956 sr = self.get_system_roles() 3957 unrestrictable = [str(sr.ADMIN), 3958 str(sr.ANONYMOUS), 3959 str(sr.AUTHENTICATED)] 3960 for group_id in group_ids: 3961 if group_id not in assigned_groups: 3962 membership = {"user_id": user_id, 3963 "group_id": group_id} 3964 if for_pe is not None and str(group_id) not in unrestrictable: 3965 membership["pe_id"] = for_pe 3966 #membership_id = mtable.insert(**membership) 3967 mtable.insert(**membership) 3968 3969 # Update roles for current user if required 3970 if self.user and str(user_id) == str(self.user.id): 3971 self.s3_set_roles()
3972 3973 # -------------------------------------------------------------------------
3974 - def s3_withdraw_role(self, user_id, group_id, for_pe=None):
3975 """ 3976 Removes a role assignment from a user account 3977 3978 @param user_id: the record ID of the user account 3979 @param group_id: the record ID(s)/UID(s) of the role 3980 @param for_pe: only remove the group membership for this 3981 realm, possible values: 3982 3983 - None: only remove for the default realm 3984 - 0: only remove for the site-wide realm 3985 - X: only remove for entity X 3986 - []: remove for any realms 3987 3988 @note: strings are assumed to be role UIDs 3989 """ 3990 3991 if not group_id: 3992 return 3993 3994 db = current.db 3995 gtable = self.settings.table_group 3996 mtable = self.settings.table_membership 3997 3998 # Find the group IDs 3999 query = None 4000 if isinstance(group_id, (list, tuple)): 4001 if isinstance(group_id[0], str): 4002 query = (gtable.uuid.belongs(group_id)) 4003 else: 4004 group_ids = group_id 4005 elif isinstance(group_id, str): 4006 query = (gtable.uuid == group_id) 4007 else: 4008 group_ids = [group_id] 4009 if query is not None: 4010 query = (gtable.deleted != True) & query 4011 groups = db(query).select(gtable.id) 4012 group_ids = [g.id for g in groups] 4013 4014 # Get the assigned groups 4015 query = (mtable.deleted != True) & \ 4016 (mtable.user_id == user_id) & \ 4017 (mtable.group_id.belongs(group_ids)) 4018 4019 sr = self.get_system_roles() 4020 unrestrictable = [str(sr.ADMIN), 4021 str(sr.ANONYMOUS), 4022 str(sr.AUTHENTICATED)] 4023 if for_pe != []: 4024 query &= ((mtable.pe_id == for_pe) | \ 4025 (mtable.group_id.belongs(unrestrictable))) 4026 memberships = db(query).select() 4027 4028 # Archive the memberships 4029 for m in memberships: 4030 deleted_fk = {"user_id": m.user_id, 4031 "group_id": m.group_id} 4032 if for_pe: 4033 deleted_fk["pe_id"] = for_pe 4034 deleted_fk = json.dumps(deleted_fk) 4035 m.update_record(deleted=True, 4036 deleted_fk=deleted_fk, 4037 user_id=None, 4038 group_id=None) 4039 4040 # Update roles for current user if required 4041 if self.user and str(user_id) == str(self.user.id): 4042 self.s3_set_roles()
4043 4044 # -------------------------------------------------------------------------
4045 - def s3_get_roles(self, user_id, for_pe=DEFAULT):
4046 """ 4047 Lookup all roles which have been assigned to user for an entity 4048 4049 @param user_id: the user_id 4050 @param for_pe: the entity (pe_id) or list of entities 4051 """ 4052 4053 if not user_id: 4054 return [] 4055 4056 mtable = self.settings.table_membership 4057 query = (mtable.deleted != True) & \ 4058 (mtable.user_id == user_id) 4059 if isinstance(for_pe, (list, tuple)): 4060 if len(for_pe): 4061 query &= (mtable.pe_id.belongs(for_pe)) 4062 elif for_pe is not DEFAULT: 4063 query &= (mtable.pe_id == for_pe) 4064 rows = current.db(query).select(mtable.group_id) 4065 return list(set([row.group_id for row in rows]))
4066 4067 # -------------------------------------------------------------------------
4068 - def s3_has_role(self, role, for_pe=None):
4069 """ 4070 Check whether the currently logged-in user has a certain role 4071 (auth_group membership). 4072 4073 @param role: the record ID or UID of the role 4074 @param for_pe: check for this particular realm, possible values: 4075 4076 None - for any entity 4077 0 - site-wide 4078 X - for entity X 4079 """ 4080 4081 # Allow override 4082 if self.override: 4083 return True 4084 4085 system_roles = self.get_system_roles() 4086 if role == system_roles.ANONYMOUS: 4087 # All users have the anonymous role 4088 return True 4089 4090 s3 = current.session.s3 4091 4092 # Trigger HTTP basic auth 4093 self.s3_logged_in() 4094 4095 # Get the realms 4096 if not s3: 4097 return False 4098 realms = None 4099 if self.user: 4100 realms = self.user.realms 4101 elif s3.roles: 4102 realms = Storage([(r, None) for r in s3.roles]) 4103 if not realms: 4104 return False 4105 4106 # Administrators have all roles 4107 if system_roles.ADMIN in realms: 4108 return True 4109 4110 # Resolve role ID/UID 4111 if isinstance(role, str): 4112 if role.isdigit(): 4113 role = int(role) 4114 else: 4115 gtable = self.settings.table_group 4116 query = (gtable.uuid == role) & \ 4117 (gtable.deleted != True) 4118 row = current.db(query).select(gtable.id, 4119 cache = (current.cache.ram, 600), 4120 limitby = (0, 1), 4121 ).first() 4122 if row: 4123 role = row.id 4124 else: 4125 return False 4126 4127 # Check the realm 4128 if role in realms: 4129 realm = realms[role] 4130 if realm is None or for_pe is None or for_pe in realm: 4131 return True 4132 return False
4133 4134 # -------------------------------------------------------------------------
4135 - def s3_has_roles(self, roles, for_pe=None, all=False):
4136 """ 4137 Check whether the currently logged-in user has at least one 4138 out of a set of roles (or all of them, with all=True) 4139 4140 @param roles: list|tuple|set of role IDs or UIDs 4141 @param for_pe: check for this particular realm, possible values: 4142 None - for any entity 4143 0 - site-wide 4144 X - for entity X 4145 @param all: check whether the user has all of the roles 4146 """ 4147 4148 # Override 4149 if self.override or not roles: 4150 return True 4151 4152 # Get the realms 4153 session_s3 = current.session.s3 4154 if not session_s3: 4155 return False 4156 realms = None 4157 if self.user: 4158 realms = self.user.realms 4159 elif session_s3.roles: 4160 realms = Storage([(r, None) for r in session_s3.roles]) 4161 if not realms: 4162 return False 4163 4164 # Administrators have all roles (no need to check) 4165 system_roles = self.get_system_roles() 4166 if system_roles.ADMIN in realms: 4167 return True 4168 4169 # Resolve any role UIDs 4170 if not isinstance(roles, (tuple, list, set)): 4171 roles = [roles] 4172 4173 check = set() 4174 resolve = set() 4175 for role in roles: 4176 if isinstance(role, basestring): 4177 resolve.add(role) 4178 else: 4179 check.add(role) 4180 4181 if resolve: 4182 gtable = self.settings.table_group 4183 query = (gtable.uuid.belongs(resolve)) & \ 4184 (gtable.deleted != True) 4185 rows = current.db(query).select(gtable.id, 4186 cache = (current.cache.ram, 600), 4187 ) 4188 for row in rows: 4189 check.add(row.id) 4190 4191 # Check each role 4192 for role in check: 4193 4194 if role == system_roles.ANONYMOUS: 4195 # All users have the anonymous role 4196 has_role = True 4197 elif role in realms: 4198 realm = realms[role] 4199 has_role = realm is None or for_pe is None or for_pe in realm 4200 else: 4201 has_role = False 4202 4203 if has_role: 4204 if not all: 4205 return True 4206 elif all: 4207 return False 4208 4209 return bool(all)
4210 4211 # -------------------------------------------------------------------------
4212 - def s3_group_members(self, group_id, for_pe=DEFAULT):
4213 """ 4214 Get a list of members of a group 4215 4216 @param group_id: the group record ID 4217 @param for_pe: show only group members for this PE 4218 4219 @return: a list of the user_ids for members of a group 4220 """ 4221 4222 mtable = self.settings.table_membership 4223 4224 query = (mtable.deleted != True) & \ 4225 (mtable.group_id == group_id) 4226 if for_pe is None: 4227 query &= (mtable.pe_id == None) 4228 elif for_pe is not DEFAULT: 4229 query &= (mtable.pe_id == for_pe) 4230 members = current.db(query).select(mtable.user_id) 4231 return [m.user_id for m in members]
4232 4233 # -------------------------------------------------------------------------
4234 - def s3_delegate_role(self, 4235 group_id, 4236 entity, 4237 receiver=None, 4238 role=None, 4239 role_type=None):
4240 """ 4241 Delegate a role (auth_group) from one entity to another 4242 4243 @param group_id: the role ID or UID (or a list of either) 4244 @param entity: the delegating entity 4245 @param receiver: the pe_id of the receiving entity (or a list of pe_ids) 4246 @param role: the affiliation role 4247 @param role_type: the role type for the affiliation role (default=9) 4248 4249 @note: if role is None, a new role of role_type 0 will be created 4250 for each entity in receiver and used for the delegation 4251 (1:1 delegation) 4252 @note: if both receiver and role are specified, the delegation will 4253 add all receivers to this role and create a 1:N delegation to 4254 this role. If the role does not exist, it will be created (using 4255 the given role type) 4256 """ 4257 4258 if not self.permission.delegations: 4259 return False 4260 4261 db = current.db 4262 s3db = current.s3db 4263 dtable = s3db.table("pr_delegation") 4264 rtable = s3db.table("pr_role") 4265 atable = s3db.table("pr_affiliation") 4266 if dtable is None or \ 4267 rtable is None or \ 4268 atable is None: 4269 return False 4270 if not group_id or not entity or not receiver and not role: 4271 return False 4272 4273 # Find the group IDs 4274 gtable = self.settings.table_group 4275 query = None 4276 uuids = None 4277 if isinstance(group_id, (list, tuple)): 4278 if isinstance(group_id[0], str): 4279 uuids = group_id 4280 query = (gtable.uuid.belongs(group_id)) 4281 else: 4282 group_ids = group_id 4283 elif isinstance(group_id, str) and not group_id.isdigit(): 4284 uuids = [group_id] 4285 query = (gtable.uuid == group_id) 4286 else: 4287 group_ids = [group_id] 4288 if query is not None: 4289 query = (gtable.deleted != True) & query 4290 groups = db(query).select(gtable.id, gtable.uuid) 4291 group_ids = [g.id for g in groups] 4292 missing = [u for u in uuids if u not in [g.uuid for g in groups]] 4293 for m in missing: 4294 group_id = self.s3_create_role(m, uid=m) 4295 if group_id: 4296 group_ids.append(group_id) 4297 if not group_ids: 4298 return False 4299 4300 if receiver is not None: 4301 if not isinstance(receiver, (list, tuple)): 4302 receiver = [receiver] 4303 query = (dtable.deleted != True) & \ 4304 (dtable.group_id.belongs(group_ids)) & \ 4305 (dtable.role_id == rtable.id) & \ 4306 (rtable.deleted != True) & \ 4307 (atable.role_id == rtable.id) & \ 4308 (atable.deleted != True) & \ 4309 (atable.pe_id.belongs(receiver)) 4310 rows = db(query).select(atable.pe_id) 4311 assigned = [row.pe_id for row in rows] 4312 receivers = [r for r in receiver if r not in assigned] 4313 else: 4314 receivers = None 4315 4316 if role_type is None: 4317 role_type = 9 # Other 4318 4319 roles = [] 4320 if role is None: 4321 if receivers is None: 4322 return False 4323 for pe_id in receivers: 4324 role_name = "__DELEGATION__%s__%s__" % (entity, pe_id) 4325 query = (rtable.role == role_name) 4326 role = db(query).select(limitby=(0, 1)).first() 4327 if role is not None: 4328 if role.deleted: 4329 role.update_record(deleted=False, 4330 role_type=0) 4331 role_id = role.id 4332 else: 4333 role_id = s3db.pr_add_affiliation(entity, pe_id, 4334 role=role_name, 4335 role_type=0) 4336 if role_id: 4337 roles.append(role_id) 4338 else: 4339 query = (rtable.deleted != True) & \ 4340 (rtable.pe_id == entity) & \ 4341 (rtable.role == role) 4342 row = db(query).select(rtable.id, limitby=(0, 1)).first() 4343 if row is None: 4344 role_id = rtable.insert(pe_id = entity, 4345 role = role, 4346 role_type = role_type) 4347 else: 4348 role_id = row.id 4349 if role_id: 4350 if receivers is not None: 4351 pr_rebuild_path = s3db.pr_rebuild_path 4352 for pe_id in receivers: 4353 atable.insert(role_id=role_id, 4354 pe_id=pe_id) 4355 pr_rebuild_path(pe_id, clear=True) 4356 roles.append(role_id) 4357 4358 for role_id in roles: 4359 for group_id in group_ids: 4360 dtable.insert(role_id=role_id, group_id=group_id) 4361 4362 # Update roles for current user if required 4363 self.s3_set_roles() 4364 4365 return True
4366 4367 # -------------------------------------------------------------------------
4368 - def s3_remove_delegation(self, 4369 group_id, 4370 entity, 4371 receiver=None, 4372 role=None):
4373 """ 4374 Remove a delegation. 4375 4376 @param group_id: the auth_group ID or UID (or a list of either) 4377 @param entity: the delegating entity 4378 @param receiver: the receiving entity 4379 @param role: the affiliation role 4380 4381 @note: if receiver is specified, only 1:1 delegations (to role_type 0) 4382 will be removed, but not 1:N delegations => to remove for 1:N 4383 you must specify the role instead of the receiver 4384 @note: if both receiver and role are None, all delegations with this 4385 group_id will be removed for the entity 4386 """ 4387 4388 if not self.permission.delegations: 4389 return False 4390 4391 db = current.db 4392 s3db = current.s3db 4393 dtable = s3db.table("pr_delegation") 4394 rtable = s3db.table("pr_role") 4395 atable = s3db.table("pr_affiliation") 4396 if dtable is None or \ 4397 rtable is None or \ 4398 atable is None: 4399 return False 4400 if not group_id or not entity or not receiver and not role: 4401 return False 4402 4403 # Find the group IDs 4404 gtable = self.settings.table_group 4405 query = None 4406 #uuids = None 4407 if isinstance(group_id, (list, tuple)): 4408 if isinstance(group_id[0], str): 4409 #uuids = group_id 4410 query = (gtable.uuid.belongs(group_id)) 4411 else: 4412 group_ids = group_id 4413 elif isinstance(group_id, str) and not group_id.isdigit(): 4414 #uuids = [group_id] 4415 query = (gtable.uuid == group_id) 4416 else: 4417 group_ids = [group_id] 4418 if query is not None: 4419 query = (gtable.deleted != True) & query 4420 groups = db(query).select(gtable.id, gtable.uuid) 4421 group_ids = [g.id for g in groups] 4422 if not group_ids: 4423 return False 4424 4425 # Get all delegations 4426 query = (dtable.deleted != True) & \ 4427 (dtable.group_id.belongs(group_ids)) & \ 4428 (dtable.role_id == rtable.id) & \ 4429 (rtable.pe_id == entity) & \ 4430 (atable.role_id == rtable.id) 4431 if receiver: 4432 if not isinstance(receiver, (list, tuple)): 4433 receiver = [receiver] 4434 query &= (atable.pe_id.belongs(receiver)) 4435 elif role: 4436 query &= (rtable.role == role) 4437 rows = db(query).select(dtable.id, 4438 dtable.group_id, 4439 rtable.id, 4440 rtable.role_type) 4441 4442 # Remove properly 4443 rmv = Storage() 4444 for row in rows: 4445 if not receiver or row[rtable.role_type] == 0: 4446 deleted_fk = {"role_id": row[rtable.id], 4447 "group_id": row[dtable.group_id]} 4448 rmv[row[dtable.id]] = json.dumps(deleted_fk) 4449 for record_id in rmv: 4450 query = (dtable.id == record_id) 4451 data = {"role_id": None, 4452 "group_id": None, 4453 "deleted_fk": rmv[record_id]} 4454 db(query).update(**data) 4455 4456 # Maybe update the current user's delegations? 4457 if len(rmv): 4458 self.s3_set_roles() 4459 return True
4460 4461 # -------------------------------------------------------------------------
4462 - def s3_get_delegations(self, entity, role_type=0, by_role=False):
4463 """ 4464 Lookup delegations for an entity, ordered either by 4465 receiver (by_role=False) or by affiliation role (by_role=True) 4466 4467 @param entity: the delegating entity (pe_id) 4468 @param role_type: limit the lookup to this affiliation role type, 4469 (can use 0 to lookup 1:1 delegations) 4470 @param by_role: group by affiliation roles 4471 4472 @return: a Storage {<receiver>: [group_ids]}, or 4473 a Storage {<rolename>: {entities:[pe_ids], groups:[group_ids]}} 4474 """ 4475 4476 if not entity or not self.permission.delegations: 4477 return None 4478 s3db = current.s3db 4479 dtable = s3db.pr_delegation 4480 rtable = s3db.pr_role 4481 atable = s3db.pr_affiliation 4482 if None in (dtable, rtable, atable): 4483 return None 4484 4485 query = (rtable.deleted != True) & \ 4486 (dtable.deleted != True) & \ 4487 (atable.deleted != True) & \ 4488 (rtable.pe_id == entity) & \ 4489 (dtable.role_id == rtable.id) & \ 4490 (atable.role_id == rtable.id) 4491 if role_type is not None: 4492 query &= (rtable.role_type == role_type) 4493 rows = current.db(query).select(atable.pe_id, 4494 rtable.role, 4495 dtable.group_id) 4496 delegations = Storage() 4497 for row in rows: 4498 receiver = row[atable.pe_id] 4499 role = row[rtable.role] 4500 group_id = row[dtable.group_id] 4501 if by_role: 4502 if role not in delegations: 4503 delegations[role] = Storage(entities=[], groups=[]) 4504 delegation = delegations[role] 4505 if receiver not in delegation.entities: 4506 delegation.entities.append(receiver) 4507 if group_id not in delegation.groups: 4508 delegation.groups.append(group_id) 4509 else: 4510 if receiver not in delegations: 4511 delegations[receiver] = [group_id] 4512 else: 4513 delegations[receiver].append(group_id) 4514 return delegations
4515 4516 # ------------------------------------------------------------------------- 4517 # ACL management 4518 # -------------------------------------------------------------------------
4519 - def s3_update_acls(self, role, *acls):
4520 """ Wrapper for permission.update_acl to allow batch updating """ 4521 4522 for acl in acls: 4523 self.permission.update_acl(role, **acl)
4524 4525 # ------------------------------------------------------------------------- 4526 # User Identity 4527 # -------------------------------------------------------------------------
4528 - def s3_get_user_id(self, person_id=None, pe_id=None):
4529 """ 4530 Get the user_id for a person_id 4531 4532 @param person_id: the pr_person record ID, or a user email address 4533 @param pe_id: the person entity ID, alternatively 4534 """ 4535 4536 result = None 4537 4538 if isinstance(person_id, basestring) and not person_id.isdigit(): 4539 # User email address 4540 utable = self.settings.table_user 4541 query = (utable.email == person_id) 4542 user = current.db(query).select(utable.id, 4543 limitby=(0, 1), 4544 ).first() 4545 if user: 4546 result = user.id 4547 else: 4548 # Person/PE ID 4549 s3db = current.s3db 4550 ltable = s3db.pr_person_user 4551 if person_id: 4552 ptable = s3db.pr_person 4553 query = (ptable.id == person_id) & \ 4554 (ptable.pe_id == ltable.pe_id) 4555 else: 4556 query = (ltable.pe_id == pe_id) 4557 link = current.db(query).select(ltable.user_id, 4558 limitby=(0, 1), 4559 ).first() 4560 if link: 4561 result = link.user_id 4562 4563 return result
4564 4565 # -------------------------------------------------------------------------
4566 - def s3_user_pe_id(self, user_id):
4567 """ 4568 Get the person pe_id for a user ID 4569 4570 @param user_id: the user ID 4571 """ 4572 4573 table = current.s3db.pr_person_user 4574 row = current.db(table.user_id == user_id).select(table.pe_id, 4575 limitby=(0, 1), 4576 ).first() 4577 return row.pe_id if row else None
4578 4579 # -------------------------------------------------------------------------
4580 - def s3_bulk_user_pe_id(self, user_ids):
4581 """ 4582 Get the list of person pe_id for list of user_ids 4583 4584 @param user_id: list of user IDs 4585 """ 4586 4587 table = current.s3db.pr_person_user 4588 if not isinstance(user_ids, list): 4589 user_ids = [user_ids] 4590 rows = current.db(table.user_id.belongs([user_id for user_id in user_ids])).\ 4591 select(table.pe_id, 4592 table.user_id) 4593 if rows: 4594 return {row.user_id: row.pe_id for row in rows} 4595 return None
4596 4597 # -------------------------------------------------------------------------
4598 - def s3_logged_in_person(self):
4599 """ 4600 Get the person record ID for the current logged-in user 4601 """ 4602 4603 row = None 4604 4605 if self.s3_logged_in(): 4606 ptable = current.s3db.pr_person 4607 try: 4608 query = (ptable.pe_id == self.user.pe_id) 4609 except AttributeError: 4610 # Prepop (auth.override, self.user is None) 4611 pass 4612 else: 4613 row = current.db(query).select(ptable.id, 4614 limitby=(0, 1), 4615 ).first() 4616 4617 return row.id if row else None
4618 4619 # -------------------------------------------------------------------------
4620 - def s3_logged_in_human_resource(self):
4621 """ 4622 Get the first HR record ID for the current logged-in user 4623 """ 4624 4625 row = None 4626 4627 if self.s3_logged_in(): 4628 s3db = current.s3db 4629 ptable = s3db.pr_person 4630 htable = s3db.hrm_human_resource 4631 try: 4632 query = (htable.person_id == ptable.id) & \ 4633 (ptable.pe_id == self.user.pe_id) 4634 except AttributeError: 4635 # Prepop (auth.override, self.user is None) 4636 pass 4637 else: 4638 row = current.db(query).select(htable.id, 4639 orderby = ~htable.modified_on, 4640 limitby = (0, 1), 4641 ).first() 4642 4643 return row.id if row else None
4644 4645 # ------------------------------------------------------------------------- 4646 # Core Authorization Methods 4647 # -------------------------------------------------------------------------
4648 - def s3_has_permission(self, method, table, record_id=None, c=None, f=None):
4649 """ 4650 S3 framework function to define whether a user can access a record 4651 in manner "method". Designed to be called from the RESTlike 4652 controller. 4653 4654 @param method: the access method as string, one of 4655 "create", "read", "update", "delete" 4656 @param table: the table or tablename 4657 @param record_id: the record ID (if any) 4658 @param c: the controller name (overrides current.request) 4659 @param f: the function name (overrides current.request) 4660 """ 4661 4662 if self.override: 4663 return True 4664 4665 sr = self.get_system_roles() 4666 4667 if not hasattr(table, "_tablename"): 4668 tablename = table 4669 table = current.s3db.table(tablename, db_only=True) 4670 if table is None: 4671 current.log.warning("Permission check on Table %s failed as couldn't load table. Module disabled?" % tablename) 4672 # Return a different Falsy value 4673 return None 4674 4675 policy = current.deployment_settings.get_security_policy() 4676 4677 # Simple policy 4678 if policy == 1: 4679 # Anonymous users can Read. 4680 if method == "read": 4681 authorised = True 4682 else: 4683 # Authentication required for Create/Update/Delete. 4684 authorised = self.s3_logged_in() 4685 4686 # Editor policy 4687 elif policy == 2: 4688 # Anonymous users can Read. 4689 if method == "read": 4690 authorised = True 4691 elif method == "create": 4692 # Authentication required for Create. 4693 authorised = self.s3_logged_in() 4694 elif record_id == 0 and method == "update": 4695 # Authenticated users can update at least some records 4696 authorised = self.s3_logged_in() 4697 else: 4698 # Editor role required for Update/Delete. 4699 authorised = self.s3_has_role(sr.EDITOR) 4700 if not authorised and self.user and "owned_by_user" in table: 4701 # Creator of Record is allowed to Edit 4702 query = (table.id == record_id) 4703 record = current.db(query).select(table.owned_by_user, 4704 limitby=(0, 1)).first() 4705 if record and self.user.id == record.owned_by_user: 4706 authorised = True 4707 4708 # Use S3Permission ACLs 4709 elif policy in (3, 4, 5, 6, 7, 8): 4710 authorised = self.permission.has_permission(method, 4711 c = c, 4712 f = f, 4713 t = table, 4714 record = record_id) 4715 4716 # Web2py default policy 4717 else: 4718 if self.s3_logged_in(): 4719 # Administrators are always authorised 4720 if self.s3_has_role(sr.ADMIN): 4721 authorised = True 4722 else: 4723 # Require records in auth_permission to specify access 4724 # (default Web2Py-style) 4725 authorised = self.has_permission(method, table, record_id) 4726 else: 4727 # No access for anonymous 4728 authorised = False 4729 4730 return authorised
4731 4732 # -------------------------------------------------------------------------
4733 - def s3_accessible_query(self, method, table, c=None, f=None):
4734 """ 4735 Returns a query with all accessible records for the currently 4736 logged-in user 4737 4738 @param method: the access method as string, one of: 4739 "create", "read", "update" or "delete" 4740 @param table: the table or table name 4741 @param c: the controller name (overrides current.request) 4742 @param f: the function name (overrides current.request) 4743 4744 @note: This method does not work on GAE because it uses JOIN and IN 4745 """ 4746 4747 if self.override: 4748 return table.id > 0 4749 4750 sr = self.get_system_roles() 4751 4752 if not hasattr(table, "_tablename"): 4753 table = current.s3db[table] 4754 4755 policy = current.deployment_settings.get_security_policy() 4756 4757 if policy == 1: 4758 # "simple" security policy: show all records 4759 return table.id > 0 4760 elif policy == 2: 4761 # "editor" security policy: show all records 4762 return table.id > 0 4763 elif policy in (3, 4, 5, 6, 7, 8): 4764 # ACLs: use S3Permission method 4765 query = self.permission.accessible_query(method, table, c=c, f=f) 4766 return query 4767 4768 # "Full" security policy 4769 if self.s3_has_role(sr.ADMIN): 4770 # Administrators can see all data 4771 return table.id > 0 4772 4773 # If there is access to the entire table then show all records 4774 try: 4775 user_id = self.user.id 4776 except: 4777 user_id = 0 4778 if self.has_permission(method, table, 0, user_id): 4779 return table.id > 0 4780 4781 # Filter Records to show only those to which the user has access 4782 current.session.warning = current.T("Only showing accessible records!") 4783 membership = self.settings.table_membership 4784 permission = self.settings.table_permission 4785 query = (membership.user_id == user_id) & \ 4786 (membership.group_id == permission.group_id) & \ 4787 (permission.name == method) & \ 4788 (permission.table_name == table) 4789 return table.id.belongs(current.db(query)._select(permission.record_id))
4790 4791 # ------------------------------------------------------------------------- 4792 # S3 Variants of web2py Authorization Methods 4793 # -------------------------------------------------------------------------
4794 - def s3_has_membership(self, group_id=None, user_id=None, role=None):
4795 """ 4796 Checks if user is member of group_id or role 4797 4798 Extends Web2Py's requires_membership() to add new functionality: 4799 - Custom Flash style 4800 - Uses s3_has_role() 4801 """ 4802 4803 # Allow override 4804 if self.override: 4805 return True 4806 4807 group_id = group_id or self.id_group(role) 4808 try: 4809 group_id = int(group_id) 4810 except: 4811 group_id = self.id_group(group_id) # interpret group_id as a role 4812 4813 if self.s3_has_role(group_id): 4814 r = True 4815 else: 4816 r = False 4817 4818 log = self.messages.has_membership_log 4819 if log: 4820 if not user_id and self.user: 4821 user_id = self.user.id 4822 self.log_event(log, dict(user_id=user_id, 4823 group_id=group_id, check=r)) 4824 return r
4825 4826 # Override original method 4827 has_membership = s3_has_membership 4828 4829 # -------------------------------------------------------------------------
4830 - def s3_requires_membership(self, role):
4831 """ 4832 Decorator that prevents access to action if not logged in or 4833 if user logged in is not a member of group_id. If role is 4834 provided instead of group_id then the group_id is calculated. 4835 4836 Extends Web2Py's requires_membership() to add new functionality: 4837 - Custom Flash style 4838 - Uses s3_has_role() 4839 - Administrators (id=1) are deemed to have all roles 4840 """ 4841 4842 def decorator(action): 4843 4844 def f(*a, **b): 4845 4846 if self.override: 4847 return action(*a, **b) 4848 4849 ADMIN = self.get_system_roles().ADMIN 4850 if not self.s3_has_role(role) and not self.s3_has_role(ADMIN): 4851 self.permission.fail() 4852 4853 return action(*a, **b)
4854 4855 f.__doc__ = action.__doc__ 4856 4857 return f 4858 4859 return decorator 4860 4861 # Override original method 4862 requires_membership = s3_requires_membership 4863 4864 # ------------------------------------------------------------------------- 4865 # Record Ownership 4866 # -------------------------------------------------------------------------
4867 - def s3_make_session_owner(self, table, record_id):
4868 """ 4869 Makes the current session owner of a record 4870 4871 @param table: the table or table name 4872 @param record_id: the record ID 4873 """ 4874 4875 if hasattr(table, "_tablename"): 4876 tablename = original_tablename(table) 4877 else: 4878 tablename = table 4879 4880 if not self.user: 4881 4882 session = current.session 4883 if "owned_records" not in session: 4884 session.owned_records = {} 4885 4886 records = session.owned_records.get(tablename, []) 4887 record_id = str(record_id) 4888 if record_id not in records: 4889 records.append(record_id) 4890 4891 session.owned_records[tablename] = records
4892 4893 # -------------------------------------------------------------------------
4894 - def s3_session_owns(self, table, record_id):
4895 """ 4896 Checks whether the current session owns a record 4897 4898 @param table: the table or table name 4899 @param record_id: the record ID 4900 """ 4901 4902 session = current.session 4903 if self.user or not record_id or "owned_records" not in session: 4904 return False 4905 4906 if hasattr(table, "_tablename"): 4907 tablename = original_tablename(table) 4908 else: 4909 tablename = table 4910 4911 records = session.owned_records.get(tablename) 4912 if records: 4913 return str(record_id) in records 4914 4915 return False
4916 4917 # -------------------------------------------------------------------------
4918 - def s3_clear_session_ownership(self, table=None, record_id=None):
4919 """ 4920 Removes session ownership for a record 4921 4922 @param table: the table or table name (default: all tables) 4923 @param record_id: the record ID (default: all records) 4924 """ 4925 4926 session = current.session 4927 if "owned_records" not in session: 4928 return 4929 4930 if table is not None: 4931 4932 if hasattr(table, "_tablename"): 4933 tablename = original_tablename(table) 4934 else: 4935 tablename = table 4936 4937 if tablename in session.owned_records: 4938 4939 if record_id: 4940 # Remove just this record ID 4941 record_id = str(record_id) 4942 records = session.owned_records[tablename] 4943 if record_id in records: 4944 records.remove(record_id) 4945 else: 4946 # Remove all record IDs for this table 4947 del session.owned_records[tablename] 4948 else: 4949 # Remove all session ownerships 4950 session.owned_records = {}
4951 4952 # -------------------------------------------------------------------------
4953 - def s3_update_record_owner(self, table, record, update=False, **fields):
4954 """ 4955 Update ownership fields in a record (DRY helper method for 4956 s3_set_record_owner and set_realm_entity) 4957 4958 @param table: the table 4959 @param record: the record or record ID 4960 @param update: True to update realm_entity in all realm-components 4961 @param fields: dict of {ownership_field:value} 4962 """ 4963 4964 # Ownership fields 4965 OUSR = "owned_by_user" 4966 OGRP = "owned_by_group" 4967 REALM = "realm_entity" 4968 4969 ownership_fields = (OUSR, OGRP, REALM) 4970 4971 pkey = table._id.name 4972 if isinstance(record, (Row, dict)) and pkey in record: 4973 record_id = record[pkey] 4974 else: 4975 record_id = record 4976 4977 data = dict((key, fields[key]) for key in fields 4978 if key in ownership_fields) 4979 if not data: 4980 return 4981 4982 db = current.db 4983 4984 # Update record 4985 q = (table._id == record_id) 4986 success = db(q).update(**data) 4987 4988 if success and update and REALM in data: 4989 4990 # Update realm-components 4991 # Only goes down 1 level: doesn't do components of components 4992 s3db = current.s3db 4993 realm_components = s3db.get_config(table, "realm_components") 4994 4995 if realm_components: 4996 resource = s3db.resource(table, 4997 components = realm_components, 4998 ) 4999 components = resource.components 5000 realm = {REALM: data[REALM]} 5001 for alias in realm_components: 5002 component = components.get(alias) 5003 if not component: 5004 continue 5005 ctable = component.table 5006 if REALM not in ctable.fields: 5007 continue 5008 query = component.get_join() & q 5009 rows = db(query).select(ctable._id) 5010 ids = set(row[ctable._id] for row in rows) 5011 if ids: 5012 ctablename = component.tablename 5013 if ctable._tablename != ctablename: 5014 # Component with table alias => switch to 5015 # original table for update: 5016 ctable = db[ctablename] 5017 db(ctable._id.belongs(ids)).update(**realm) 5018 5019 # Update super-entity 5020 self.update_shared_fields(table, record, **data)
5021 5022 # -------------------------------------------------------------------------
5023 - def s3_set_record_owner(self, 5024 table, 5025 record, 5026 force_update=False, 5027 **fields):
5028 """ 5029 Set the record owned_by_user, owned_by_group and realm_entity 5030 for a record (auto-detect values). 5031 5032 To be called by CRUD and Importer during record creation. 5033 5034 @param table: the Table (or table name) 5035 @param record: the record (or record ID) 5036 @param force_update: True to update all fields regardless of 5037 the current value in the record, False 5038 to only update if current value is None 5039 @param fields: override auto-detected values, see keywords 5040 @keyword owned_by_user: the auth_user ID of the owner user 5041 @keyword owned_by_group: the auth_group ID of the owner group 5042 @keyword realm_entity: the pe_id of the realm entity, or a tuple 5043 (instance_type, instance_id) to lookup the 5044 pe_id, e.g. ("org_organisation", 2) 5045 5046 @note: Only use with force_update for deliberate owner changes (i.e. 5047 with explicit owned_by_user/owned_by_group) - autodetected 5048 values can have undesirable side-effects. For mere realm 5049 updates use set_realm_entity instead. 5050 5051 @note: If used with force_update, this will also update the 5052 realm_entity in all configured realm_components, i.e. 5053 no separate call to set_realm_entity required. 5054 """ 5055 5056 s3db = current.s3db 5057 5058 # Ownership fields 5059 OUSR = "owned_by_user" 5060 OGRP = "owned_by_group" 5061 REALM = "realm_entity" 5062 5063 ownership_fields = (OUSR, OGRP, REALM) 5064 5065 # Entity reference fields 5066 EID = "pe_id" 5067 OID = "organisation_id" 5068 SID = "site_id" 5069 GID = "group_id" 5070 PID = "person_id" 5071 entity_fields = (EID, OID, SID, GID, PID) 5072 5073 # Find the table 5074 if hasattr(table, "_tablename"): 5075 tablename = original_tablename(table) 5076 else: 5077 tablename = table 5078 table = s3db.table(tablename) 5079 if not table: 5080 return 5081 5082 # Get the record ID 5083 pkey = table._id.name 5084 if isinstance(record, (Row, dict)): 5085 if pkey not in record: 5086 return 5087 else: 5088 record_id = record[pkey] 5089 else: 5090 record_id = record 5091 record = Storage() 5092 5093 # Find the available fields 5094 fields_in_table = [f for f in ownership_fields if f in table.fields] 5095 if not fields_in_table: 5096 return 5097 fields_in_table += [f for f in entity_fields if f in table.fields] 5098 5099 # Get all available fields for the record 5100 fields_missing = [f for f in fields_in_table if f not in record] 5101 if fields_missing: 5102 fields_to_load = [table._id] + [table[f] for f in fields_in_table] 5103 query = (table._id == record_id) 5104 row = current.db(query).select(limitby=(0, 1), 5105 *fields_to_load).first() 5106 else: 5107 row = record 5108 if not row: 5109 return 5110 5111 # Prepare the update 5112 data = Storage() 5113 5114 # Find owned_by_user 5115 if OUSR in fields_in_table: 5116 pi = ("pr_person", 5117 "pr_identity", 5118 "pr_education", 5119 "pr_contact", 5120 "pr_address", 5121 "pr_contact_emergency", 5122 "pr_physical_description", 5123 "pr_group_membership", 5124 "pr_image", 5125 "hrm_training", 5126 ) 5127 if OUSR in fields: 5128 data[OUSR] = fields[OUSR] 5129 elif not row[OUSR] or tablename in pi: 5130 user_id = None 5131 # Records in PI tables should be owned by the person 5132 # they refer to (if that person has a user account) 5133 if tablename == "pr_person": 5134 user_id = self.s3_get_user_id(person_id = row[table._id]) 5135 elif PID in row and tablename in pi: 5136 user_id = self.s3_get_user_id(person_id = row[PID]) 5137 elif EID in row and tablename in pi: 5138 user_id = self.s3_get_user_id(pe_id = row[EID]) 5139 if not user_id and self.s3_logged_in() and self.user: 5140 # Fallback to current user 5141 user_id = self.user.id 5142 if user_id: 5143 data[OUSR] = user_id 5144 5145 # Find owned_by_group 5146 if OGRP in fields_in_table: 5147 # Check for type-specific handler to find the owner group 5148 handler = s3db.get_config(tablename, "owner_group") 5149 if handler: 5150 if callable(handler): 5151 data[OGRP] = handler(table, row) 5152 else: 5153 data[OGRP] = handler 5154 # Otherwise, only set if explicitly specified 5155 elif OGRP in fields: 5156 data[OGRP] = fields[OGRP] 5157 5158 # Find realm entity 5159 if REALM in fields_in_table: 5160 if REALM in row and row[REALM] and not force_update: 5161 pass 5162 else: 5163 if REALM in fields: 5164 entity = fields[REALM] 5165 else: 5166 entity = 0 5167 realm_entity = self.get_realm_entity(table, row, 5168 entity=entity) 5169 data[REALM] = realm_entity 5170 5171 self.s3_update_record_owner(table, row, update=force_update, **data)
5172 5173 # -------------------------------------------------------------------------
5174 - def set_realm_entity(self, table, records, entity=0, force_update=False):
5175 """ 5176 Update the realm entity for records, will also update the 5177 realm in all configured realm-entities, see: 5178 5179 http://eden.sahanafoundation.org/wiki/S3AAA/OrgAuth#Realms1 5180 5181 To be called by CRUD and Importer during record update. 5182 5183 @param table: the Table (or tablename) 5184 @param records: - a single record 5185 - a single record ID 5186 - a list of records, or a Rows object 5187 - a list of record IDs 5188 - a query to find records in table 5189 @param entity: - an entity ID 5190 - a tuple (table, instance_id) 5191 - 0 for default lookup 5192 """ 5193 5194 db = current.db 5195 s3db = current.s3db 5196 5197 REALM = "realm_entity" 5198 5199 EID = "pe_id" 5200 OID = "organisation_id" 5201 SID = "site_id" 5202 GID = "group_id" 5203 entity_fields = (EID, OID, SID, GID) 5204 5205 # Find the table 5206 if hasattr(table, "_tablename"): 5207 tablename = original_tablename(table) 5208 else: 5209 tablename = table 5210 table = s3db.table(tablename) 5211 if not table or REALM not in table.fields: 5212 return 5213 5214 # Find the available fields 5215 fields_in_table = [table._id.name, REALM] + \ 5216 [f for f in entity_fields if f in table.fields] 5217 fields_to_load = [table[f] for f in fields_in_table] 5218 5219 # Realm entity specified by call? 5220 realm_entity = entity 5221 if isinstance(realm_entity, tuple): 5222 realm_entity = s3db.pr_get_pe_id(realm_entity) 5223 if not realm_entity: 5224 return 5225 5226 if isinstance(records, Query): 5227 query = records 5228 else: 5229 query = None 5230 5231 # Bulk update? 5232 if realm_entity != 0 and force_update and query is not None: 5233 data = {REALM:realm_entity} 5234 db(query).update(**data) 5235 self.update_shared_fields(table, query, **data) 5236 return 5237 5238 # Find the records 5239 if query is not None: 5240 if not force_update: 5241 query &= (table[REALM] == None) 5242 records = db(query).select(*fields_to_load) 5243 elif not isinstance(records, (list, Rows)): 5244 records = [records] 5245 if not records: 5246 return 5247 5248 # Update record by record 5249 get_realm_entity = self.get_realm_entity 5250 s3_update_record_owner = self.s3_update_record_owner 5251 for record in records: 5252 5253 if not isinstance(record, (Row, Storage)): 5254 record_id = record 5255 row = Storage() 5256 else: 5257 row = record 5258 if table._id.name not in record: 5259 continue 5260 record_id = row[table._id.name] 5261 q = (table._id == record_id) 5262 5263 # Do we need to reload the record? 5264 fields_missing = [f for f in fields_in_table if f not in row] 5265 if fields_missing: 5266 row = db(q).select(*fields_to_load, limitby = (0, 1)).first() 5267 if not row: 5268 continue 5269 5270 # Do we need to update the record at all? 5271 if row[REALM] and not force_update: 5272 continue 5273 5274 _realm_entity = get_realm_entity(table, row, 5275 entity=realm_entity) 5276 data = {REALM:_realm_entity} 5277 s3_update_record_owner(table, row, 5278 update=force_update, **data) 5279 5280 return
5281 5282 # -------------------------------------------------------------------------
5283 - def get_realm_entity(self, table, record, entity=0):
5284 """ 5285 Lookup the realm entity for a record 5286 5287 @param table: the Table 5288 @param record: the record (as Row or dict) 5289 @param entity: the entity (pe_id) 5290 """ 5291 5292 if "realm_entity" not in table: 5293 return None 5294 5295 s3db = current.s3db 5296 5297 # Entity specified by call? 5298 if isinstance(entity, tuple): 5299 realm_entity = s3db.pr_get_pe_id(entity) 5300 else: 5301 realm_entity = entity 5302 5303 # See if there is a deployment-global method to determine the realm entity 5304 if realm_entity == 0: 5305 handler = current.deployment_settings.get_auth_realm_entity() 5306 if callable(handler): 5307 realm_entity = handler(table, record) 5308 5309 # Fall back to table-specific method 5310 if realm_entity == 0: 5311 handler = s3db.get_config(table, "realm_entity") 5312 if callable(handler): 5313 realm_entity = handler(table, record) 5314 5315 # Fall back to standard lookup cascade 5316 if realm_entity == 0: 5317 tablename = original_tablename(table) 5318 if "pe_id" in record and \ 5319 tablename not in ("pr_person", "dvi_body"): 5320 realm_entity = record["pe_id"] 5321 elif "organisation_id" in record: 5322 realm_entity = s3db.pr_get_pe_id("org_organisation", 5323 record["organisation_id"]) 5324 elif "site_id" in record: 5325 realm_entity = s3db.pr_get_pe_id("org_site", 5326 record["site_id"]) 5327 elif "group_id" in record: 5328 realm_entity = s3db.pr_get_pe_id("pr_group", 5329 record["group_id"]) 5330 else: 5331 realm_entity = None 5332 5333 return realm_entity
5334 5335 # -------------------------------------------------------------------------
5336 - def update_shared_fields(self, table, record, **data):
5337 """ 5338 Update the shared fields in data in all super-entity rows linked 5339 with this record. 5340 5341 @param table: the table 5342 @param record: a record, record ID or a query 5343 @param data: the field/value pairs to update 5344 """ 5345 5346 db = current.db 5347 s3db = current.s3db 5348 5349 super_entities = s3db.get_config(table, "super_entity") 5350 if not super_entities: 5351 return 5352 if not isinstance(super_entities, (list, tuple)): 5353 super_entities = [super_entities] 5354 5355 tables = dict() 5356 load = s3db.table 5357 super_key = s3db.super_key 5358 for se in super_entities: 5359 supertable = load(se) 5360 if not supertable or \ 5361 not any([f in supertable.fields for f in data]): 5362 continue 5363 tables[super_key(supertable)] = supertable 5364 5365 if not isinstance(record, (Row, dict)) or \ 5366 any([f not in record for f in tables]): 5367 if isinstance(record, Query): 5368 query = record 5369 limitby = None 5370 elif isinstance(record, (Row, dict)): 5371 query = table._id == record[table._id.name] 5372 limitby = (0, 1) 5373 else: 5374 query = table._id == record 5375 limitby = (0, 1) 5376 fields = [table[f] for f in tables] 5377 records = db(query).select(limitby=limitby, *fields) 5378 else: 5379 records = [record] 5380 if not records: 5381 return 5382 5383 for record in records: 5384 for skey in tables: 5385 supertable = tables[skey] 5386 if skey in record: 5387 query = (supertable[skey] == record[skey]) 5388 else: 5389 continue 5390 updates = dict((f, data[f]) 5391 for f in data if f in supertable.fields) 5392 if not updates: 5393 continue 5394 db(query).update(**updates)
5395 5396 # -------------------------------------------------------------------------
5397 - def permitted_facilities(self, 5398 table=None, 5399 error_msg=None, 5400 redirect_on_error=True, 5401 facility_type=None):
5402 """ 5403 If there are no facilities that the user has permission for, 5404 prevents create & update of records in table & gives a 5405 warning if the user tries to. 5406 5407 @param table: the table or table name 5408 @param error_msg: error message 5409 @param redirect_on_error: whether to redirect on error 5410 @param facility_type: restrict to this particular type of 5411 facilities (a tablename) 5412 """ 5413 5414 T = current.T 5415 ERROR = T("You do not have permission for any facility to perform this action.") 5416 HINT = T("Create a new facility or ensure that you have permissions for an existing facility.") 5417 5418 if not error_msg: 5419 error_msg = ERROR 5420 5421 s3db = current.s3db 5422 site_ids = [] 5423 if facility_type is None: 5424 site_types = self.org_site_types 5425 else: 5426 if facility_type not in self.org_site_types: 5427 return 5428 site_types = [s3db[facility_type]] 5429 for site_type in site_types: 5430 try: 5431 ftable = s3db[site_type] 5432 if not "site_id" in ftable.fields: 5433 continue 5434 query = self.s3_accessible_query("update", ftable) 5435 if "deleted" in ftable: 5436 query &= (ftable.deleted != True) 5437 rows = current.db(query).select(ftable.site_id) 5438 site_ids += [row.site_id for row in rows] 5439 except: 5440 # Module disabled 5441 pass 5442 5443 if site_ids: 5444 return site_ids 5445 5446 args = current.request.args 5447 if "update" in args or "create" in args: 5448 if redirect_on_error: 5449 # Trying to create or update 5450 # If they do no have permission to any facilities 5451 current.session.error = "%s %s" % (error_msg, HINT) 5452 redirect(URL(c="default", f="index")) 5453 elif table is not None: 5454 if hasattr(table, "_tablename"): 5455 tablename = original_tablename(table) 5456 else: 5457 tablename = table 5458 s3db.configure(tablename, insertable=False) 5459 5460 return site_ids # Will be []
5461 5462 # -------------------------------------------------------------------------
5463 - def permitted_organisations(self, 5464 table=None, 5465 error_msg=None, 5466 redirect_on_error=True):
5467 """ 5468 If there are no organisations that the user has update 5469 permission for, prevents create & update of a record in 5470 table & gives an warning if the user tries to. 5471 5472 @param table: the table or table name 5473 @param error_msg: error message 5474 @param redirect_on_error: whether to redirect on error 5475 """ 5476 5477 T = current.T 5478 ERROR = T("You do not have permission for any organization to perform this action.") 5479 HINT = T("Create a new organization or ensure that you have permissions for an existing organization.") 5480 5481 if not error_msg: 5482 error_msg = ERROR 5483 5484 s3db = current.s3db 5485 org_table = s3db.org_organisation 5486 query = self.s3_accessible_query("update", org_table) 5487 query &= (org_table.deleted == False) 5488 rows = current.db(query).select(org_table.id) 5489 if rows: 5490 return [org.id for org in rows] 5491 request = current.request 5492 if "update" in request.args or "create" in request.args: 5493 if redirect_on_error: 5494 current.session.error = error_msg + " " + HINT 5495 redirect(URL(c="default", f="index")) 5496 elif table is not None: 5497 if hasattr(table, "_tablename"): 5498 tablename = original_tablename(table) 5499 else: 5500 tablename = table 5501 s3db.configure(tablename, insertable=False) 5502 5503 return []
5504 5505 # -------------------------------------------------------------------------
5506 - def root_org(self):
5507 """ 5508 Return the current user's root organisation ID or None 5509 """ 5510 5511 if not self.user: 5512 return None 5513 org_id = self.user.organisation_id 5514 if not org_id: 5515 return None 5516 if not current.deployment_settings.get_org_branches(): 5517 return org_id 5518 return current.cache.ram( 5519 # Common key for all users of this org & vol_service_record() & hrm_training_event_realm_entity() 5520 "root_org_%s" % org_id, 5521 lambda: current.s3db.org_root_organisation(org_id), 5522 time_expire=120 5523 )
5524 5525 # -------------------------------------------------------------------------
5526 - def root_org_name(self):
5527 """ 5528 Return the current user's root organisation name or None 5529 """ 5530 5531 if not self.user: 5532 return None 5533 org_id = self.user.organisation_id 5534 if not org_id: 5535 return None 5536 if not current.deployment_settings.get_org_branches(): 5537 s3db = current.s3db 5538 table = s3db.org_organisation 5539 row = current.db(table.id == org_id).select(table.name, 5540 cache = s3db.cache, 5541 limitby=(0, 1)).first() 5542 try: 5543 return row.name 5544 except: 5545 # Org not found! 5546 return None 5547 return current.cache.ram( 5548 # Common key for all users of this org 5549 "root_org_name_%s" % org_id, 5550 lambda: current.s3db.org_root_organisation_name(org_id), 5551 time_expire=120 5552 )
5553 5554 # -------------------------------------------------------------------------
5555 - def filter_by_root_org(self, table):
5556 """ 5557 Function to return a query to filter a table to only display results 5558 for the user's root org OR record with no root org 5559 @ToDo: Restore Realms and add a role/functionality support for Master Data 5560 Then this function is redundant 5561 """ 5562 5563 root_org = self.root_org() 5564 if root_org: 5565 return (table.organisation_id == root_org) | (table.organisation_id == None) 5566 else: 5567 return (table.organisation_id == None)
5568
5569 # ============================================================================= 5570 -class S3Permission(object):
5571 """ S3 Class to handle permissions """ 5572 5573 TABLENAME = "s3_permission" 5574 5575 CREATE = 0x0001 # Permission to create new records 5576 READ = 0x0002 # Permission to read records 5577 UPDATE = 0x0004 # Permission to update records 5578 DELETE = 0x0008 # Permission to delete records 5579 REVIEW = 0x0010 # Permission to review unapproved records 5580 APPROVE = 0x0020 # Permission to approve records 5581 PUBLISH = 0x0040 # Permission to publish records outside of Eden 5582 5583 ALL = CREATE | READ | UPDATE | DELETE | REVIEW | APPROVE | PUBLISH 5584 NONE = 0x0000 # must be 0! 5585 5586 PERMISSION_OPTS = OrderedDict([ 5587 [CREATE, "CREATE"], 5588 [READ, "READ"], 5589 [UPDATE, "UPDATE"], 5590 [DELETE, "DELETE"], 5591 [REVIEW, "REVIEW"], 5592 [APPROVE, "APPROVE"], 5593 #[PUBLISH, "PUBLISH"], # currently unused 5594 ]) 5595 5596 # Method <-> required permission 5597 METHODS = Storage({ 5598 "create": CREATE, 5599 "read": READ, 5600 "update": UPDATE, 5601 "delete": DELETE, 5602 "map": READ, 5603 "report": READ, 5604 #"search": READ, 5605 "timeplot": READ, 5606 "import": CREATE, 5607 "review": REVIEW, 5608 "approve": APPROVE, 5609 "reject": APPROVE, 5610 "publish": PUBLISH, 5611 }) 5612 5613 # Lambda expressions for ACL handling 5614 required_acl = lambda self, methods: \ 5615 reduce(lambda a, b: a | b, 5616 [self.METHODS[m] 5617 for m in methods if m in self.METHODS], 5618 self.NONE) 5619 most_permissive = lambda self, acl: \ 5620 reduce(lambda x, y: (x[0]|y[0], x[1]|y[1]), 5621 acl, (self.NONE, self.NONE)) 5622 most_restrictive = lambda self, acl: \ 5623 reduce(lambda x, y: (x[0]&y[0], x[1]&y[1]), 5624 acl, (self.ALL, self.ALL)) 5625 5626 # -------------------------------------------------------------------------
5627 - def __init__(self, auth, tablename=None):
5628 """ 5629 Constructor, invoked by AuthS3.__init__ 5630 5631 @param auth: the AuthS3 instance 5632 @param tablename: the name for the permissions table 5633 """ 5634 5635 db = current.db 5636 5637 # Instantiated once per request, but before Auth tables 5638 # are defined and authentication is checked, thus no use 5639 # to check permissions in the constructor 5640 5641 # Store auth reference in self because current.auth is not 5642 # available at this point yet, but needed in define_table. 5643 self.auth = auth 5644 5645 self.error = S3PermissionError 5646 5647 settings = current.deployment_settings 5648 5649 # Policy: which level of granularity do we want? 5650 self.policy = settings.get_security_policy() 5651 # ACLs to control access per controller: 5652 self.use_cacls = self.policy in (3, 4, 5, 6, 7 ,8) 5653 # ACLs to control access per function within controllers: 5654 self.use_facls = self.policy in (4, 5, 6, 7, 8) 5655 # ACLs to control access per table: 5656 self.use_tacls = self.policy in (5, 6, 7, 8) 5657 # Authorization takes realm entity into account: 5658 self.entity_realm = self.policy in (6, 7, 8) 5659 # Permissions shared along the hierarchy of entities: 5660 self.entity_hierarchy = self.policy in (7, 8) 5661 # Permission sets can be delegated: 5662 self.delegations = self.policy == 8 5663 5664 # Permissions table 5665 self.tablename = tablename or self.TABLENAME 5666 if self.tablename in db: 5667 self.table = db[self.tablename] 5668 else: 5669 self.table = None 5670 5671 # Error messages 5672 T = current.T 5673 self.INSUFFICIENT_PRIVILEGES = T("Insufficient Privileges") 5674 self.AUTHENTICATION_REQUIRED = T("Authentication Required") 5675 5676 # Request information 5677 request = current.request 5678 self.controller = request.controller 5679 self.function = request.function 5680 5681 # Request format 5682 self.format = s3_get_extension() 5683 5684 # Settings 5685 self.record_approval = settings.get_auth_record_approval() 5686 self.strict_ownership = settings.get_security_strict_ownership() 5687 5688 # Clear cache 5689 self.clear_cache() 5690 5691 # Pages which never require permission: 5692 # Make sure that any data access via these pages uses 5693 # accessible_query explicitly! 5694 self.unrestricted_pages = ("default/index", 5695 "default/user", 5696 "default/contact", 5697 "default/about") 5698 5699 # Default landing pages 5700 _next = URL(args=request.args, vars=request.get_vars) 5701 self.homepage = URL(c="default", f="index") 5702 self.loginpage = URL(c="default", f="user", args="login", 5703 vars=dict(_next=_next))
5704 5705 # -------------------------------------------------------------------------
5706 - def clear_cache(self):
5707 """ Clear any cached permissions or accessible-queries """ 5708 5709 self.permission_cache = {} 5710 self.query_cache = {}
5711 5712 # -------------------------------------------------------------------------
5713 - def check_settings(self):
5714 """ 5715 Check whether permission-relevant settings have changed 5716 during the request, and clear the cache if so. 5717 """ 5718 5719 clear_cache = False 5720 settings = current.deployment_settings 5721 5722 record_approval = settings.get_auth_record_approval() 5723 if record_approval != self.record_approval: 5724 clear_cache = True 5725 self.record_approval = record_approval 5726 5727 strict_ownership = settings.get_security_strict_ownership() 5728 if strict_ownership != self.strict_ownership: 5729 clear_cache = True 5730 self.strict_ownership = strict_ownership 5731 5732 if clear_cache: 5733 self.clear_cache()
5734 5735 # -------------------------------------------------------------------------
5736 - def define_table(self, migrate=True, fake_migrate=False):
5737 """ 5738 Define permissions table, invoked by AuthS3.define_tables() 5739 """ 5740 5741 table_group = self.auth.settings.table_group 5742 if table_group is None: 5743 table_group = "integer" # fallback (doesn't work with requires) 5744 5745 if not self.table: 5746 db = current.db 5747 db.define_table(self.tablename, 5748 Field("group_id", table_group), 5749 Field("controller", length=64), 5750 Field("function", length=512), 5751 Field("tablename", length=512), 5752 Field("record", "integer"), 5753 Field("oacl", "integer", default=self.ALL), 5754 Field("uacl", "integer", default=self.READ), 5755 # apply this ACL only to records owned 5756 # by this entity 5757 Field("entity", "integer"), 5758 # apply this ACL to all records regardless 5759 # of the realm entity 5760 Field("unrestricted", "boolean", 5761 default=False), 5762 migrate=migrate, 5763 fake_migrate=fake_migrate, 5764 *S3MetaFields.sync_meta_fields()) 5765 self.table = db[self.tablename]
5766 5767 # ------------------------------------------------------------------------- 5768 # ACL Management 5769 # -------------------------------------------------------------------------
5770 - def update_acl(self, group, 5771 c=None, 5772 f=None, 5773 t=None, 5774 record=None, 5775 oacl=None, 5776 uacl=None, 5777 entity=None, 5778 delete=False):
5779 """ 5780 Update an ACL 5781 5782 @param group: the ID or UID of the auth_group this ACL applies to 5783 @param c: the controller 5784 @param f: the function 5785 @param t: the tablename 5786 @param record: the record (as ID or Row with ID) 5787 @param oacl: the ACL for the owners of the specified record(s) 5788 @param uacl: the ACL for all other users 5789 @param entity: restrict this ACL to the records owned by this 5790 entity (pe_id), specify "any" for any entity 5791 @param delete: delete the ACL instead of updating it 5792 """ 5793 5794 ANY = "any" 5795 5796 unrestricted = entity == ANY 5797 if unrestricted: 5798 entity = None 5799 5800 table = self.table 5801 if not table: 5802 # ACLs not relevant to this security policy 5803 return None 5804 5805 s3 = current.response.s3 5806 if "restricted_tables" in s3: 5807 del s3["restricted_tables"] 5808 self.clear_cache() 5809 5810 if c is None and f is None and t is None: 5811 return None 5812 if t is not None: 5813 c = f = None 5814 else: 5815 record = None 5816 5817 if uacl is None: 5818 uacl = self.NONE 5819 if oacl is None: 5820 oacl = uacl 5821 5822 success = False 5823 if group: 5824 group_id = None 5825 acl = dict(group_id=group_id, 5826 deleted=False, 5827 controller=c, 5828 function=f, 5829 tablename=t, 5830 record=record, 5831 oacl=oacl, 5832 uacl=uacl, 5833 unrestricted=unrestricted, 5834 entity=entity) 5835 5836 if isinstance(group, basestring) and not group.isdigit(): 5837 gtable = self.auth.settings.table_group 5838 query = (gtable.uuid == group) & \ 5839 (table.group_id == gtable.id) 5840 else: 5841 query = (table.group_id == group) 5842 group_id = group 5843 5844 query &= ((table.controller == c) & \ 5845 (table.function == f) & \ 5846 (table.tablename == t) & \ 5847 (table.record == record) & \ 5848 (table.unrestricted == unrestricted) & \ 5849 (table.entity == entity)) 5850 record = current.db(query).select(table.id, 5851 table.group_id, 5852 limitby=(0, 1)).first() 5853 if record: 5854 if delete: 5855 acl = dict( 5856 group_id = None, 5857 deleted = True, 5858 deleted_fk = '{"group_id": %d}' % record.group_id 5859 ) 5860 else: 5861 acl["group_id"] = record.group_id 5862 record.update_record(**acl) 5863 success = record.id 5864 elif group_id: 5865 acl["group_id"] = group_id 5866 success = table.insert(**acl) 5867 else: 5868 # Lookup the group_id 5869 record = current.db(gtable.uuid == group).select(gtable.id, 5870 limitby=(0, 1) 5871 ).first() 5872 if record: 5873 acl["group_id"] = group_id 5874 success = table.insert(**acl) 5875 5876 return success
5877 5878 # -------------------------------------------------------------------------
5879 - def delete_acl(self, group, 5880 c=None, 5881 f=None, 5882 t=None, 5883 record=None, 5884 entity=None):
5885 """ 5886 Delete an ACL 5887 @param group: the ID or UID of the auth_group this ACL applies to 5888 @param c: the controller 5889 @param f: the function 5890 @param t: the tablename 5891 @param record: the record (as ID or Row with ID) 5892 @param entity: restrict this ACL to the records owned by this 5893 entity (pe_id), specify "any" for any entity 5894 """ 5895 5896 return self.update_acl(group, 5897 c=c, 5898 f=f, 5899 t=t, 5900 record=record, 5901 entity=entity, 5902 delete=True)
5903 5904 # ------------------------------------------------------------------------- 5905 # Record Ownership 5906 # -------------------------------------------------------------------------
5907 - def get_owners(self, table, record):
5908 """ 5909 Get the entity/group/user owning a record 5910 5911 @param table: the table 5912 @param record: the record ID (or the Row, if already loaded) 5913 5914 @note: if passing a Row, it must contain all available ownership 5915 fields (id, owned_by_user, owned_by_group, realm_entity), 5916 otherwise the record will be re-loaded by this function. 5917 5918 @return: tuple of (realm_entity, owner_group, owner_user) 5919 """ 5920 5921 realm_entity = None 5922 owner_group = None 5923 owner_user = None 5924 5925 record_id = None 5926 5927 DEFAULT = (None, None, None) 5928 5929 # Load the table, if necessary 5930 if table and not hasattr(table, "_tablename"): 5931 table = current.s3db.table(table) 5932 if not table: 5933 return DEFAULT 5934 5935 # Check which ownership fields the table defines 5936 ownership_fields = ("realm_entity", 5937 "owned_by_group", 5938 "owned_by_user") 5939 fields = [f for f in ownership_fields if f in table.fields] 5940 if not fields: 5941 # Ownership is not defined for this table 5942 return DEFAULT 5943 5944 if isinstance(record, Row): 5945 # Check if all necessary fields are present 5946 missing = [f for f in fields if f not in record] 5947 if missing: 5948 # Have to reload the record :( 5949 if table._id.name in record: 5950 record_id = record[table._id.name] 5951 record = None 5952 else: 5953 # Record ID given, must load the record anyway 5954 record_id = record 5955 record = None 5956 5957 if not record and record_id: 5958 # Get the record 5959 fs = [table[f] for f in fields] + [table.id] 5960 query = (table._id == record_id) 5961 record = current.db(query).select(limitby=(0, 1), *fs).first() 5962 if not record: 5963 # Record does not exist 5964 return DEFAULT 5965 5966 if "realm_entity" in record: 5967 realm_entity = record["realm_entity"] 5968 if "owned_by_group" in record: 5969 owner_group = record["owned_by_group"] 5970 if "owned_by_user" in record: 5971 owner_user = record["owned_by_user"] 5972 return (realm_entity, owner_group, owner_user)
5973 5974 # -------------------------------------------------------------------------
5975 - def is_owner(self, table, record, owners=None, strict=False):
5976 """ 5977 Check whether the current user owns the record 5978 5979 @param table: the table or tablename 5980 @param record: the record ID (or the Row if already loaded) 5981 @param owners: override the actual record owners by a tuple 5982 (realm_entity, owner_group, owner_user) 5983 5984 @return: True if the current user owns the record, else False 5985 """ 5986 5987 auth = self.auth 5988 user_id = None 5989 sr = auth.get_system_roles() 5990 5991 if auth.user is not None: 5992 user_id = auth.user.id 5993 5994 session = current.session 5995 roles = [sr.ANONYMOUS] 5996 if session.s3 is not None: 5997 roles = session.s3.roles or roles 5998 5999 if sr.ADMIN in roles: 6000 # Admin owns all records 6001 return True 6002 elif owners is not None: 6003 realm_entity, owner_group, owner_user = owners 6004 elif record: 6005 realm_entity, owner_group, owner_user = \ 6006 self.get_owners(table, record) 6007 else: 6008 # All users own no records 6009 return True 6010 6011 # Session ownership? 6012 if not user_id: 6013 if isinstance(record, (Row, dict)): 6014 record_id = record[table._id.name] 6015 else: 6016 record_id = record 6017 if auth.s3_session_owns(table, record_id): 6018 # Session owns record 6019 return True 6020 else: 6021 return False 6022 6023 # Individual record ownership 6024 if owner_user and owner_user == user_id: 6025 return True 6026 6027 # Public record? 6028 if not any((realm_entity, owner_group, owner_user)) and not strict: 6029 return True 6030 elif strict: 6031 return False 6032 6033 # OrgAuth: apply only group memberships within the realm 6034 if self.entity_realm and realm_entity: 6035 realms = auth.user.realms 6036 roles = [sr.ANONYMOUS] 6037 append = roles.append 6038 for r in realms: 6039 realm = realms[r] 6040 if realm is None or realm_entity in realm: 6041 append(r) 6042 6043 # Ownership based on user role 6044 if owner_group and owner_group in roles: 6045 return True 6046 else: 6047 return False
6048 6049 # -------------------------------------------------------------------------
6050 - def owner_query(self, 6051 table, 6052 user, 6053 use_realm=True, 6054 realm=None, 6055 no_realm=None):
6056 """ 6057 Returns a query to select the records in table owned by user 6058 6059 @param table: the table 6060 @param user: the current auth.user (None for not authenticated) 6061 @param use_realm: use realms 6062 @param realm: limit owner access to these realms 6063 @param no_realm: don't include these entities in role realms 6064 @return: a web2py Query instance, or None if no query can be 6065 constructed 6066 """ 6067 6068 OUSR = "owned_by_user" 6069 OGRP = "owned_by_group" 6070 OENT = "realm_entity" 6071 6072 if realm is None: 6073 realm = [] 6074 6075 no_realm = set() if no_realm is None else set(no_realm) 6076 6077 query = None 6078 if user is None: 6079 # Session ownership? 6080 if hasattr(table, "_tablename"): 6081 tablename = original_tablename(table) 6082 else: 6083 tablename = table 6084 session = current.session 6085 if "owned_records" in session and \ 6086 tablename in session.owned_records: 6087 query = (table._id.belongs(session.owned_records[tablename])) 6088 else: 6089 use_realm = use_realm and \ 6090 OENT in table.fields and self.entity_realm 6091 6092 # Individual owner query 6093 if OUSR in table.fields: 6094 user_id = user.id 6095 query = (table[OUSR] == user_id) 6096 if use_realm: 6097 # Limit owner access to permitted realms 6098 if realm: 6099 realm_query = self.realm_query(table, realm) 6100 if realm_query: 6101 query &= realm_query 6102 else: 6103 query = None 6104 6105 if not self.strict_ownership: 6106 6107 # Any authenticated user owns all records with no owner 6108 public = None 6109 if OUSR in table.fields: 6110 public = (table[OUSR] == None) 6111 if OGRP in table.fields: 6112 q = (table[OGRP] == None) 6113 if public: 6114 public &= q 6115 else: 6116 public = q 6117 if use_realm: 6118 q = (table[OENT] == None) 6119 if public: 6120 public &= q 6121 else: 6122 public = q 6123 6124 if public is not None: 6125 if query is not None: 6126 query |= public 6127 else: 6128 query = public 6129 6130 # Group ownerships 6131 if OGRP in table.fields: 6132 any_entity = set() 6133 g = None 6134 user_realms = user.realms 6135 for group_id in user_realms: 6136 6137 role_realm = user_realms[group_id] 6138 6139 if role_realm is None or not use_realm: 6140 any_entity.add(group_id) 6141 continue 6142 6143 role_realm = set(role_realm) - no_realm 6144 6145 if role_realm: 6146 q = (table[OGRP] == group_id) & (table[OENT].belongs(role_realm)) 6147 if g is None: 6148 g = q 6149 else: 6150 g |= q 6151 if any_entity: 6152 q = (table[OGRP].belongs(any_entity)) 6153 if g is None: 6154 g = q 6155 else: 6156 g |= q 6157 if g is not None: 6158 if query is None: 6159 query = g 6160 else: 6161 query |= g 6162 6163 return query
6164 6165 # -------------------------------------------------------------------------
6166 - def realm_query(self, table, entities):
6167 """ 6168 Returns a query to select the records owned by one of the entities. 6169 6170 @param table: the table 6171 @param entities: list of entities 6172 @return: a web2py Query instance, or None if no query can be 6173 constructed 6174 """ 6175 6176 OENT = "realm_entity" 6177 6178 query = None 6179 6180 if entities and "ANY" not in entities and OENT in table.fields: 6181 public = (table[OENT] == None) 6182 if len(entities) == 1: 6183 query = (table[OENT] == entities[0]) | public 6184 else: 6185 query = (table[OENT].belongs(entities)) | public 6186 6187 return query
6188 6189 # -------------------------------------------------------------------------
6190 - def permitted_realms(self, tablename, method="read"):
6191 """ 6192 Returns a list of the realm entities which a user can access for 6193 the given table. 6194 6195 @param tablename: the tablename 6196 @param method: the method 6197 @return: a list of pe_ids or None (for no restriction) 6198 """ 6199 6200 if not self.entity_realm: 6201 # Security Policy doesn't use Realms, so unrestricted 6202 return 6203 6204 auth = self.auth 6205 sr = auth.get_system_roles() 6206 user = auth.user 6207 if auth.is_logged_in(): 6208 realms = user.realms 6209 if sr.ADMIN in realms: 6210 # ADMIN can see all Realms 6211 return None 6212 delegations = user.delegations 6213 else: 6214 realms = Storage({sr.ANONYMOUS:None}) 6215 delegations = Storage() 6216 6217 racl = self.required_acl([method]) 6218 request = current.request 6219 acls = self.applicable_acls(racl, 6220 realms=realms, 6221 delegations=delegations, 6222 c=request.controller, 6223 f=request.function, 6224 t=tablename) 6225 if "ANY" in acls: 6226 # User is permitted access for all Realms 6227 return None 6228 6229 entities = [] 6230 for entity in acls: 6231 acl = acls[entity] 6232 if acl[0] & racl == racl: 6233 entities.append(entity) 6234 6235 return entities
6236 6237 # ------------------------------------------------------------------------- 6238 # Record approval 6239 # -------------------------------------------------------------------------
6240 - def approved(self, table, record, approved=True):
6241 """ 6242 Check whether a record has been approved or not 6243 6244 @param table: the table 6245 @param record: the record or record ID 6246 @param approved: True = check if approved, 6247 False = check if unapproved 6248 """ 6249 6250 if "approved_by" not in table.fields or \ 6251 not self.requires_approval(table): 6252 return approved 6253 6254 if isinstance(record, (Row, dict)): 6255 if "approved_by" not in record: 6256 record_id = record[table._id] 6257 record = None 6258 else: 6259 record_id = record 6260 record = None 6261 6262 if record is None and record_id: 6263 record = current.db(table._id == record_id).select(table.approved_by, 6264 limitby=(0, 1) 6265 ).first() 6266 if not record: 6267 return False 6268 6269 if approved and record["approved_by"] is not None: 6270 return True 6271 elif not approved and record["approved_by"] is None: 6272 return True 6273 else: 6274 return False
6275 6276 # -------------------------------------------------------------------------
6277 - def unapproved(self, table, record):
6278 """ 6279 Check whether a record has not been approved yet 6280 6281 @param table: the table 6282 @param record: the record or record ID 6283 """ 6284 6285 return self.approved(table, record, approved=False)
6286 6287 # ------------------------------------------------------------------------- 6288 @classmethod
6289 - def requires_approval(cls, table):
6290 """ 6291 Check whether record approval is required for a table 6292 6293 @param table: the table (or tablename) 6294 """ 6295 6296 settings = current.deployment_settings 6297 6298 if settings.get_auth_record_approval(): 6299 6300 if type(table) is Table: 6301 tablename = original_tablename(table) 6302 else: 6303 tablename = table 6304 6305 tables = settings.get_auth_record_approval_required_for() 6306 if tables is not None: 6307 return tablename in tables 6308 6309 elif current.s3db.get_config(tablename, "requires_approval"): 6310 return True 6311 6312 else: 6313 return False 6314 else: 6315 return False
6316 6317 # ------------------------------------------------------------------------- 6318 @classmethod
6319 - def set_default_approver(cls, table, force=False):
6320 """ 6321 Set the default approver for new records in table 6322 6323 @param table: the table 6324 @param force: whether to force approval for tables which 6325 require manual approval 6326 """ 6327 6328 APPROVER = "approved_by" 6329 if APPROVER in table: 6330 approver = table[APPROVER] 6331 else: 6332 return 6333 6334 settings = current.deployment_settings 6335 auth = current.auth 6336 6337 tablename = original_tablename(table) 6338 6339 if not settings.get_auth_record_approval(): 6340 if auth.s3_logged_in() and auth.user: 6341 approver.default = auth.user.id 6342 else: 6343 approver.default = 0 6344 elif force or \ 6345 tablename not in settings.get_auth_record_approval_manual(): 6346 if auth.override: 6347 approver.default = 0 6348 elif auth.s3_logged_in() and \ 6349 auth.s3_has_permission("approve", table): 6350 approver.default = auth.user.id 6351 else: 6352 approver.default = None
6353 6354 # ------------------------------------------------------------------------- 6355 # Authorization 6356 # -------------------------------------------------------------------------
6357 - def has_permission(self, method, c=None, f=None, t=None, record=None):
6358 """ 6359 Check permission to access a record with method 6360 6361 @param method: the access method (string) 6362 @param c: the controller name (falls back to current request) 6363 @param f: the function name (falls back to current request) 6364 @param t: the table or tablename 6365 @param record: the record or record ID (None for any record) 6366 """ 6367 6368 # Auth override, system roles and login 6369 auth = self.auth 6370 if auth.override: 6371 #_debug("==> auth.override") 6372 #_debug("*** GRANTED ***") 6373 return True 6374 6375 # Multiple methods? 6376 if isinstance(method, (list, tuple)): 6377 for m in method: 6378 if self.has_permission(m, c=c, f=f, t=t, record=record): 6379 return True 6380 return False 6381 else: 6382 method = [method] 6383 6384 if record == 0: 6385 record = None 6386 6387 #_debug("\nhas_permission('%s', c=%s, f=%s, t=%s, record=%s)", 6388 # "|".join(method), 6389 # c or current.request.controller, 6390 # f or current.request.function, 6391 # t, 6392 # record, 6393 # ) 6394 6395 sr = auth.get_system_roles() 6396 logged_in = auth.s3_logged_in() 6397 self.check_settings() 6398 6399 # Required ACL 6400 racl = self.required_acl(method) 6401 #_debug("==> required ACL: %04X", racl) 6402 6403 # Get realms and delegations 6404 if not logged_in: 6405 realms = Storage({sr.ANONYMOUS:None}) 6406 delegations = Storage() 6407 else: 6408 realms = auth.user.realms 6409 delegations = auth.user.delegations 6410 6411 # Administrators have all permissions 6412 if sr.ADMIN in realms: 6413 #_debug("==> user is ADMIN") 6414 #_debug("*** GRANTED ***") 6415 return True 6416 6417 # Fall back to current request 6418 c = c or self.controller 6419 f = f or self.function 6420 6421 if not self.use_cacls: 6422 #_debug("==> simple authorization") 6423 # Fall back to simple authorization 6424 if logged_in: 6425 #_debug("*** GRANTED ***") 6426 return True 6427 else: 6428 if self.page_restricted(c=c, f=f): 6429 permitted = racl == self.READ 6430 else: 6431 #_debug("==> unrestricted page") 6432 permitted = True 6433 #if permitted: 6434 # _debug("*** GRANTED ***") 6435 #else: 6436 # _debug("*** DENIED ***") 6437 return permitted 6438 6439 # Do we need to check the owner role (i.e. table+record given)? 6440 if t is not None and record is not None: 6441 owners = self.get_owners(t, record) 6442 is_owner = self.is_owner(t, record, owners=owners) 6443 entity = owners[0] 6444 else: 6445 owners = [] 6446 is_owner = True 6447 entity = None 6448 6449 permission_cache = self.permission_cache 6450 if permission_cache is None: 6451 permission_cache = self.permission_cache = {} 6452 key = "%s/%s/%s/%s/%s" % (method, c, f, t, record) 6453 if key in permission_cache: 6454 #permitted = permission_cache[key] 6455 #if permitted is None: 6456 # pass 6457 #elif permitted: 6458 # _debug("*** GRANTED (cached) ***") 6459 #else: 6460 # _debug("*** DENIED (cached) ***") 6461 return permission_cache[key] 6462 6463 # Get the applicable ACLs 6464 acls = self.applicable_acls(racl, 6465 realms=realms, 6466 delegations=delegations, 6467 c=c, 6468 f=f, 6469 t=t, 6470 entity=entity) 6471 6472 permitted = None 6473 if acls is None: 6474 #_debug("==> no ACLs defined for this case") 6475 permitted = True 6476 elif not acls: 6477 #_debug("==> no applicable ACLs") 6478 permitted = False 6479 else: 6480 if entity: 6481 if entity in acls: 6482 uacl, oacl = acls[entity] 6483 elif "ANY" in acls: 6484 uacl, oacl = acls["ANY"] 6485 else: 6486 #_debug("==> Owner entity outside realm") 6487 permitted = False 6488 else: 6489 uacl, oacl = self.most_permissive(acls.values()) 6490 6491 #_debug("==> uacl: %04X, oacl: %04X", uacl, oacl) 6492 6493 if permitted is None: 6494 if uacl & racl == racl: 6495 permitted = True 6496 elif oacl & racl == racl: 6497 #if is_owner and record: 6498 # _debug("==> User owns the record") 6499 #elif record: 6500 # _debug("==> User does not own the record") 6501 permitted = is_owner 6502 else: 6503 permitted = False 6504 6505 if permitted is None: 6506 raise self.error("Cannot determine permission.") 6507 6508 elif permitted and \ 6509 t is not None and record is not None and \ 6510 self.requires_approval(t): 6511 6512 # Approval possible for this table? 6513 if not hasattr(t, "_tablename"): 6514 table = current.s3db.table(t) 6515 if not table: 6516 raise AttributeError("undefined table %s" % t) 6517 else: 6518 table = t 6519 if "approved_by" in table.fields: 6520 6521 approval_methods = ("approve", "review", "reject") 6522 access_approved = not all([m in approval_methods for m in method]) 6523 access_unapproved = any([m in method for m in approval_methods]) 6524 6525 if access_unapproved: 6526 if not access_approved: 6527 permitted = self.unapproved(table, record) 6528 #if not permitted: 6529 # _debug("==> Record already approved") 6530 else: 6531 permitted = self.approved(table, record) or \ 6532 self.is_owner(table, record, owners, strict=True) or \ 6533 self.has_permission("review", t=table, record=record) 6534 #if not permitted: 6535 # _debug("==> Record not approved") 6536 # _debug("==> is owner: %s", is_owner) 6537 else: 6538 # Approval not possible for this table => no change 6539 pass 6540 6541 #if permitted: 6542 # _debug("*** GRANTED ***") 6543 #else: 6544 # _debug("*** DENIED ***") 6545 6546 # Remember the result for subsequent checks 6547 permission_cache[key] = permitted 6548 6549 return permitted
6550 6551 # -------------------------------------------------------------------------
6552 - def accessible_query(self, method, table, c=None, f=None, deny=True):
6553 """ 6554 Returns a query to select the accessible records for method 6555 in table. 6556 6557 @param method: the method as string or a list of methods (AND) 6558 @param table: the database table or table name 6559 @param c: controller name (falls back to current request) 6560 @param f: function name (falls back to current request) 6561 """ 6562 6563 # Get the table 6564 if not hasattr(table, "_tablename"): 6565 tablename = table 6566 error = AttributeError("undefined table %s" % tablename) 6567 table = current.s3db.table(tablename, 6568 db_only = True, 6569 default = error, 6570 ) 6571 6572 if not isinstance(method, (list, tuple)): 6573 method = [method] 6574 6575 #_debug("\naccessible_query(%s, '%s')", table, ",".join(method)) 6576 6577 # Defaults 6578 ALL_RECORDS = (table._id > 0) 6579 NO_RECORDS = (table._id == 0) if deny else None 6580 6581 # Record approval required? 6582 if self.requires_approval(table) and \ 6583 "approved_by" in table.fields: 6584 requires_approval = True 6585 APPROVED = (table.approved_by != None) 6586 UNAPPROVED = (table.approved_by == None) 6587 else: 6588 requires_approval = False 6589 APPROVED = ALL_RECORDS 6590 UNAPPROVED = NO_RECORDS 6591 6592 # Approval method? 6593 approval_methods = ("review", "approve", "reject") 6594 unapproved = any([m in method for m in approval_methods]) 6595 approved = not all([m in approval_methods for m in method]) 6596 6597 # What does ALL RECORDS mean? 6598 ALL_RECORDS = ALL_RECORDS if approved and unapproved \ 6599 else UNAPPROVED if unapproved \ 6600 else APPROVED 6601 6602 # Auth override, system roles and login 6603 auth = self.auth 6604 if auth.override: 6605 #_debug("==> auth.override") 6606 #_debug("*** ALL RECORDS ***") 6607 return ALL_RECORDS 6608 6609 sr = auth.get_system_roles() 6610 logged_in = auth.s3_logged_in() 6611 self.check_settings() 6612 6613 # Get realms and delegations 6614 user = auth.user 6615 if not logged_in: 6616 realms = Storage({sr.ANONYMOUS:None}) 6617 delegations = Storage() 6618 else: 6619 realms = user.realms 6620 delegations = user.delegations 6621 6622 # Don't filter out unapproved records owned by the user 6623 if requires_approval and not unapproved and \ 6624 "owned_by_user" in table.fields: 6625 ALL_RECORDS = (table.approved_by != None) 6626 if user: 6627 owner_query = (table.owned_by_user == user.id) 6628 else: 6629 owner_query = self.owner_query(table, None) 6630 if owner_query is not None: 6631 ALL_RECORDS |= owner_query 6632 6633 # Administrators have all permissions 6634 if sr.ADMIN in realms: 6635 #_debug("==> user is ADMIN") 6636 #_debug("*** ALL RECORDS ***") 6637 return ALL_RECORDS 6638 6639 # Multiple methods? 6640 if len(method) > 1: 6641 query = None 6642 for m in method: 6643 q = self.accessible_query(m, table, c=c, f=f, deny=False) 6644 if q is not None: 6645 if query is None: 6646 query = q 6647 else: 6648 query |= q 6649 if query is None: 6650 query = NO_RECORDS 6651 return query 6652 6653 key = "%s/%s/%s/%s/%s" % (method, table, c, f, deny) 6654 query_cache = self.query_cache 6655 if key in query_cache: 6656 query = query_cache[key] 6657 return query 6658 6659 # Required ACL 6660 racl = self.required_acl(method) 6661 #_debug("==> required permissions: %04X", racl) 6662 6663 # Use ACLs? 6664 if not self.use_cacls: 6665 #_debug("==> simple authorization") 6666 # Fall back to simple authorization 6667 if logged_in: 6668 #_debug("*** ALL RECORDS ***") 6669 return ALL_RECORDS 6670 else: 6671 permitted = racl == self.READ 6672 if permitted: 6673 #_debug("*** ALL RECORDS ***") 6674 return ALL_RECORDS 6675 else: 6676 #_debug("*** ACCESS DENIED ***") 6677 return NO_RECORDS 6678 6679 # Fall back to current request 6680 c = c or self.controller 6681 f = f or self.function 6682 6683 # Get the applicable ACLs 6684 acls = self.applicable_acls(racl, 6685 realms=realms, 6686 delegations=delegations, 6687 c=c, 6688 f=f, 6689 t=table) 6690 6691 if acls is None: 6692 #_debug("==> no ACLs defined for this case") 6693 #_debug("*** ALL RECORDS ***") 6694 query = query_cache[key] = ALL_RECORDS 6695 return query 6696 elif not acls: 6697 #_debug("==> no applicable ACLs") 6698 #_debug("*** ACCESS DENIED ***") 6699 query = query_cache[key] = NO_RECORDS 6700 return query 6701 6702 oacls = [] 6703 uacls = [] 6704 for entity in acls: 6705 acl = acls[entity] 6706 if acl[0] & racl == racl: 6707 uacls.append(entity) 6708 elif acl[1] & racl == racl and entity not in uacls: 6709 oacls.append(entity) 6710 6711 query = None 6712 no_realm = [] 6713 check_owner_acls = True 6714 6715 if "ANY" in uacls: 6716 #_debug("==> permitted for any records") 6717 query = ALL_RECORDS 6718 check_owner_acls = False 6719 6720 elif uacls: 6721 query = self.realm_query(table, uacls) 6722 if query is None: 6723 #_debug("==> permitted for any records") 6724 query = ALL_RECORDS 6725 check_owner_acls = False 6726 else: 6727 #_debug("==> permitted for records owned by entities %s", str(uacls)) 6728 no_realm = uacls 6729 6730 if check_owner_acls: 6731 6732 use_realm = "ANY" not in oacls 6733 owner_query = self.owner_query(table, 6734 user, 6735 use_realm=use_realm, 6736 realm=oacls, 6737 no_realm=no_realm, 6738 ) 6739 6740 if owner_query is not None: 6741 #_debug("==> permitted for owned records (limit to realms=%s)", use_realm) 6742 if query is not None: 6743 query |= owner_query 6744 else: 6745 query = owner_query 6746 elif use_realm: 6747 #_debug("==> permitted for any records owned by entities %s", str(uacls+oacls)) 6748 query = self.realm_query(table, uacls+oacls) 6749 6750 if query is not None and requires_approval: 6751 base_filter = None if approved and unapproved else \ 6752 UNAPPROVED if unapproved else APPROVED 6753 if base_filter is not None: 6754 query = base_filter & query 6755 6756 # Fallback 6757 if query is None: 6758 query = NO_RECORDS 6759 6760 #_debug("*** Accessible Query ***") 6761 #_debug(str(query)) 6762 query_cache[key] = query 6763 return query
6764 6765 # -------------------------------------------------------------------------
6766 - def accessible_url(self, 6767 c=None, 6768 f=None, 6769 p=None, 6770 t=None, 6771 a=None, 6772 args=None, 6773 vars=None, 6774 anchor="", 6775 extension=None, 6776 env=None):
6777 """ 6778 Return a URL only if accessible by the user, otherwise False 6779 - used for Navigation Items 6780 6781 @param c: the controller 6782 @param f: the function 6783 @param p: the permission (defaults to READ) 6784 @param t: the tablename (defaults to <c>_<f>) 6785 @param a: the application name 6786 @param args: the URL arguments 6787 @param vars: the URL variables 6788 @param anchor: the anchor (#) of the URL 6789 @param extension: the request format extension 6790 @param env: the environment 6791 """ 6792 6793 if args is None: 6794 args = [] 6795 if vars is None: 6796 vars = {} 6797 6798 if c != "static": 6799 # Hide disabled modules 6800 settings = current.deployment_settings 6801 if not settings.has_module(c): 6802 return False 6803 6804 if t is None: 6805 t = "%s_%s" % (c, f) 6806 table = current.s3db.table(t) 6807 if not table: 6808 t = None 6809 if not p: 6810 p = "read" 6811 6812 permitted = self.has_permission(p, c=c, f=f, t=t) 6813 if permitted: 6814 return URL(a=a, 6815 c=c, 6816 f=f, 6817 args=args, 6818 vars=vars, 6819 anchor=anchor, 6820 extension=extension, 6821 env=env) 6822 else: 6823 return False
6824 6825 # -------------------------------------------------------------------------
6826 - def fail(self):
6827 """ Action upon insufficient permissions """ 6828 6829 if self.format == "html": 6830 # HTML interactive request => flash message + redirect 6831 if self.auth.s3_logged_in(): 6832 current.session.error = self.INSUFFICIENT_PRIVILEGES 6833 redirect(self.homepage) 6834 else: 6835 current.session.error = self.AUTHENTICATION_REQUIRED 6836 redirect(self.loginpage) 6837 else: 6838 # non-HTML request => raise proper HTTP error 6839 if self.auth.s3_logged_in(): 6840 raise HTTP(403, body=self.INSUFFICIENT_PRIVILEGES) 6841 else: 6842 # RFC1945/2617 compliance: 6843 # Must raise an HTTP Auth challenge with status 401 6844 challenge = {"WWW-Authenticate": 6845 u"Basic realm=\"%s\"" % current.request.application} 6846 raise HTTP(401, body=self.AUTHENTICATION_REQUIRED, **challenge)
6847 6848 # ------------------------------------------------------------------------- 6849 # ACL Lookup 6850 # -------------------------------------------------------------------------
6851 - def applicable_acls(self, racl, 6852 realms=None, 6853 delegations=None, 6854 c=None, 6855 f=None, 6856 t=None, 6857 entity=None):
6858 """ 6859 Find all applicable ACLs for the specified situation for 6860 the specified realms and delegations 6861 6862 @param racl: the required ACL 6863 @param realms: the realms 6864 @param delegations: the delegations 6865 @param c: the controller name, falls back to current request 6866 @param f: the function name, falls back to current request 6867 @param t: the tablename 6868 @param entity: the realm entity 6869 6870 @return: None for no ACLs defined (allow), 6871 [] for no ACLs applicable (deny), 6872 or list of applicable ACLs 6873 """ 6874 6875 if not self.use_cacls: 6876 # We do not use ACLs at all (allow all) 6877 return None 6878 else: 6879 acls = {} 6880 6881 # Get all roles 6882 if realms: 6883 roles = set(realms.keys()) 6884 if delegations: 6885 for role in delegations: 6886 roles.add(role) 6887 else: 6888 # No roles available (deny all) 6889 return acls 6890 6891 db = current.db 6892 table = self.table 6893 6894 c = c or self.controller 6895 f = f or self.function 6896 page_restricted = self.page_restricted(c=c, f=f) 6897 6898 # Base query 6899 query = (table.deleted != True) & \ 6900 (table.group_id.belongs(roles)) 6901 6902 # Page ACLs 6903 if page_restricted: 6904 q = (table.function == None) 6905 if f and self.use_facls: 6906 q |= (table.function == f) 6907 q &= (table.controller == c) 6908 else: 6909 q = None 6910 6911 # Table ACLs 6912 if t and self.use_tacls: 6913 # Be sure to use the original table name 6914 if hasattr(t, "_tablename"): 6915 t = original_tablename(t) 6916 tq = (table.controller == None) & \ 6917 (table.function == None) & \ 6918 (table.tablename == t) 6919 q = tq if q is None else q | tq 6920 table_restricted = self.table_restricted(t) 6921 else: 6922 table_restricted = False 6923 6924 # Retrieve the ACLs 6925 if q is not None: 6926 query &= q 6927 rows = db(query).select(table.group_id, 6928 table.controller, 6929 table.function, 6930 table.tablename, 6931 table.unrestricted, 6932 table.entity, 6933 table.uacl, 6934 table.oacl, 6935 cacheable=True) 6936 else: 6937 rows = [] 6938 6939 # Cascade ACLs 6940 ANY = "ANY" 6941 6942 ALL = (self.ALL, self.ALL) 6943 NONE = (self.NONE, self.NONE) 6944 6945 use_facls = self.use_facls 6946 def rule_type(r): 6947 if r.controller is not None: 6948 if r.function is None: 6949 return "c" 6950 elif use_facls: 6951 return "f" 6952 elif r.tablename is not None: 6953 return "t" 6954 return None
6955 6956 most_permissive = lambda x, y: (x[0] | y[0], x[1] | y[1]) 6957 most_restrictive = lambda x, y: (x[0] & y[0], x[1] & y[1]) 6958 6959 # Realms 6960 delegation_rows = [] 6961 append_delegation = delegation_rows.append 6962 6963 use_realms = self.entity_realm 6964 for row in rows: 6965 6966 # Get the assigning entities 6967 group_id = row.group_id 6968 if group_id in delegations: 6969 append_delegation(row) 6970 if group_id not in realms: 6971 continue 6972 rtype = rule_type(row) 6973 if rtype is None: 6974 continue 6975 6976 if use_realms: 6977 if row.unrestricted: 6978 entities = [ANY] 6979 elif row.entity is not None: 6980 entities = [row.entity] 6981 else: 6982 entities = realms[group_id] 6983 if entities is None: 6984 entities = [ANY] 6985 else: 6986 entities = [ANY] 6987 6988 # Merge the ACL 6989 acl = (row["uacl"], row["oacl"]) 6990 for e in entities: 6991 if e in acls: 6992 eacls = acls[e] 6993 if rtype in eacls: 6994 eacls[rtype] = most_permissive(eacls[rtype], acl) 6995 else: 6996 eacls[rtype] = acl 6997 else: 6998 acls[e] = {rtype: acl} 6999 7000 if ANY in acls: 7001 default = dict(acls[ANY]) 7002 else: 7003 default = None 7004 7005 # Delegations 7006 if self.delegations: 7007 for row in delegation_rows: 7008 7009 # Get the rule type 7010 rtype = rule_type(row) 7011 if rtype is None: 7012 continue 7013 7014 # Get the delegation realms 7015 group_id = row.group_id 7016 if group_id not in delegations: 7017 continue 7018 else: 7019 drealms = delegations[group_id] 7020 7021 acl = (row["uacl"], row["oacl"]) 7022 7023 # Resolve the delegation realms 7024 # @todo: optimize 7025 for receiver in drealms: 7026 drealm = drealms[receiver] 7027 7028 # Skip irrelevant delegations 7029 if entity: 7030 if entity not in drealm: 7031 continue 7032 else: 7033 drealm = [entity] 7034 7035 # What ACLs do we have for the receiver? 7036 if receiver in acls: 7037 dacls = dict(acls[receiver]) 7038 elif default is not None: 7039 dacls = default 7040 else: 7041 continue 7042 7043 # Filter the delegated ACLs 7044 if rtype in dacls: 7045 dacls[rtype] = most_restrictive(dacls[rtype], acl) 7046 else: 7047 dacls[rtype] = acl 7048 7049 # Add/extend the new realms (e=entity, t=rule type) 7050 # @todo: optimize 7051 for e in drealm: 7052 if e in acls: 7053 for acltype in ("c", "f", "t"): 7054 if acltype in acls[e]: 7055 if acltype in dacls: 7056 dacls[acltype] = most_restrictive( 7057 dacls[acltype], 7058 acls[e][acltype], 7059 ) 7060 else: 7061 dacls[acltype] = acls[e][acltype] 7062 acls[e] = dacls 7063 7064 acl = acls.get(ANY, {}) 7065 7066 # Default page ACL 7067 if "c" in acl: 7068 if "f" in acl: 7069 default_page_acl = acl["f"] 7070 else: 7071 default_page_acl = acl["c"] 7072 elif page_restricted: 7073 default_page_acl = NONE 7074 else: 7075 default_page_acl = ALL 7076 7077 # Default table ACL 7078 if "t" in acl: 7079 default_table_acl = acl["t"] 7080 elif table_restricted: 7081 default_table_acl = default_page_acl if page_restricted else NONE 7082 else: 7083 default_table_acl = default_page_acl if page_restricted else ALL 7084 7085 # No ACLs inevitably causes a "no applicable ACLs" permission failure, 7086 # so for unrestricted pages or tables, we must create a default ACL 7087 # here in order to have the default apply: 7088 if not acls: 7089 if t and self.use_tacls: 7090 if not table_restricted: 7091 acls[ANY] = {"t": default_table_acl} 7092 elif not page_restricted: 7093 acls[ANY] = {"c": default_page_acl} 7094 7095 # Order by precedence 7096 s3db = current.s3db 7097 ancestors = set() 7098 if entity and self.entity_hierarchy and \ 7099 s3db.pr_instance_type(entity) == "pr_person": 7100 # If the realm entity is a person, then we apply the ACLs 7101 # for the immediate OU ancestors, for two reasons: 7102 # a) it is not possible to assign roles for personal realms anyway 7103 # b) looking up OU ancestors of a person (=a few) is much more 7104 # efficient than looking up pr_person OU descendants of the 7105 # role realm (=could be tens or hundreds of thousands) 7106 ancestors = set(s3db.pr_realm(entity)) 7107 7108 result = {} 7109 for e in acls: 7110 # Skip irrelevant ACLs 7111 if entity and e != entity and e != ANY: 7112 if e in ancestors: 7113 key = entity 7114 else: 7115 continue 7116 else: 7117 key = e 7118 7119 acl = acls[e] 7120 7121 # Get the page ACL 7122 if "f" in acl: 7123 page_acl = most_permissive(default_page_acl, acl["f"]) 7124 elif "c" in acl: 7125 page_acl = most_permissive(default_page_acl, acl["c"]) 7126 elif page_restricted: 7127 page_acl = default_page_acl 7128 else: 7129 page_acl = ALL 7130 7131 # Get the table ACL 7132 if "t" in acl: 7133 table_acl = most_permissive(default_table_acl, acl["t"]) 7134 elif table_restricted: 7135 table_acl = default_table_acl 7136 else: 7137 table_acl = ALL 7138 7139 # Merge 7140 acl = most_restrictive(page_acl, table_acl) 7141 7142 # Include ACL if relevant 7143 if acl[0] & racl == racl or acl[1] & racl == racl: 7144 result[key] = acl 7145 7146 #for pe in result: 7147 # sys.stderr.write("ACL for PE %s: %04X %04X\n" % 7148 # (pe, result[pe][0], result[pe][1])) 7149 7150 return result
7151 7152 # ------------------------------------------------------------------------- 7153 # Utilities 7154 # -------------------------------------------------------------------------
7155 - def page_restricted(self, c=None, f=None):
7156 """ 7157 Checks whether a page is restricted (=whether ACLs 7158 are to be applied) 7159 7160 @param c: controller name 7161 @param f: function name 7162 """ 7163 7164 7165 page = "%s/%s" % (c, f) 7166 if page in self.unrestricted_pages: 7167 restricted = False 7168 elif c != "default" or f not in ("tables", "table"): 7169 modules = current.deployment_settings.modules 7170 restricted = c in modules and modules[c].restricted 7171 else: 7172 restricted = True 7173 7174 return restricted
7175 7176 # -------------------------------------------------------------------------
7177 - def table_restricted(self, t=None):
7178 """ 7179 Check whether access to a table is restricted 7180 7181 @param t: the table name or Table 7182 """ 7183 7184 s3 = current.response.s3 7185 7186 if not "restricted_tables" in s3: 7187 table = self.table 7188 query = (table.deleted != True) & \ 7189 (table.controller == None) & \ 7190 (table.function == None) 7191 rows = current.db(query).select(table.tablename, 7192 groupby=table.tablename) 7193 s3.restricted_tables = [row.tablename for row in rows] 7194 7195 return str(t) in s3.restricted_tables
7196 7197 # -------------------------------------------------------------------------
7198 - def hidden_modules(self):
7199 """ List of modules to hide from the main menu """ 7200 7201 hidden_modules = [] 7202 if self.use_cacls: 7203 sr = self.auth.get_system_roles() 7204 modules = current.deployment_settings.modules 7205 restricted_modules = [m for m in modules 7206 if modules[m].restricted] 7207 roles = [] 7208 if current.session.s3 is not None: 7209 roles = current.session.s3.roles or [] 7210 if sr.ADMIN in roles or sr.EDITOR in roles: 7211 return [] 7212 if not roles: 7213 hidden_modules = restricted_modules 7214 else: 7215 t = self.table 7216 query = (t.deleted != True) & \ 7217 (t.controller.belongs(restricted_modules)) & \ 7218 (t.tablename == None) 7219 if roles: 7220 query = query & (t.group_id.belongs(roles)) 7221 else: 7222 query = query & (t.group_id == None) 7223 rows = current.db(query).select() 7224 acls = dict() 7225 for acl in rows: 7226 if acl.controller not in acls: 7227 acls[acl.controller] = self.NONE 7228 acls[acl.controller] |= acl.oacl | acl.uacl 7229 hidden_modules = [m for m in restricted_modules 7230 if m not in acls or not acls[m]] 7231 return hidden_modules
7232 7233 # -------------------------------------------------------------------------
7234 - def ownership_required(self, method, table, c=None, f=None):
7235 """ 7236 Checks whether ownership can be required to access records in 7237 this table (this may not apply to every record in this table). 7238 7239 @param method: the method as string or a list of methods (AND) 7240 @param table: the database table or table name 7241 @param c: controller name (falls back to current request) 7242 @param f: function name (falls back to current request) 7243 """ 7244 7245 if not self.use_cacls: 7246 if self.policy in (1, 2): 7247 return False 7248 else: 7249 return True 7250 7251 if not hasattr(table, "_tablename"): 7252 tablename = table 7253 table = current.s3db.table(tablename) 7254 if not table: 7255 raise AttributeError("undefined table %s" % tablename) 7256 7257 # If the table doesn't have any ownership fields, then no 7258 if "owned_by_user" not in table.fields and \ 7259 "owned_by_group" not in table.fields and \ 7260 "realm_entity" not in table.fields: 7261 return False 7262 7263 if not isinstance(method, (list, tuple)): 7264 method = [method] 7265 7266 # Auth override, system roles and login 7267 auth = self.auth 7268 if self.auth.override or not self.use_cacls: 7269 return False 7270 sr = auth.get_system_roles() 7271 logged_in = auth.s3_logged_in() 7272 7273 # Required ACL 7274 racl = self.required_acl(method) 7275 7276 # Get realms and delegations 7277 user = auth.user 7278 if not logged_in: 7279 realms = Storage({sr.ANONYMOUS:None}) 7280 delegations = Storage() 7281 else: 7282 realms = user.realms 7283 delegations = user.delegations 7284 7285 # Admin always owns all records 7286 if sr.ADMIN in realms: 7287 return False 7288 7289 # Fall back to current request 7290 c = c or self.controller 7291 f = f or self.function 7292 7293 # Get the applicable ACLs 7294 acls = self.applicable_acls(racl, 7295 realms=realms, 7296 delegations=delegations, 7297 c=c, 7298 f=f, 7299 t=table) 7300 acls = [entity for entity in acls if acls[entity][0] & racl == racl] 7301 7302 # If we have a UACL and it is not limited to any realm, then no 7303 if "ANY" in acls or acls and "realm_entity" not in table.fields: 7304 return False 7305 7306 # In all other cases: yes 7307 return True
7308 7309 # -------------------------------------------------------------------------
7310 - def forget(self, table=None, record_id=None):
7311 """ 7312 Remove any cached permissions for a record. This can be 7313 necessary in methods which change the status of the record 7314 (e.g. approval). 7315 7316 @param table: the table 7317 @param record_id: the record ID 7318 """ 7319 7320 if table is None: 7321 self.permission_cache = {} 7322 return 7323 permissions = self.permission_cache 7324 if not permissions: 7325 return 7326 7327 if hasattr(table, "_tablename"): 7328 tablename = original_tablename(table) 7329 else: 7330 tablename = table 7331 7332 for key in list(permissions.keys()): 7333 r = key.split("/") 7334 if len(r) > 1 and r[-2] == tablename: 7335 if record_id is None or \ 7336 record_id is not None and r[-1] == str(record_id): 7337 del permissions[key]
7338
7339 # ============================================================================= 7340 -class S3Audit(object):
7341 """ S3 Audit Trail Writer Class """ 7342
7343 - def __init__(self, 7344 tablename="s3_audit", 7345 migrate=True, 7346 fake_migrate=False):
7347 """ 7348 Constructor 7349 7350 @param tablename: the name of the audit table 7351 @param migrate: migration setting 7352 7353 @note: this defines the audit table 7354 """ 7355 7356 settings = current.deployment_settings 7357 audit_read = settings.get_security_audit_read() 7358 audit_write = settings.get_security_audit_write() 7359 if not audit_read and not audit_write: 7360 # Auditing is Disabled 7361 self.table = None 7362 return 7363 7364 db = current.db 7365 if tablename not in db: 7366 db.define_table(tablename, 7367 Field("timestmp", "datetime", 7368 represent = S3DateTime.datetime_represent, 7369 ), 7370 Field("user_id", db.auth_user), 7371 Field("method"), 7372 Field("tablename"), 7373 Field("record_id", "integer"), 7374 Field("representation"), 7375 # List of Key:Values 7376 Field("old_value", "text"), 7377 # List of Key:Values 7378 Field("new_value", "text"), 7379 Field("repository_id", "integer"), 7380 migrate=migrate, 7381 fake_migrate=fake_migrate, 7382 ) 7383 self.table = db[tablename] 7384 7385 user = current.auth.user 7386 if user: 7387 self.user_id = user.id 7388 else: 7389 self.user_id = None
7390 7391 # -------------------------------------------------------------------------
7392 - def __call__(self, method, prefix, name, 7393 form=None, 7394 record=None, 7395 representation="unknown"):
7396 """ 7397 Audit 7398 7399 @param method: Method to log, one of 7400 "create", "update", "read", "list" or "delete" 7401 @param prefix: the module prefix of the resource 7402 @param name: the name of the resource (without prefix) 7403 @param form: the form 7404 @param record: the record ID 7405 @param representation: the representation format 7406 """ 7407 7408 table = self.table 7409 if not table: 7410 # Don't Audit 7411 return True 7412 7413 #if DEBUG: 7414 # _debug("Audit %s: %s_%s record=%s representation=%s", 7415 # method, 7416 # prefix, 7417 # name, 7418 # record, 7419 # representation, 7420 # ) 7421 7422 if method in ("list", "read"): 7423 audit = current.deployment_settings.get_security_audit_read() 7424 elif method in ("create", "update", "delete"): 7425 audit = current.deployment_settings.get_security_audit_write() 7426 else: 7427 # Don't Audit 7428 return True 7429 7430 if not audit: 7431 # Don't Audit 7432 return True 7433 7434 tablename = "%s_%s" % (prefix, name) 7435 7436 if record: 7437 if isinstance(record, Row): 7438 record = record.get("id", None) 7439 if not record: 7440 return True 7441 try: 7442 record = int(record) 7443 except ValueError: 7444 record = None 7445 elif form: 7446 try: 7447 record = form.vars["id"] 7448 except: 7449 try: 7450 record = form["id"] 7451 except: 7452 record = None 7453 if record: 7454 try: 7455 record = int(record) 7456 except ValueError: 7457 record = None 7458 else: 7459 record = None 7460 7461 if callable(audit): 7462 audit = audit(method, tablename, form, record, representation) 7463 if not audit: 7464 # Don't Audit 7465 return True 7466 7467 if method in ("list", "read"): 7468 table.insert(timestmp = datetime.datetime.utcnow(), 7469 user_id = self.user_id, 7470 method = method, 7471 tablename = tablename, 7472 record_id = record, 7473 representation = representation, 7474 repository_id = current.response.s3.repository_id, 7475 ) 7476 7477 elif method == "create": 7478 if form: 7479 form_vars = form.vars 7480 if not record: 7481 record = form_vars["id"] 7482 new_value = ["%s:%s" % (var, str(form_vars[var])) 7483 for var in form_vars if form_vars[var]] 7484 else: 7485 new_value = [] 7486 table.insert(timestmp = datetime.datetime.utcnow(), 7487 user_id = self.user_id, 7488 method = method, 7489 tablename = tablename, 7490 record_id = record, 7491 representation = representation, 7492 new_value = new_value, 7493 repository_id = current.response.s3.repository_id, 7494 ) 7495 7496 elif method == "update": 7497 if form: 7498 rvars = form.record 7499 if rvars: 7500 old_value = ["%s:%s" % (var, str(rvars[var])) 7501 for var in rvars] 7502 else: 7503 old_value = [] 7504 fvars = form.vars 7505 if not record: 7506 record = fvars["id"] 7507 new_value = ["%s:%s" % (var, str(fvars[var])) 7508 for var in fvars] 7509 else: 7510 new_value = [] 7511 old_value = [] 7512 table.insert(timestmp = datetime.datetime.utcnow(), 7513 user_id = self.user_id, 7514 method = method, 7515 tablename = tablename, 7516 record_id = record, 7517 representation = representation, 7518 old_value = old_value, 7519 new_value = new_value, 7520 repository_id = current.response.s3.repository_id, 7521 ) 7522 7523 elif method == "delete": 7524 db = current.db 7525 query = (db[tablename].id == record) 7526 row = db(query).select(limitby=(0, 1)).first() 7527 old_value = [] 7528 if row: 7529 old_value = ["%s:%s" % (field, row[field]) 7530 for field in row] 7531 table.insert(timestmp = datetime.datetime.utcnow(), 7532 user_id = self.user_id, 7533 method = method, 7534 tablename = tablename, 7535 record_id = record, 7536 representation = representation, 7537 old_value = old_value, 7538 repository_id = current.response.s3.repository_id, 7539 ) 7540 7541 return True
7542 7543 # -------------------------------------------------------------------------
7544 - def represent(self, records):
7545 """ 7546 Provide a Human-readable representation of Audit records 7547 - currently unused 7548 7549 @param record: the record IDs 7550 """ 7551 7552 table = self.table 7553 # Retrieve the records 7554 if isinstance(records, int): 7555 limit = 1 7556 query = (table.id == records) 7557 else: 7558 limit = len(records) 7559 query = (table.id.belongs(records)) 7560 records = current.db(query).select(table.tablename, 7561 table.method, 7562 table.user_id, 7563 table.old_value, 7564 table.new_value, 7565 limitby=(0, limit) 7566 ) 7567 7568 # Convert to Human-readable form 7569 s3db = current.s3db 7570 output = [] 7571 oappend = output.append 7572 for record in records: 7573 table = s3db[record.tablename] 7574 method = record.method 7575 if method == "create": 7576 new_value = record.new_value 7577 if not new_value: 7578 continue 7579 diff = [] 7580 dappend = diff.append 7581 for v in new_value: 7582 fieldname, value = v.split(":", 1) 7583 represent = table[fieldname].represent 7584 if represent: 7585 value = represent(value) 7586 label = table[fieldname].label or fieldname 7587 dappend("%s is %s" % (label, value)) 7588 7589 elif method == "update": 7590 old_values = record.old_value 7591 new_values = record.new_value 7592 if not new_value: 7593 continue 7594 changed = {} 7595 for v in new_values: 7596 fieldname, new_value = v.split(":", 1) 7597 old_value = old_values.get(fieldname, None) 7598 if new_value != old_value: 7599 ftype = table[fieldname].type 7600 if ftype == "integer" or \ 7601 ftype.startswith("reference"): 7602 if new_value: 7603 new_value = int(new_value) 7604 if new_value == old_value: 7605 continue 7606 represent = table[fieldname].represent 7607 if represent: 7608 new_value = represent(new_value) 7609 label = table[fieldname].label or fieldname 7610 if old_value: 7611 if represent: 7612 old_value = represent(old_value) 7613 changed[fieldname] = "%s changed from %s to %s" % \ 7614 (label, old_value, new_value) 7615 else: 7616 changed[fieldname] = "%s changed to %s" % \ 7617 (label, new_value) 7618 diff = [] 7619 dappend = diff.append 7620 for fieldname in changed: 7621 dappend(changed[fieldname]) 7622 7623 elif method == "delete": 7624 old_value = record.old_value 7625 if not old_value: 7626 continue 7627 diff = [] 7628 dappend = diff.append 7629 for v in old_value: 7630 fieldname, value = v.split(":", 1) 7631 represent = table[fieldname].represent 7632 if represent: 7633 value = represent(value) 7634 label = table[fieldname].label or fieldname 7635 dappend("%s was %s" % (label, value)) 7636 7637 oappend("\n".join(diff)) 7638 7639 return output
7640
7641 # ============================================================================= 7642 -class S3GroupedOptionsWidget(OptionsWidget):
7643 """ 7644 A custom Field widget to create a SELECT element with grouped options. 7645 """ 7646 7647 @classmethod
7648 - def widget(cls, field, value, options, **attributes):
7649 """ 7650 Generates a SELECT tag, with OPTIONs grouped by OPTGROUPs 7651 7652 @param field: the field needing the widget 7653 @param value: value 7654 @param options: list 7655 @param options: a list of tuples, each either (label, value) or (label, {options}) 7656 @param attributes: any other attributes to be applied 7657 7658 @return: SELECT object 7659 """ 7660 7661 default = dict(value=value) 7662 attr = cls._attributes(field, default, **attributes) 7663 select_items = [] 7664 7665 for option in options: 7666 if isinstance(option[1], dict): 7667 items = [(v, k) for k, v in option[1].items()] 7668 if not items: 7669 continue 7670 items.sort() 7671 opts = [OPTION(v, _value=k) for v, k in items] 7672 select_items.append(OPTGROUP(*opts, _label=option[0])) 7673 else: 7674 select_items.append(OPTION(option[1], _label=option[0])) 7675 7676 return SELECT(select_items, **attr)
7677
7678 # ============================================================================= 7679 -class S3EntityRoleManager(S3Method):
7680 """ Entity/User role manager """ 7681 7682 ENTITY_TYPES = ["org_organisation", 7683 "org_office", 7684 "inv_warehouse", 7685 "hms_hospital", 7686 #"po_area", 7687 "pr_group", 7688 ] 7689
7690 - def __init__(self, *args, **kwargs):
7691 """ Constructor """ 7692 7693 super(S3EntityRoleManager, self).__init__(*args, **kwargs) 7694 7695 # Dictionary of pentities this admin can manage 7696 self.realm = self.get_realm() 7697 7698 # The list of user accounts linked to pentities in this realm 7699 self.realm_users = current.s3db.pr_realm_users(self.realm) 7700 7701 # Create the dictionary of roles 7702 self.roles = {} 7703 7704 self.modules = self.get_modules() 7705 self.acls = self.get_access_levels() 7706 7707 for module_uid, module_label in self.modules.items(): 7708 for acl_uid, acl_label in self.acls.items(): 7709 role_uid = "%s_%s" % (module_uid, acl_uid) 7710 7711 self.roles[role_uid] = { 7712 "module": { 7713 "uid": module_uid, 7714 "label": module_label 7715 }, 7716 "acl": { 7717 "uid": acl_uid, 7718 "label": acl_label 7719 } 7720 }
7721 7722 # ------------------------------------------------------------------------- 7723 @classmethod
7724 - def set_method(cls, r, entity=None, record_id=None):
7725 """ 7726 Plug-in OrgAdmin Role Managers when appropriate 7727 7728 @param r: the S3Request 7729 @param entity: override target entity (default: r.tablename) 7730 @param record_id: specify target record ID (only for OU's) 7731 """ 7732 7733 s3db = current.s3db 7734 auth = current.auth 7735 7736 if not current.deployment_settings.get_auth_entity_role_manager() or \ 7737 auth.user is None: 7738 return False 7739 7740 sr = auth.get_system_roles() 7741 realms = auth.user.realms or Storage() 7742 7743 ORG_ADMIN = sr.ORG_ADMIN 7744 7745 admin = sr.ADMIN in realms 7746 org_admin = ORG_ADMIN in realms 7747 7748 if admin or org_admin: 7749 7750 if entity is not None: 7751 tablename = entity 7752 record = None 7753 else: 7754 tablename = r.tablename 7755 record = r.record 7756 7757 all_entities = admin or org_admin and realms[ORG_ADMIN] is None 7758 7759 if not all_entities and tablename in cls.ENTITY_TYPES: 7760 7761 if not record and record_id is not None: 7762 7763 # Try to load the record and check pe_id 7764 table = s3db.table(tablename) 7765 if table and "pe_id" in table.fields: 7766 record = current.db(table._id==record_id).select(table.pe_id, 7767 limitby = (0, 1)).first() 7768 7769 if record and record.pe_id not in realms[ORG_ADMIN]: 7770 return False 7771 7772 if entity is not None: 7773 # Configure as custom method for this resource 7774 prefix, name = tablename.split("_", 1) 7775 s3db.set_method(prefix, name, method="roles", action=cls) 7776 7777 elif tablename in cls.ENTITY_TYPES: 7778 # Configure as method handler for this request 7779 r.set_handler("roles", cls) 7780 7781 else: 7782 # Unsupported entity 7783 return False 7784 7785 return True
7786 7787 # -------------------------------------------------------------------------
7788 - def apply_method(self, r, **attr):
7789 """ 7790 """ 7791 7792 if self.method == "roles" and \ 7793 (r.tablename in self.ENTITY_TYPES + ["pr_person"]): 7794 context = self.get_context_data(r, **attr) 7795 else: 7796 r.error(405, current.ERROR.BAD_METHOD) 7797 7798 # Set the default view 7799 current.response.view = "admin/manage_roles.html" 7800 7801 return context
7802 7803 # -------------------------------------------------------------------------
7804 - def get_context_data(self, r, **attr):
7805 """ 7806 @todo: description? 7807 7808 @return: dictionary for the view 7809 7810 { 7811 # All the possible roles 7812 "roles": { 7813 "staff_reader": { 7814 "module": { 7815 "uid": "staff", 7816 "label": "Staff" 7817 }, 7818 ... 7819 }, 7820 ... 7821 }, 7822 7823 # The roles currently assigned to users for entit(y/ies) 7824 "assigned_roles": { 7825 "1": [ 7826 "staff_reader", 7827 "project_editor", 7828 ... 7829 ], 7830 ... 7831 }, 7832 7833 "pagination_list": [ 7834 ( 7835 "User One", 7836 "1" 7837 ), 7838 ... 7839 ], 7840 7841 # The object (user/entity) we are assigning roles for 7842 "foreign_object": { 7843 "id": "1", 7844 "name": "User One" 7845 } 7846 or 7847 "foreign_object": { 7848 "id": "70", 7849 "name": "Organisation Seventy" 7850 } 7851 } 7852 """ 7853 7854 T = current.T 7855 7856 # organisation or site entity 7857 self.entity = self.get_entity() 7858 7859 # user account to assigned roles to 7860 self.user = self.get_user() 7861 7862 # roles already assigned to a user or users 7863 self.assigned_roles = self.get_assigned_roles() 7864 7865 # The foreign object is the one selected in the role form 7866 # for a person this is the entity 7867 # for an entity (organisation or office) this is a user 7868 self.foreign_object = self.get_foreign_object() 7869 7870 form = self.get_form() 7871 7872 # if we are editing roles, set those assigned roles as initial values 7873 # for the form 7874 form.vars.update(self.get_form_vars()) 7875 7876 if form.accepts(r.post_vars, current.session): 7877 before = self.assigned_roles[self.foreign_object["id"]] if self.foreign_object else [] 7878 after = ["%s_%s" % (mod_uid, acl_uid) for mod_uid, acl_uid 7879 in form.vars.items() 7880 if mod_uid in self.modules.keys() 7881 and acl_uid in self.acls.keys()] 7882 7883 # either both values will have been specified or one will 7884 # be supplied by the form (for roles on new objects) 7885 user_id = self.user["id"] if self.user else form.vars.foreign_object 7886 entity_id = self.entity["id"] if self.entity else form.vars.foreign_object 7887 7888 self.update_roles(user_id, entity_id, before, after) 7889 current.session.confirmation = T("Roles updated") 7890 redirect(r.url(vars={})) 7891 7892 context = {"roles": self.roles, 7893 "foreign_object": self.foreign_object, 7894 "form": form, 7895 "title": T("Roles"), 7896 } 7897 7898 if not self.foreign_object: 7899 # how many assigned roles to show per page 7900 pagination_size = int(r.get_vars.get("page_size", 4)) 7901 # what page of assigned roles to view 7902 pagination_offset = int(r.get_vars.get("page_offset", 0)) 7903 # the number of pages of assigned roles 7904 import math 7905 pagination_pages = int(math.ceil(len(self.assigned_roles) / float(pagination_size))) 7906 # the list of objects to show on this page sorted by name 7907 pagination_list = [(self.objects[id], id) for id in self.assigned_roles] 7908 pagination_list = sorted(pagination_list)[pagination_offset * pagination_size:pagination_offset * pagination_size + pagination_size] 7909 7910 context.update({"assigned_roles": self.assigned_roles, 7911 "pagination_size": pagination_size, 7912 "pagination_offset": pagination_offset, 7913 "pagination_list": pagination_list, 7914 "pagination_pages": pagination_pages, 7915 }) 7916 7917 return context
7918 7919 # -------------------------------------------------------------------------
7920 - def get_realm(self):
7921 """ 7922 Returns the realm (list of pe_ids) that this user can manage 7923 or raises a permission error if the user is not logged in 7924 """ 7925 7926 auth = current.auth 7927 system_roles = auth.get_system_roles() 7928 ORG_ADMIN = system_roles.ORG_ADMIN 7929 ADMIN = system_roles.ADMIN 7930 7931 if auth.user: 7932 realms = auth.user.realms 7933 else: 7934 # User is not logged in 7935 auth.permission.fail() 7936 7937 # Get the realm from the current realms 7938 if ADMIN in realms: 7939 return realms[ADMIN] 7940 elif ORG_ADMIN in realms: 7941 return realms[ORG_ADMIN] 7942 else: 7943 # raise an error here - user is not permitted 7944 # to access the role matrix 7945 auth.permission.fail()
7946 7947 # -------------------------------------------------------------------------
7948 - def get_modules(self):
7949 """ 7950 This returns an OrderedDict of modules with their uid as the key, 7951 e.g., {hrm: "Human Resources",} 7952 7953 @return: OrderedDict 7954 """ 7955 return current.deployment_settings.get_auth_role_modules()
7956 7957 # -------------------------------------------------------------------------
7958 - def get_access_levels(self):
7959 """ 7960 This returns an OrderedDict of access levels and their uid as 7961 the key, e.g., {reader: "Reader",} 7962 7963 @return: OrderedDict 7964 """ 7965 return current.deployment_settings.get_auth_access_levels()
7966 7967 # -------------------------------------------------------------------------
7968 - def get_assigned_roles(self, entity_id=None, user_id=None):
7969 """ 7970 If an entity ID is provided, the dict will be the users 7971 with roles assigned to that entity. The key will be the user IDs. 7972 7973 If a user ID is provided, the dict will be the entities the 7974 user has roles for. The key will be the entity pe_ids. 7975 7976 If both an entity and user ID is provided, the dict will be 7977 the roles assigned to that user for that entity. The key will be 7978 the user ID. 7979 7980 @type entity_id: int 7981 @param entity_id: the pe_id of the entity 7982 @type user_id: int 7983 @param user_id: id of the user account 7984 7985 @return: dict 7986 { 7987 1: [ 7988 "staff_reader", 7989 "project_reader", 7990 ... 7991 ] 7992 2: [ 7993 ... 7994 ], 7995 ... 7996 } 7997 """ 7998 7999 if not entity_id and not user_id: 8000 raise RuntimeError("Not enough arguments") 8001 8002 mtable = current.auth.settings.table_membership 8003 gtable = current.auth.settings.table_group 8004 utable = current.auth.settings.table_user 8005 8006 query = (mtable.deleted != True) & \ 8007 (gtable.deleted != True) & \ 8008 (gtable.id == mtable.group_id) & \ 8009 (utable.deleted != True) & \ 8010 (utable.id == mtable.user_id) 8011 8012 if user_id: 8013 field = mtable.pe_id 8014 query &= (mtable.user_id == user_id) & \ 8015 (mtable.pe_id != None) 8016 8017 if entity_id: 8018 field = utable.id 8019 query &= (mtable.pe_id == entity_id) 8020 8021 rows = current.db(query).select(utable.id, 8022 gtable.uuid, 8023 mtable.pe_id) 8024 8025 assigned_roles = OrderedDict() 8026 roles = self.roles 8027 for row in rows: 8028 object_id = row[field] 8029 role_uid = row[gtable.uuid] 8030 8031 if role_uid in roles: 8032 if object_id not in assigned_roles: 8033 assigned_roles[object_id] = [] 8034 assigned_roles[object_id].append(role_uid) 8035 8036 return assigned_roles
8037 8038 # -------------------------------------------------------------------------
8039 - def get_form(self):
8040 """ 8041 Contructs the role form 8042 8043 @return: SQLFORM 8044 """ 8045 8046 fields = self.get_form_fields() 8047 form = SQLFORM.factory(*fields, 8048 table_name="roles", 8049 _id="role-form", 8050 _action="", 8051 _method="POST") 8052 return form
8053 8054 # -------------------------------------------------------------------------
8055 - def get_form_fields(self):
8056 """ 8057 @todo: description? 8058 8059 @return: list of Fields 8060 """ 8061 8062 fields = [] 8063 requires = IS_EMPTY_OR(IS_IN_SET(self.acls.keys(), 8064 labels=self.acls.values())) 8065 for module_uid, module_label in self.modules.items(): 8066 field = Field(module_uid, 8067 label=module_label, 8068 requires=requires) 8069 fields.append(field) 8070 return fields
8071 8072 # -------------------------------------------------------------------------
8073 - def get_form_vars(self):
8074 """ 8075 Get the roles currently assigned for a user/entity and put it 8076 into a Storage object for the form 8077 8078 @return: Storage() to pre-populate the role form 8079 """ 8080 8081 form_vars = Storage() 8082 8083 fo = self.foreign_object 8084 roles = self.roles 8085 if fo and fo["id"] in self.assigned_roles: 8086 for role in self.assigned_roles[fo["id"]]: 8087 mod_uid = roles[role]["module"]["uid"] 8088 acl_uid = roles[role]["acl"]["uid"] 8089 form_vars[mod_uid] = acl_uid 8090 8091 return form_vars
8092 8093 # -------------------------------------------------------------------------
8094 - def update_roles(self, user_id, entity_id, before, after):
8095 """ 8096 Update the users roles on entity based on the selected roles 8097 in before and after 8098 8099 @param user_id: id (pk) of the user account to modify 8100 @param entity_id: id of the pentity to modify roles for 8101 @param before: list of role_uids (current values for the user) 8102 @param after: list of role_uids (new values from the admin) 8103 """ 8104 8105 auth = current.auth 8106 assign_role = auth.s3_assign_role 8107 withdraw_role = auth.s3_withdraw_role 8108 8109 for role_uid in before: 8110 # If role_uid is not in after, 8111 # the access level has changed. 8112 if role_uid not in after: 8113 withdraw_role(user_id, role_uid, entity_id) 8114 8115 for role_uid in after: 8116 # If the role_uid is not in before, 8117 # the access level has changed 8118 if role_uid != "None" and role_uid not in before: 8119 assign_role(user_id, role_uid, entity_id)
8120
8121 # ============================================================================= 8122 -class S3OrgRoleManager(S3EntityRoleManager):
8123
8124 - def __init__(self, *args, **kwargs):
8125 """ 8126 Constructor 8127 """ 8128 8129 super(S3OrgRoleManager, self).__init__(*args, **kwargs) 8130 8131 # dictionary {id: name, ...} of user accounts 8132 self.objects = current.s3db.pr_realm_users(None)
8133 8134 # -------------------------------------------------------------------------
8135 - def get_context_data(self, r, **attr):
8136 """ 8137 Override to set the context from the perspective of an entity 8138 8139 @return: dictionary for view 8140 """ 8141 8142 context = super(S3OrgRoleManager, self).get_context_data(r, **attr) 8143 context["foreign_object_label"] = current.T("Users") 8144 return context
8145 8146 # -------------------------------------------------------------------------
8147 - def get_entity(self):
8148 """ 8149 We are on an entity (org/office) so we can fetch the entity 8150 details from the request record. 8151 8152 @return: dictionary containing the ID and name of the entity 8153 """ 8154 8155 entity_id = int(self.request.record.pe_id) 8156 8157 entity = {"id": entity_id, 8158 "name": current.s3db.pr_get_entities( 8159 pe_ids = [entity_id], 8160 types = self.ENTITY_TYPES, 8161 )[entity_id], 8162 } 8163 8164 return entity
8165 8166 # -------------------------------------------------------------------------
8167 - def get_user(self):
8168 """ 8169 The edit parameter 8170 8171 @return: dictionary containing the ID and username/email of 8172 the user account. 8173 """ 8174 8175 user = self.request.get_vars.get("edit", None) 8176 if user: 8177 user = dict(id=int(user), name=self.objects.get(int(user), None)) 8178 return user
8179 8180 # -------------------------------------------------------------------------
8181 - def get_foreign_object(self):
8182 """ 8183 We are on an entity so our target is a user account. 8184 8185 @return: dictionary with ID and username/email of user account 8186 """ 8187 8188 return self.user
8189 8190 # -------------------------------------------------------------------------
8191 - def get_assigned_roles(self, entity_id=None, user_id=None):
8192 """ 8193 Override to get assigned roles for this entity 8194 8195 @return: dictionary with user IDs as the keys. 8196 """ 8197 8198 assigned_roles = super(S3OrgRoleManager, self).get_assigned_roles 8199 8200 return assigned_roles(entity_id=self.entity["id"])
8201 8202 # -------------------------------------------------------------------------
8203 - def get_form_fields(self):
8204 """ 8205 Override the standard method so we can add the user-selection 8206 field to the list. 8207 8208 @return: list of Fields 8209 """ 8210 8211 T = current.T 8212 8213 fields = super(S3OrgRoleManager, self).get_form_fields() 8214 8215 if not self.user: 8216 assigned_roles = self.assigned_roles 8217 8218 realm_users = dict((k, v) 8219 for k, v in self.realm_users.items() 8220 if k not in assigned_roles) 8221 8222 other_users = dict((k, v) 8223 for k, v in self.objects.items() 8224 if k not in assigned_roles and \ 8225 k not in self.realm_users) 8226 8227 options = [("", ""), 8228 (T("Users in my Organizations"), realm_users), 8229 (T("Other Users"), other_users), 8230 ] 8231 8232 widget = lambda field, value: \ 8233 S3GroupedOptionsWidget.widget(field, 8234 value, 8235 options=options, 8236 ) 8237 8238 object_field = Field("foreign_object", 8239 label = T("User"), 8240 requires = IS_IN_SET(self.objects), 8241 widget = widget, 8242 ) 8243 fields.insert(0, object_field) 8244 8245 return fields
8246
8247 # ============================================================================= 8248 -class S3PersonRoleManager(S3EntityRoleManager):
8249 """ Role Manager for Person Records """ 8250
8251 - def __init__(self, *args, **kwargs):
8252 """ Constructor """ 8253 8254 super(S3PersonRoleManager, self).__init__(*args, **kwargs) 8255 8256 # dictionary {id: name, ...} of pentities 8257 self.objects = current.s3db.pr_get_entities(types=self.ENTITY_TYPES)
8258 8259 # -------------------------------------------------------------------------
8260 - def get_context_data(self, r, **attr):
8261 """ 8262 Override to set the context from the perspective of a person 8263 8264 @return: dictionary for view 8265 """ 8266 8267 context = super(S3PersonRoleManager, self).get_context_data(r, **attr) 8268 context["foreign_object_label"] = current.T("Organizations / Teams / Facilities") 8269 return context
8270 8271 # -------------------------------------------------------------------------
8272 - def get_entity(self):
8273 """ 8274 An entity needs to be specified with the "edit" query string 8275 parameter. 8276 8277 @return: dictionary with pe_id and name of the org/office. 8278 """ 8279 8280 entity = self.request.get_vars.get("edit", None) 8281 if entity: 8282 entity = dict(id=int(entity), 8283 name=self.objects.get(int(entity), None)) 8284 return entity
8285 8286 # -------------------------------------------------------------------------
8287 - def get_user(self):
8288 """ 8289 We are on a person record so we need to find the associated user 8290 account. 8291 8292 @return: dictionary with ID and username/email of the user account 8293 """ 8294 8295 settings = current.auth.settings 8296 utable = settings.table_user 8297 ptable = current.s3db.pr_person_user 8298 8299 pe_id = int(self.request.record.pe_id) 8300 8301 userfield = settings.login_userfield 8302 8303 query = (ptable.pe_id == pe_id) & \ 8304 (ptable.user_id == utable.id) 8305 record = current.db(query).select(utable.id, 8306 utable[userfield], 8307 limitby=(0, 1)).first() 8308 8309 return dict(id=record.id, 8310 name=record[utable[userfield]]) if record else None
8311 8312 # -------------------------------------------------------------------------
8313 - def get_foreign_object(self):
8314 """ 8315 We are on a user/person so we want to target an entity (org/office) 8316 """ 8317 8318 return self.entity
8319 8320 # -------------------------------------------------------------------------
8321 - def get_assigned_roles(self, entity_id=None, user_id=None):
8322 """ 8323 @todo: description? 8324 8325 @return: dictionary of assigned roles with entity pe_id as the keys 8326 """ 8327 8328 assigned_roles = super(S3PersonRoleManager, self).get_assigned_roles 8329 8330 return assigned_roles(user_id=self.user["id"])
8331 8332 # -------------------------------------------------------------------------
8333 - def get_form_fields(self):
8334 """ 8335 Return a list of fields, including a field for selecting 8336 a realm entity (such as an organisation or office). 8337 8338 @return: list of Fields 8339 """ 8340 8341 s3db = current.s3db 8342 fields = super(S3PersonRoleManager, self).get_form_fields() 8343 8344 if not self.entity: 8345 options = s3db.pr_get_entities(pe_ids=self.realm, 8346 types=self.ENTITY_TYPES, 8347 group=True) 8348 8349 nice_name = s3db.table("pr_pentity").instance_type.represent 8350 8351 # filter out options that already have roles assigned 8352 filtered_options = [] 8353 for entity_type, entities in options.items(): 8354 entities = Storage([(entity_id, entity_name) 8355 for entity_id, entity_name 8356 in entities.items() 8357 if entity_id not in self.assigned_roles]) 8358 filtered_options.append((nice_name(entity_type), entities)) 8359 8360 widget = lambda field, value: \ 8361 S3GroupedOptionsWidget.widget(field, 8362 value, 8363 options=filtered_options, 8364 ) 8365 8366 object_field = Field("foreign_object", 8367 label = current.T("Entity"), 8368 requires = IS_IN_SET(self.objects), 8369 widget = widget, 8370 ) 8371 fields.insert(0, object_field) 8372 8373 return fields
8374 8375 # END ========================================================================= 8376