1
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
42 import time
43
44 from collections import OrderedDict
45 from uuid import uuid4
46
47
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
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
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
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
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
183
184
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
206 self.permission = S3Permission(self)
207
208
209 self.override = False
210
211
212
213 self.rollback = False
214
215
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"),
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
237
238 self.PROTECTED = ("admin",)
239
240
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
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
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
308 S3MetaFields.uuid(),
309
310 S3MetaFields.created_on(),
311 S3MetaFields.modified_on(),
312 ]
313
314 userfield = settings.login_userfield
315 if userfield != "email":
316
317 utable_fields.insert(2, Field(userfield, length=128,
318 default="",
319 unique=True))
320
321
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
337
338
339
340
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
354 gtable = settings.table_group
355 gname = settings.table_group_name
356 if not gtable:
357 define_table(gname,
358
359 Field("uuid", length=64, notnull=True, unique=True,
360 readable=False, writable=False),
361
362
363 Field("hidden", "boolean",
364 readable=False, writable=False,
365 default=False),
366
367
368 Field("system", "boolean",
369 readable=False, writable=False,
370 default=False),
371
372
373 Field("protected", "boolean",
374 readable=False, writable=False,
375 default=False),
376
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
384 S3MetaFields.created_on(),
385 S3MetaFields.modified_on(),
386 S3MetaFields.deleted(),
387
388
389 migrate = migrate,
390 fake_migrate=fake_migrate,
391 )
392 gtable = settings.table_group = db[gname]
393
394
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
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
415 self.permission.define_table(migrate=migrate,
416 fake_migrate=fake_migrate)
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
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
452 ),
453 Field("client_ip",
454 default=request.client,
455
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
461 ),
462 Field("origin", default="auth", length=512,
463
464 requires = IS_NOT_EMPTY()),
465 Field("description", "text", default="",
466
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
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
500 """
501 Set a Cookie to the client browser so that we know this user has
502 registered & so we should present them with a login form instead
503 of a register form
504 """
505
506 cookies = current.response.cookies
507
508 cookies["registered"] = "yes"
509 cookies["registered"]["expires"] = 365 * 24 * 3600
510 cookies["registered"]["path"] = "/"
511
512
513 - def login(self,
514 next = DEFAULT,
515 onvalidation = DEFAULT,
516 onaccept = DEFAULT,
517 log = DEFAULT,
518 inline = False,
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
554
555 response.title = T("Login")
556
557
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
568 self_registration = deployment_settings.get_security_registration_visible()
569 if self_registration and register_link:
570 if self_registration == "index":
571
572 controller = "index"
573 else:
574
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
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
595
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
611 form.add_class("auth_login")
612
613 if settings.remember_me_form:
614
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
662
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
676 query = (utable[userfield] == form.vars[userfield])
677 user = db(query).select(limitby=(0, 1)).first()
678 if user:
679
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
693
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
701 form.vars[passfield] = None
702 user = self.get_or_create_user(form.vars)
703 break
704 if not user:
705
706 if settings.login_methods[0] == self:
707
708 if temp_user[passfield] == form.vars.get(passfield, ""):
709
710 user = temp_user
711 else:
712
713 if not settings.alternate_requires_registration:
714
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
721 form.vars[passfield] = None
722
723 register_onaccept = settings.register_onaccept
724 if register_onaccept:
725 settings.register_onaccept = \
726 [self.s3_register_onaccept,
727 register_onaccept,
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
737 session.error = messages.invalid_login
738 if inline:
739
740 next_url = URL(args=request.args,
741 vars=request.get_vars)
742 else:
743
744 next_url = self.url(args=request.args,
745 vars=request.get_vars)
746 redirect(next_url)
747 else:
748
749 cas = settings.login_form
750 cas_user = cas.get_user()
751 if cas_user:
752 cas_user[passfield] = None
753
754 register_onaccept = settings.register_onaccept
755 if register_onaccept:
756 settings.register_onaccept = \
757 [self.s3_register_onaccept,
758 register_onaccept,
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
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
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
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
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
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
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
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
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
1051 return form
1052
1053
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
1068
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
1084 self.set_cookie()
1085
1086
1087 language = user.language
1088 current.T.force(language)
1089 session.s3.language = language
1090
1091 session.confirmation = self.messages.logged_in
1092
1093
1094 utable = settings.table_user
1095 db(utable.id == self.user.id).update(timestmp = request.utcnow)
1096
1097
1098
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
1106 closestpoint = 0
1107 closestdistance = 0
1108 gis = current.gis
1109
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
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
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
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
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
1224 form.add_class("auth_register")
1225
1226 if js_validation:
1227
1228 self.s3_register_validation()
1229
1230
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
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
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
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
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
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
1377 self.s3_approve_user(form.vars)
1378 current.session.confirmation = self.messages.registration_successful
1379
1380
1381 admin_group_id = 1
1382 self.add_membership(admin_group_id, users.first().id)
1383
1384
1385 if "language" not in form.vars:
1386
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
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
1406
1407 next = URL(c="default", f="message",
1408 args = ["verify_email_sent"],
1409 vars = {"email": form.vars.email})
1410
1411 else:
1412
1413 approved = self.s3_verify_user(form.vars)
1414
1415 if approved:
1416
1417 if "language" not in form.vars:
1418
1419 form.vars.language = T.accepted_language
1420 user = Storage(utable._filter_fields(form.vars, id=True))
1421 self.login_user(user)
1422
1423
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
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
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)
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
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
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
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
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
1574
1575
1576 utable.organisation_id.writable = False
1577 utable.organisation_id.comment = None
1578
1579
1580
1581
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
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
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
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
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)):
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
1967
1968
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
1998 if parent:
1999 records = db(otable.name == parent).select(otable.id)
2000 if len(records) == 1:
2001
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
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
2011 parent_id = otable.insert(name=parent)
2012 update_super(otable, Storage(id=parent_id))
2013 set_record_owner(otable, parent_id)
2014
2015
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
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
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
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
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
2075 (organisation_id, pe_id) = add_org(org, parent)
2076
2077 elif ORG_ADMIN:
2078
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
2084 (organisation_id, pe_id) = add_org(org, parent)
2085
2086 return (organisation_id, pe_id)
2087
2088
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
2100 element.text = looked_up[pe_tablename][pe_value]["pe_id"]
2101
2102 continue
2103
2104 if pe_tablename == "org_organisation" and pe_field == "name":
2105
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,
2112 table.pe_id,
2113 limitby=(0, 1)
2114 ).first()
2115 if record:
2116 record_id = record.id
2117 else:
2118
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
2127 element.text = new_value
2128
2129 looked_up[pe_tablename][pe_value] = dict(pe_id=new_value,
2130 id=str(record_id),
2131 )
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198 @staticmethod
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
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
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
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
2275 s3.jquery_ready.append('''s3_register_validation()''')
2276
2277
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
2288 sql_query="insert into ofGroupUser values (\'%s\',\'%s\' ,0);" % (chat_server["groupname"], chat_username)
2289 chatdb.executesql(sql_query)
2290
2291
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
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
2338
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
2345 home = form_vars.home
2346 if home:
2347 record["home"] = home
2348
2349
2350 mobile = form_vars.mobile
2351 if mobile:
2352 record["mobile"] = mobile
2353
2354
2355 image = form_vars.image
2356 if image != None and hasattr(image, "file"):
2357
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
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
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
2402 session.information = deployment_settings.get_auth_registration_pending_approval()
2403 else:
2404
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
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
2427 if "@" in approver:
2428
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
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
2496 result = None
2497 if not result:
2498
2499
2500 current.response.error = self.messages.email_send_failed
2501 return False
2502
2503 return approved
2504
2505
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
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
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
2555 continue
2556
2557
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
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
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
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
2606 db(utable.id == user_id).update(registration_key = "")
2607
2608
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
2615
2616 approved = False,
2617 unapproved = True,
2618 )
2619 approved = org_resource.approve()
2620 if not approved:
2621
2622
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
2629 self.s3_send_welcome_email(user, password)
2630
2631
2633 """
2634 S3 framework function
2635
2636 Designed to be called when a user is created & approved through:
2637 - prepop
2638 - approved automatically during registration
2639 - approved by admin
2640 - added by admin
2641 - updated by admin
2642
2643 Does the following:
2644 - Calls s3_link_to_organisation:
2645 Creates (if not existing) User's Organisation and links User
2646 - Calls s3_link_to_person:
2647 Creates (if not existing) User's Person Record and links User
2648 - Calls s3_link_to_human_resource:
2649 Creates (if not existing) User's Human Resource Record and links User
2650 - Calls s3_link_to_member
2651 """
2652
2653
2654 organisation_id = self.s3_link_to_organisation(user)
2655
2656
2657 person_id = self.s3_link_to_person(user, organisation_id)
2658
2659 if user.org_group_id:
2660 self.s3_link_to_org_group(user, person_id)
2661
2662 utable = self.settings.table_user
2663
2664 link_user_to = user.link_user_to or utable.link_user_to.default
2665
2666 if link_user_to:
2667 if "staff" in link_user_to:
2668
2669 self.s3_link_to_human_resource(user, person_id, hr_type=1)
2670 if "volunteer" in link_user_to:
2671
2672 self.s3_link_to_human_resource(user, person_id, hr_type=2)
2673 if "member" in link_user_to:
2674
2675 self.s3_link_to_member(user, person_id)
2676
2677
2678 @staticmethod
2680 """ Update the UI locale from user profile """
2681
2682 if form.vars.language:
2683 current.session.s3.language = form.vars.language
2684
2685
2689 """
2690 Links user accounts to person registry entries
2691
2692 @param user: the user record
2693 @param organisation_id: the user's organisation_id
2694 to get the person's realm_entity
2695
2696 Policy for linking to pre-existing person records:
2697
2698 If this user is already linked to a person record with a different
2699 first_name, last_name, email or realm_entity these will be
2700 updated to those of the user.
2701
2702 If a person record with exactly the same first name and
2703 last name exists, which has a contact information record
2704 with exactly the same email address as used in the user
2705 account, and is not linked to another user account, then
2706 this person record will be linked to this user account.
2707
2708 Otherwise, a new person record is created, and a new email
2709 contact record with the email address from the user record
2710 is registered for that person.
2711 """
2712
2713 db = current.db
2714 s3db = current.s3db
2715
2716 utable = self.settings.table_user
2717
2718 ttable = s3db.auth_user_temp
2719 ptable = s3db.pr_person
2720 ctable = s3db.pr_contact
2721 atable = s3db.pr_address
2722 gctable = s3db.gis_config
2723 ltable = s3db.pr_person_user
2724
2725
2726
2727 if organisation_id:
2728 org_pe_id = s3db.pr_get_pe_id("org_organisation",
2729 organisation_id)
2730 else:
2731 org_pe_id = None
2732
2733 left = [ltable.on(ltable.user_id == utable.id),
2734 ptable.on(ptable.pe_id == ltable.pe_id),
2735 ttable.on(utable.id == ttable.user_id)]
2736
2737 if user is not None:
2738 if not isinstance(user, (list, tuple)):
2739 user = [user]
2740 user_ids = [u.id for u in user]
2741 query = (utable.id.belongs(user_ids))
2742 else:
2743 query = (utable.id != None)
2744
2745 fields = [utable.id,
2746 utable.first_name,
2747 utable.last_name,
2748 utable.email,
2749 ltable.pe_id,
2750 ptable.id,
2751 ptable.first_name,
2752 ttable.home,
2753 ttable.mobile,
2754 ttable.image,
2755 ]
2756 middle_name = current.deployment_settings.get_L10n_mandatory_middlename()
2757 if middle_name:
2758
2759 fields.append(ptable.middle_name)
2760 else:
2761 fields.append(ptable.last_name)
2762
2763 rows = db(query).select(*fields,
2764 left=left, distinct=True)
2765
2766 person_ids = []
2767
2768 if current.request.vars.get("opt_in", None):
2769 opt_in = current.deployment_settings.get_auth_opt_in_team_list()
2770 else:
2771 opt_in = []
2772
2773 for row in rows:
2774
2775
2776 user = row.auth_user
2777
2778
2779 tuser = row.auth_user_temp
2780
2781
2782 person = row.pr_person
2783
2784
2785 link = row.pr_person_user
2786
2787 pe_id = link.pe_id
2788 if pe_id is not None:
2789
2790
2791
2792
2793 if user.first_name != person.first_name or \
2794 (not middle_name and user.last_name != person.last_name) or \
2795 (middle_name and user.last_name != person.middle_name):
2796 query = (ptable.pe_id == pe_id)
2797 if middle_name:
2798 db(query).update(first_name = user.first_name,
2799 middle_name = user.last_name,
2800 )
2801 else:
2802 db(query).update(first_name = user.first_name,
2803 last_name = user.last_name,
2804 )
2805
2806
2807 query = (ctable.pe_id == pe_id) & \
2808 (ctable.contact_method == "EMAIL") & \
2809 (ctable.value == user.email)
2810 item = db(query).select(ctable.id,
2811 limitby=(0, 1)).first()
2812 if item is None:
2813 ctable.insert(pe_id = pe_id,
2814 contact_method = "EMAIL",
2815 value = user.email,
2816 )
2817
2818
2819 if tuser.mobile:
2820 query = (ctable.pe_id == pe_id) & \
2821 (ctable.contact_method == "SMS") & \
2822 (ctable.value == tuser.mobile)
2823 item = db(query).select(ctable.id,
2824 limitby=(0, 1)).first()
2825 if item is None:
2826 ctable.insert(pe_id = pe_id,
2827 contact_method = "SMS",
2828 value = tuser.mobile,
2829 )
2830
2831
2832
2833 person_ids.append(person.id)
2834
2835 else:
2836
2837
2838
2839
2840 first_name = user.first_name
2841 last_name = user.last_name
2842 email = user.email.lower()
2843 if email:
2844 if middle_name:
2845 mquery = (ptable.middle_name == last_name)
2846 else:
2847 mquery = (ptable.last_name == last_name)
2848 query = (ptable.first_name == first_name) & \
2849 mquery & \
2850 (ctable.pe_id == ptable.pe_id) & \
2851 (ctable.contact_method == "EMAIL") & \
2852 (ctable.value.lower() == email)
2853 person = db(query).select(ptable.id,
2854 ptable.pe_id,
2855 limitby=(0, 1)).first()
2856 else:
2857
2858 person = None
2859
2860
2861 owner = Storage(owned_by_user=user.id)
2862
2863 if person:
2864 other = db(ltable.pe_id == person.pe_id).select(ltable.id,
2865 limitby=(0, 1),
2866 ).first()
2867 if person and not other:
2868
2869
2870 pe_id = person.pe_id
2871
2872
2873 realm_entity = self.get_realm_entity(ptable, person)
2874 if not realm_entity:
2875
2876 realm_entity = org_pe_id
2877 owner.realm_entity = realm_entity
2878
2879
2880 ltable.insert(user_id=user.id, pe_id=pe_id)
2881
2882
2883 person.update_record(**owner)
2884
2885
2886 db(ctable.pe_id == pe_id).update(**owner)
2887
2888
2889 db(atable.pe_id == pe_id).update(**owner)
2890
2891
2892 db(gctable.pe_id == pe_id).update(**owner)
2893
2894
2895 if self.user and self.user.id == user.id:
2896 self.user.pe_id = pe_id
2897
2898 person_ids.append(person.id)
2899
2900 else:
2901
2902
2903
2904
2905 if middle_name:
2906 person_id = ptable.insert(first_name = first_name,
2907 middle_name = last_name,
2908 opt_in = opt_in,
2909 modified_by = user.id,
2910 **owner)
2911 else:
2912 person_id = ptable.insert(first_name = first_name,
2913 last_name = last_name,
2914 opt_in = opt_in,
2915 modified_by = user.id,
2916 **owner)
2917 if person_id:
2918
2919
2920 person = Storage(id=person_id)
2921 s3db.update_super(ptable, person)
2922 pe_id = person.pe_id
2923
2924
2925 realm_entity = self.get_realm_entity(ptable, person)
2926 if not realm_entity:
2927
2928 realm_entity = org_pe_id
2929 self.set_realm_entity(ptable, person,
2930 entity=realm_entity,
2931 )
2932 owner.realm_entity = realm_entity
2933
2934
2935 ltable.insert(user_id=user.id, pe_id=pe_id)
2936
2937
2938 ctable.insert(pe_id = pe_id,
2939 contact_method = "EMAIL",
2940 priority = 1,
2941 value = email,
2942 **owner)
2943
2944
2945 gtable = s3db.pr_group
2946 mtable = s3db.pr_group_membership
2947
2948 for team in opt_in:
2949 team_rec = db(gtable.name == team).select(gtable.id,
2950 limitby=(0, 1)
2951 ).first()
2952
2953 if team_rec == None:
2954 team_id = gtable.insert(name = team,
2955 group_type = 5)
2956 else:
2957 team_id = team_rec.id
2958 mtable.insert(group_id = team_id,
2959 person_id = person_id,
2960 )
2961
2962 person_ids.append(person_id)
2963
2964 else:
2965 pe_id = None
2966
2967 if pe_id is not None:
2968
2969 tuser = row.auth_user_temp
2970
2971
2972
2973 mobile = tuser.mobile
2974 if mobile:
2975 ctable.insert(pe_id = pe_id,
2976 contact_method = "SMS",
2977 priority = 2,
2978 value = mobile,
2979 **owner)
2980
2981
2982
2983 home = tuser.home
2984 if home:
2985 ctable.insert(pe_id = pe_id,
2986 contact_method = "HOME_PHONE",
2987 priority = 3,
2988 value = home,
2989 **owner)
2990
2991
2992
2993 image = tuser.image
2994 if image:
2995 itable = s3db.pr_image
2996 url = URL(c="default", f="download", args=image)
2997 itable.insert(pe_id=pe_id,
2998 profile=True,
2999 image=image,
3000 url = url,
3001 description=current.T("Profile Picture"))
3002
3003
3004 if self.user and self.user.id == user.id:
3005 self.user.pe_id = pe_id
3006
3007 if len(person_ids) == 1:
3008 return person_ids[0]
3009 else:
3010 return person_ids
3011
3012
3014 """
3015 Link a user account to an organisation
3016
3017 @param user: the user account record
3018 """
3019
3020 db = current.db
3021 s3db = current.s3db
3022
3023 user_id = user.id
3024
3025
3026 organisation_id = self.s3_approver(user)[1]
3027 if organisation_id:
3028 user.organisation_id = organisation_id
3029 else:
3030
3031 organisation_id = user.organisation_id
3032
3033
3034
3035
3036 if not organisation_id:
3037
3038 name = user.get("organisation_name", None)
3039 if name:
3040
3041 acronym = user.get("organisation_acronym", None)
3042 otable = s3db.org_organisation
3043 record = Storage(name=name,
3044 acronym=acronym)
3045 organisation_id = otable.insert(**record)
3046
3047
3048 if organisation_id:
3049 record["id"] = organisation_id
3050 s3db.update_super(otable, record)
3051 s3db.onaccept(otable, record, method="create")
3052 self.s3_set_record_owner(otable, organisation_id)
3053
3054
3055 user.organisation_id = organisation_id
3056 utable = self.settings.table_user
3057 db(utable.id == user_id).update(organisation_id=organisation_id)
3058
3059 if not organisation_id:
3060 return None
3061
3062
3063 ltable = s3db.org_organisation_user
3064
3065
3066 query = (ltable.user_id == user_id)
3067 rows = db(query).select(ltable.organisation_id,
3068 limitby=(0, 2))
3069 if len(rows) == 1:
3070
3071 if rows.first().organisation_id != organisation_id:
3072 db(query).update(organisation_id=organisation_id)
3073
3074 return organisation_id
3075 else:
3076
3077 query = (ltable.user_id == user_id) & \
3078 (ltable.organisation_id == organisation_id)
3079 row = db(query).select(ltable.id, limitby=(0, 1)).first()
3080 if not row:
3081 ltable.insert(user_id=user_id,
3082 organisation_id=organisation_id)
3083
3084 return organisation_id
3085
3086
3088 """
3089 Link a user account to an organisation group
3090
3091 @param user: the user account record
3092 @param person_id: the person record ID associated with this user
3093 """
3094
3095 db = current.db
3096 s3db = current.s3db
3097
3098 org_group_id = user.get("org_group_id")
3099 if not org_group_id or not person_id:
3100 return None
3101
3102
3103 stable = s3db.org_group_person_status
3104 query = (stable.name.lower() == "member") & \
3105 (stable.deleted != True)
3106 row = db(query).select(stable.id, limitby=(0, 1)).first()
3107 if row:
3108 status_id = row.id
3109 else:
3110 status_id = None
3111
3112
3113 ltable = s3db.org_group_person
3114 query = (ltable.person_id == person_id) & \
3115 (ltable.org_group_id == org_group_id) & \
3116 (ltable.deleted != True)
3117 row = db(query).select(ltable.id, limitby=(0, 1)).first()
3118 if not row:
3119
3120 ptable = s3db.pr_person
3121 gtable = s3db.org_group
3122 if ptable[person_id] and gtable[org_group_id]:
3123 ltable.insert(person_id=person_id,
3124 org_group_id=org_group_id,
3125 status_id=status_id,
3126 )
3127 return org_group_id
3128
3129
3135 """
3136 Take ownership of the HR records of the person record
3137 @ToDo: Add user to the Org Access role.
3138 """
3139
3140 db = current.db
3141 s3db = current.s3db
3142 settings = current.deployment_settings
3143
3144 user_id = user.id
3145 organisation_id = user.organisation_id
3146
3147 htablename = "hrm_human_resource"
3148 htable = s3db.table(htablename)
3149
3150 if not htable or (not organisation_id and \
3151 settings.get_hrm_org_required()):
3152
3153 return None
3154
3155 def customise(hr_id):
3156 """ Customise hrm_human_resource """
3157 customise = settings.customise_resource(htablename)
3158 if customise:
3159 if hr_id:
3160 args = [str(hr_id)]
3161 else:
3162 args = None
3163 request = S3Request("hrm", "human_resource",
3164 current.request,
3165 args = args,
3166 )
3167 customise(request, htablename)
3168
3169
3170 site_id = user.site_id if hr_type == 1 else None
3171
3172
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
3186
3187 record = rows.first()
3188 hr_id = record.id
3189
3190
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
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
3207
3208 if rows:
3209
3210
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
3221 row = None
3222
3223 if row:
3224
3225
3226 hr_id = row.id
3227
3228 else:
3229
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
3244 s3db.update_super(htable, record)
3245
3246
3247
3248 self.s3_set_record_owner(htable, hr_id, force_update=True)
3249
3250
3251 s3db.onaccept(htablename, record, method=accepted)
3252
3253 return hr_id
3254
3255
3260 """
3261 Link to a member Record
3262 """
3263
3264 db = current.db
3265 s3db = current.s3db
3266
3267 user_id = user.id
3268 organisation_id = user.organisation_id
3269
3270 mtablename = "member_membership"
3271 mtable = s3db.table(mtablename)
3272
3273 if not mtable or not organisation_id:
3274 return None
3275
3276
3277 ptable = s3db.pr_person
3278 ltable = s3db.pr_person_user
3279 query = (mtable.deleted == False) & \
3280 (mtable.person_id == ptable.id) & \
3281 (ptable.pe_id == ltable.pe_id) & \
3282 (ltable.user_id == user_id)
3283 rows = db(query).select(mtable.id,
3284 limitby=(0, 2))
3285 if len(rows) == 1:
3286
3287 member_id = rows.first().id
3288 db(mtable.id == member_id).update(organisation_id = organisation_id)
3289
3290 self.s3_set_record_owner(mtable, member_id, force_update=True)
3291
3292
3293 if isinstance(person_id, list):
3294 person_ids = person_id
3295 else:
3296 person_ids = [person_id]
3297 query = (mtable.person_id.belongs(person_ids)) & \
3298 (mtable.organisation_id == organisation_id)
3299 row = db(query).select(mtable.id, limitby=(0, 1)).first()
3300
3301 if row:
3302 member_id = row.id
3303 else:
3304 record = Storage(person_id=person_ids[0],
3305 organisation_id=organisation_id,
3306 owned_by_user=user_id,
3307 )
3308 member_id = mtable.insert(**record)
3309 if member_id:
3310 record["id"] = member_id
3311
3312 customise = current.deployment_settings.customise_resource(mtablename)
3313 if customise:
3314 request = S3Request("member", "membership",
3315 current.request,
3316 args=[str(member_id)])
3317 customise(request, mtablename)
3318
3319 self.s3_set_record_owner(mtable, member_id)
3320 s3db.onaccept(mtablename, record, method="create")
3321
3322 return member_id
3323
3324
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
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
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
3367 approver = current.deployment_settings.get_mail_approver()
3368 if "@" not in approver:
3369
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
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
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
3406
3407 T = current.T
3408 language = user.get("language")
3409 if language:
3410 T.force(language)
3411
3412
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
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
3442
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
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
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
3481 language = user.language
3482 current.T.force(language)
3483 session.s3.language = language
3484
3485 return user
3486
3487
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
3499
3500
3501
3502 basic = self.basic()
3503 try:
3504 return basic[2]
3505 except TypeError:
3506
3507 return basic
3508 except:
3509 return False
3510
3511 return True
3512
3513
3514
3515
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
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
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
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
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
3634 session.s3.roles.extend(list(set([row.group_id for row in rows])))
3635
3636
3637
3638
3639
3640 if not permission.entity_realm:
3641
3642 self.user["realms"] = Storage([(row.group_id, None) for row in rows])
3643 self.user["delegations"] = Storage()
3644
3645 else:
3646
3647 realms = {}
3648 delegations = {}
3649
3650
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
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
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
3684
3685
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
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
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
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
3751 if permission.delegations:
3752 for row in rows:
3753
3754
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
3762 continue
3763 if partner not in pmap:
3764 continue
3765
3766
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
3786 self.user["realms"][ANONYMOUS] = None
3787
3788
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
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
3885 mtable = self.settings.table_membership
3886 db(mtable.group_id == group_id).update(**data)
3887
3888
3889 ptable = self.permission.table
3890 db(ptable.group_id == group_id).update(**data)
3891
3892
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
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
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
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
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
3967 mtable.insert(**membership)
3968
3969
3970 if self.user and str(user_id) == str(self.user.id):
3971 self.s3_set_roles()
3972
3973
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
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
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
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
4041 if self.user and str(user_id) == str(self.user.id):
4042 self.s3_set_roles()
4043
4044
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
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
4082 if self.override:
4083 return True
4084
4085 system_roles = self.get_system_roles()
4086 if role == system_roles.ANONYMOUS:
4087
4088 return True
4089
4090 s3 = current.session.s3
4091
4092
4093 self.s3_logged_in()
4094
4095
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
4107 if system_roles.ADMIN in realms:
4108 return True
4109
4110
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
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
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
4149 if self.override or not roles:
4150 return True
4151
4152
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
4165 system_roles = self.get_system_roles()
4166 if system_roles.ADMIN in realms:
4167 return True
4168
4169
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
4192 for role in check:
4193
4194 if role == system_roles.ANONYMOUS:
4195
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
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
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
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
4363 self.s3_set_roles()
4364
4365 return True
4366
4367
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
4404 gtable = self.settings.table_group
4405 query = None
4406
4407 if isinstance(group_id, (list, tuple)):
4408 if isinstance(group_id[0], str):
4409
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
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
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
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
4457 if len(rmv):
4458 self.s3_set_roles()
4459 return True
4460
4461
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
4518
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
4527
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
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
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
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
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
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
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
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
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
4647
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
4673 return None
4674
4675 policy = current.deployment_settings.get_security_policy()
4676
4677
4678 if policy == 1:
4679
4680 if method == "read":
4681 authorised = True
4682 else:
4683
4684 authorised = self.s3_logged_in()
4685
4686
4687 elif policy == 2:
4688
4689 if method == "read":
4690 authorised = True
4691 elif method == "create":
4692
4693 authorised = self.s3_logged_in()
4694 elif record_id == 0 and method == "update":
4695
4696 authorised = self.s3_logged_in()
4697 else:
4698
4699 authorised = self.s3_has_role(sr.EDITOR)
4700 if not authorised and self.user and "owned_by_user" in table:
4701
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
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
4717 else:
4718 if self.s3_logged_in():
4719
4720 if self.s3_has_role(sr.ADMIN):
4721 authorised = True
4722 else:
4723
4724
4725 authorised = self.has_permission(method, table, record_id)
4726 else:
4727
4728 authorised = False
4729
4730 return authorised
4731
4732
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
4759 return table.id > 0
4760 elif policy == 2:
4761
4762 return table.id > 0
4763 elif policy in (3, 4, 5, 6, 7, 8):
4764
4765 query = self.permission.accessible_query(method, table, c=c, f=f)
4766 return query
4767
4768
4769 if self.s3_has_role(sr.ADMIN):
4770
4771 return table.id > 0
4772
4773
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
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
4793
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
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)
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
4827 has_membership = s3_has_membership
4828
4829
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
4862 requires_membership = s3_requires_membership
4863
4864
4865
4866
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
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
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
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
4947 del session.owned_records[tablename]
4948 else:
4949
4950 session.owned_records = {}
4951
4952
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
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
4985 q = (table._id == record_id)
4986 success = db(q).update(**data)
4987
4988 if success and update and REALM in data:
4989
4990
4991
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
5015
5016 ctable = db[ctablename]
5017 db(ctable._id.belongs(ids)).update(**realm)
5018
5019
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
5059 OUSR = "owned_by_user"
5060 OGRP = "owned_by_group"
5061 REALM = "realm_entity"
5062
5063 ownership_fields = (OUSR, OGRP, REALM)
5064
5065
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
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
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
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
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
5112 data = Storage()
5113
5114
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
5132
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
5141 user_id = self.user.id
5142 if user_id:
5143 data[OUSR] = user_id
5144
5145
5146 if OGRP in fields_in_table:
5147
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
5155 elif OGRP in fields:
5156 data[OGRP] = fields[OGRP]
5157
5158
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
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
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
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
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
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
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
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
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
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
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
5298 if isinstance(entity, tuple):
5299 realm_entity = s3db.pr_get_pe_id(entity)
5300 else:
5301 realm_entity = entity
5302
5303
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
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
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
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
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
5450
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
5461
5462
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
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
5520 "root_org_%s" % org_id,
5521 lambda: current.s3db.org_root_organisation(org_id),
5522 time_expire=120
5523 )
5524
5525
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
5546 return None
5547 return current.cache.ram(
5548
5549 "root_org_name_%s" % org_id,
5550 lambda: current.s3db.org_root_organisation_name(org_id),
5551 time_expire=120
5552 )
5553
5554
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
5571 """ S3 Class to handle permissions """
5572
5573 TABLENAME = "s3_permission"
5574
5575 CREATE = 0x0001
5576 READ = 0x0002
5577 UPDATE = 0x0004
5578 DELETE = 0x0008
5579 REVIEW = 0x0010
5580 APPROVE = 0x0020
5581 PUBLISH = 0x0040
5582
5583 ALL = CREATE | READ | UPDATE | DELETE | REVIEW | APPROVE | PUBLISH
5584 NONE = 0x0000
5585
5586 PERMISSION_OPTS = OrderedDict([
5587 [CREATE, "CREATE"],
5588 [READ, "READ"],
5589 [UPDATE, "UPDATE"],
5590 [DELETE, "DELETE"],
5591 [REVIEW, "REVIEW"],
5592 [APPROVE, "APPROVE"],
5593
5594 ])
5595
5596
5597 METHODS = Storage({
5598 "create": CREATE,
5599 "read": READ,
5600 "update": UPDATE,
5601 "delete": DELETE,
5602 "map": READ,
5603 "report": READ,
5604
5605 "timeplot": READ,
5606 "import": CREATE,
5607 "review": REVIEW,
5608 "approve": APPROVE,
5609 "reject": APPROVE,
5610 "publish": PUBLISH,
5611 })
5612
5613
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
5638
5639
5640
5641
5642
5643 self.auth = auth
5644
5645 self.error = S3PermissionError
5646
5647 settings = current.deployment_settings
5648
5649
5650 self.policy = settings.get_security_policy()
5651
5652 self.use_cacls = self.policy in (3, 4, 5, 6, 7 ,8)
5653
5654 self.use_facls = self.policy in (4, 5, 6, 7, 8)
5655
5656 self.use_tacls = self.policy in (5, 6, 7, 8)
5657
5658 self.entity_realm = self.policy in (6, 7, 8)
5659
5660 self.entity_hierarchy = self.policy in (7, 8)
5661
5662 self.delegations = self.policy == 8
5663
5664
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
5672 T = current.T
5673 self.INSUFFICIENT_PRIVILEGES = T("Insufficient Privileges")
5674 self.AUTHENTICATION_REQUIRED = T("Authentication Required")
5675
5676
5677 request = current.request
5678 self.controller = request.controller
5679 self.function = request.function
5680
5681
5682 self.format = s3_get_extension()
5683
5684
5685 self.record_approval = settings.get_auth_record_approval()
5686 self.strict_ownership = settings.get_security_strict_ownership()
5687
5688
5689 self.clear_cache()
5690
5691
5692
5693
5694 self.unrestricted_pages = ("default/index",
5695 "default/user",
5696 "default/contact",
5697 "default/about")
5698
5699
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
5707 """ Clear any cached permissions or accessible-queries """
5708
5709 self.permission_cache = {}
5710 self.query_cache = {}
5711
5712
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
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"
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
5756
5757 Field("entity", "integer"),
5758
5759
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
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
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
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
5906
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
5930 if table and not hasattr(table, "_tablename"):
5931 table = current.s3db.table(table)
5932 if not table:
5933 return DEFAULT
5934
5935
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
5942 return DEFAULT
5943
5944 if isinstance(record, Row):
5945
5946 missing = [f for f in fields if f not in record]
5947 if missing:
5948
5949 if table._id.name in record:
5950 record_id = record[table._id.name]
5951 record = None
5952 else:
5953
5954 record_id = record
5955 record = None
5956
5957 if not record and record_id:
5958
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
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
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
6009 return True
6010
6011
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
6019 return True
6020 else:
6021 return False
6022
6023
6024 if owner_user and owner_user == user_id:
6025 return True
6026
6027
6028 if not any((realm_entity, owner_group, owner_user)) and not strict:
6029 return True
6030 elif strict:
6031 return False
6032
6033
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
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
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
6093 if OUSR in table.fields:
6094 user_id = user.id
6095 query = (table[OUSR] == user_id)
6096 if use_realm:
6097
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
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
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
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
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
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
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
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
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
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
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
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
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
6369 auth = self.auth
6370 if auth.override:
6371
6372
6373 return True
6374
6375
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
6388
6389
6390
6391
6392
6393
6394
6395 sr = auth.get_system_roles()
6396 logged_in = auth.s3_logged_in()
6397 self.check_settings()
6398
6399
6400 racl = self.required_acl(method)
6401
6402
6403
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
6412 if sr.ADMIN in realms:
6413
6414
6415 return True
6416
6417
6418 c = c or self.controller
6419 f = f or self.function
6420
6421 if not self.use_cacls:
6422
6423
6424 if logged_in:
6425
6426 return True
6427 else:
6428 if self.page_restricted(c=c, f=f):
6429 permitted = racl == self.READ
6430 else:
6431
6432 permitted = True
6433
6434
6435
6436
6437 return permitted
6438
6439
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
6455
6456
6457
6458
6459
6460
6461 return permission_cache[key]
6462
6463
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
6475 permitted = True
6476 elif not acls:
6477
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
6487 permitted = False
6488 else:
6489 uacl, oacl = self.most_permissive(acls.values())
6490
6491
6492
6493 if permitted is None:
6494 if uacl & racl == racl:
6495 permitted = True
6496 elif oacl & racl == racl:
6497
6498
6499
6500
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
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
6529
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
6535
6536
6537 else:
6538
6539 pass
6540
6541
6542
6543
6544
6545
6546
6547 permission_cache[key] = permitted
6548
6549 return permitted
6550
6551
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
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
6576
6577
6578 ALL_RECORDS = (table._id > 0)
6579 NO_RECORDS = (table._id == 0) if deny else None
6580
6581
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
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
6598 ALL_RECORDS = ALL_RECORDS if approved and unapproved \
6599 else UNAPPROVED if unapproved \
6600 else APPROVED
6601
6602
6603 auth = self.auth
6604 if auth.override:
6605
6606
6607 return ALL_RECORDS
6608
6609 sr = auth.get_system_roles()
6610 logged_in = auth.s3_logged_in()
6611 self.check_settings()
6612
6613
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
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
6634 if sr.ADMIN in realms:
6635
6636
6637 return ALL_RECORDS
6638
6639
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
6660 racl = self.required_acl(method)
6661
6662
6663
6664 if not self.use_cacls:
6665
6666
6667 if logged_in:
6668
6669 return ALL_RECORDS
6670 else:
6671 permitted = racl == self.READ
6672 if permitted:
6673
6674 return ALL_RECORDS
6675 else:
6676
6677 return NO_RECORDS
6678
6679
6680 c = c or self.controller
6681 f = f or self.function
6682
6683
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
6693
6694 query = query_cache[key] = ALL_RECORDS
6695 return query
6696 elif not acls:
6697
6698
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
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
6724 query = ALL_RECORDS
6725 check_owner_acls = False
6726 else:
6727
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
6742 if query is not None:
6743 query |= owner_query
6744 else:
6745 query = owner_query
6746 elif use_realm:
6747
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
6757 if query is None:
6758 query = NO_RECORDS
6759
6760
6761
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
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
6827 """ Action upon insufficient permissions """
6828
6829 if self.format == "html":
6830
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
6839 if self.auth.s3_logged_in():
6840 raise HTTP(403, body=self.INSUFFICIENT_PRIVILEGES)
6841 else:
6842
6843
6844 challenge = {"WWW-Authenticate":
6845 u"Basic realm=\"%s\"" % current.request.application}
6846 raise HTTP(401, body=self.AUTHENTICATION_REQUIRED, **challenge)
6847
6848
6849
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
6877 return None
6878 else:
6879 acls = {}
6880
6881
6882 if realms:
6883 roles = set(realms.keys())
6884 if delegations:
6885 for role in delegations:
6886 roles.add(role)
6887 else:
6888
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
6899 query = (table.deleted != True) & \
6900 (table.group_id.belongs(roles))
6901
6902
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
6912 if t and self.use_tacls:
6913
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
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
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
6960 delegation_rows = []
6961 append_delegation = delegation_rows.append
6962
6963 use_realms = self.entity_realm
6964 for row in rows:
6965
6966
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
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
7006 if self.delegations:
7007 for row in delegation_rows:
7008
7009
7010 rtype = rule_type(row)
7011 if rtype is None:
7012 continue
7013
7014
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
7024
7025 for receiver in drealms:
7026 drealm = drealms[receiver]
7027
7028
7029 if entity:
7030 if entity not in drealm:
7031 continue
7032 else:
7033 drealm = [entity]
7034
7035
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
7044 if rtype in dacls:
7045 dacls[rtype] = most_restrictive(dacls[rtype], acl)
7046 else:
7047 dacls[rtype] = acl
7048
7049
7050
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
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
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
7086
7087
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
7096 s3db = current.s3db
7097 ancestors = set()
7098 if entity and self.entity_hierarchy and \
7099 s3db.pr_instance_type(entity) == "pr_person":
7100
7101
7102
7103
7104
7105
7106 ancestors = set(s3db.pr_realm(entity))
7107
7108 result = {}
7109 for e in acls:
7110
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
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
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
7140 acl = most_restrictive(page_acl, table_acl)
7141
7142
7143 if acl[0] & racl == racl or acl[1] & racl == racl:
7144 result[key] = acl
7145
7146
7147
7148
7149
7150 return result
7151
7152
7153
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
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
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
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
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
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
7274 racl = self.required_acl(method)
7275
7276
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
7286 if sr.ADMIN in realms:
7287 return False
7288
7289
7290 c = c or self.controller
7291 f = f or self.function
7292
7293
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
7303 if "ANY" in acls or acls and "realm_entity" not in table.fields:
7304 return False
7305
7306
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
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
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
7376 Field("old_value", "text"),
7377
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
7411 return True
7412
7413
7414
7415
7416
7417
7418
7419
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
7428 return True
7429
7430 if not audit:
7431
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
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
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
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
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
7677
7680 """ Entity/User role manager """
7681
7682 ENTITY_TYPES = ["org_organisation",
7683 "org_office",
7684 "inv_warehouse",
7685 "hms_hospital",
7686
7687 "pr_group",
7688 ]
7689
7691 """ Constructor """
7692
7693 super(S3EntityRoleManager, self).__init__(*args, **kwargs)
7694
7695
7696 self.realm = self.get_realm()
7697
7698
7699 self.realm_users = current.s3db.pr_realm_users(self.realm)
7700
7701
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
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
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
7779 r.set_handler("roles", cls)
7780
7781 else:
7782
7783 return False
7784
7785 return True
7786
7787
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
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
7857 self.entity = self.get_entity()
7858
7859
7860 self.user = self.get_user()
7861
7862
7863 self.assigned_roles = self.get_assigned_roles()
7864
7865
7866
7867
7868 self.foreign_object = self.get_foreign_object()
7869
7870 form = self.get_form()
7871
7872
7873
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
7884
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
7900 pagination_size = int(r.get_vars.get("page_size", 4))
7901
7902 pagination_offset = int(r.get_vars.get("page_offset", 0))
7903
7904 import math
7905 pagination_pages = int(math.ceil(len(self.assigned_roles) / float(pagination_size)))
7906
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
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
7935 auth.permission.fail()
7936
7937
7938 if ADMIN in realms:
7939 return realms[ADMIN]
7940 elif ORG_ADMIN in realms:
7941 return realms[ORG_ADMIN]
7942 else:
7943
7944
7945 auth.permission.fail()
7946
7947
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
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
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
8053
8054
8071
8072
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
8111
8112 if role_uid not in after:
8113 withdraw_role(user_id, role_uid, entity_id)
8114
8115 for role_uid in after:
8116
8117
8118 if role_uid != "None" and role_uid not in before:
8119 assign_role(user_id, role_uid, entity_id)
8120
8123
8125 """
8126 Constructor
8127 """
8128
8129 super(S3OrgRoleManager, self).__init__(*args, **kwargs)
8130
8131
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
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
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
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
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
8246
8249 """ Role Manager for Person Records """
8250
8252 """ Constructor """
8253
8254 super(S3PersonRoleManager, self).__init__(*args, **kwargs)
8255
8256
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
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
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
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
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
8374
8375
8376