1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8 """
9
10 import base64
11 import cPickle
12 import datetime
13 import thread
14 import logging
15 import sys
16 import os
17 import re
18 import time
19 import copy
20 import smtplib
21 import urllib
22 import urllib2
23 import Cookie
24 import cStringIO
25 from email import MIMEBase, MIMEMultipart, MIMEText, Encoders, Header, message_from_string
26
27 from contenttype import contenttype
28 from storage import Storage, StorageList, Settings, Messages
29 from utils import web2py_uuid
30 from gluon import *
31 from fileutils import read_file
32
33 import serializers
34 import contrib.simplejson as simplejson
35
36
37 __all__ = ['Mail', 'Auth', 'Recaptcha', 'Crud', 'Service', 'PluginManager', 'fetch', 'geocode', 'prettydate']
38
39 logger = logging.getLogger("web2py")
40
41 DEFAULT = lambda: None
42
43 -def callback(actions,form,tablename=None):
44 if actions:
45 if tablename and isinstance(actions,dict):
46 actions = actions.get(tablename, [])
47 if not isinstance(actions,(list, tuple)):
48 actions = [actions]
49 [action(form) for action in actions]
50
52 b = []
53 for item in a:
54 if isinstance(item, (list, tuple)):
55 b = b + list(item)
56 else:
57 b.append(item)
58 return b
59
65
67 """
68 Class for configuring and sending emails with alternative text / html
69 body, multiple attachments and encryption support
70
71 Works with SMTP and Google App Engine.
72 """
73
75 """
76 Email attachment
77
78 Arguments::
79
80 payload: path to file or file-like object with read() method
81 filename: name of the attachment stored in message; if set to
82 None, it will be fetched from payload path; file-like
83 object payload must have explicit filename specified
84 content_id: id of the attachment; automatically contained within
85 < and >
86 content_type: content type of the attachment; if set to None,
87 it will be fetched from filename using gluon.contenttype
88 module
89 encoding: encoding of all strings passed to this function (except
90 attachment body)
91
92 Content ID is used to identify attachments within the html body;
93 in example, attached image with content ID 'photo' may be used in
94 html message as a source of img tag <img src="cid:photo" />.
95
96 Examples::
97
98 #Create attachment from text file:
99 attachment = Mail.Attachment('/path/to/file.txt')
100
101 Content-Type: text/plain
102 MIME-Version: 1.0
103 Content-Disposition: attachment; filename="file.txt"
104 Content-Transfer-Encoding: base64
105
106 SOMEBASE64CONTENT=
107
108 #Create attachment from image file with custom filename and cid:
109 attachment = Mail.Attachment('/path/to/file.png',
110 filename='photo.png',
111 content_id='photo')
112
113 Content-Type: image/png
114 MIME-Version: 1.0
115 Content-Disposition: attachment; filename="photo.png"
116 Content-Id: <photo>
117 Content-Transfer-Encoding: base64
118
119 SOMEOTHERBASE64CONTENT=
120 """
121
122 - def __init__(
123 self,
124 payload,
125 filename=None,
126 content_id=None,
127 content_type=None,
128 encoding='utf-8'):
129 if isinstance(payload, str):
130 if filename == None:
131 filename = os.path.basename(payload)
132 payload = read_file(payload, 'rb')
133 else:
134 if filename == None:
135 raise Exception('Missing attachment name')
136 payload = payload.read()
137 filename = filename.encode(encoding)
138 if content_type == None:
139 content_type = contenttype(filename)
140 self.my_filename = filename
141 self.my_payload = payload
142 MIMEBase.MIMEBase.__init__(self, *content_type.split('/', 1))
143 self.set_payload(payload)
144 self['Content-Disposition'] = 'attachment; filename="%s"' % filename
145 if content_id != None:
146 self['Content-Id'] = '<%s>' % content_id.encode(encoding)
147 Encoders.encode_base64(self)
148
149 - def __init__(self, server=None, sender=None, login=None, tls=True):
150 """
151 Main Mail object
152
153 Arguments::
154
155 server: SMTP server address in address:port notation
156 sender: sender email address
157 login: sender login name and password in login:password notation
158 or None if no authentication is required
159 tls: enables/disables encryption (True by default)
160
161 In Google App Engine use::
162
163 server='gae'
164
165 For sake of backward compatibility all fields are optional and default
166 to None, however, to be able to send emails at least server and sender
167 must be specified. They are available under following fields:
168
169 mail.settings.server
170 mail.settings.sender
171 mail.settings.login
172
173 When server is 'logging', email is logged but not sent (debug mode)
174
175 Optionally you can use PGP encryption or X509:
176
177 mail.settings.cipher_type = None
178 mail.settings.sign = True
179 mail.settings.sign_passphrase = None
180 mail.settings.encrypt = True
181 mail.settings.x509_sign_keyfile = None
182 mail.settings.x509_sign_certfile = None
183 mail.settings.x509_crypt_certfiles = None
184
185 cipher_type : None
186 gpg - need a python-pyme package and gpgme lib
187 x509 - smime
188 sign : sign the message (True or False)
189 sign_passphrase : passphrase for key signing
190 encrypt : encrypt the message
191 ... x509 only ...
192 x509_sign_keyfile : the signers private key filename (PEM format)
193 x509_sign_certfile: the signers certificate filename (PEM format)
194 x509_crypt_certfiles: the certificates file to encrypt the messages
195 with can be a file name or a list of
196 file names (PEM format)
197
198 Examples::
199
200 #Create Mail object with authentication data for remote server:
201 mail = Mail('example.com:25', 'me@example.com', 'me:password')
202 """
203
204 settings = self.settings = Settings()
205 settings.server = server
206 settings.sender = sender
207 settings.login = login
208 settings.tls = tls
209 settings.ssl = False
210 settings.cipher_type = None
211 settings.sign = True
212 settings.sign_passphrase = None
213 settings.encrypt = True
214 settings.x509_sign_keyfile = None
215 settings.x509_sign_certfile = None
216 settings.x509_crypt_certfiles = None
217 settings.debug = False
218 settings.lock_keys = True
219 self.result = {}
220 self.error = None
221
222 - def send(
223 self,
224 to,
225 subject='None',
226 message='None',
227 attachments=None,
228 cc=None,
229 bcc=None,
230 reply_to=None,
231 encoding='utf-8',
232 ):
233 """
234 Sends an email using data specified in constructor
235
236 Arguments::
237
238 to: list or tuple of receiver addresses; will also accept single
239 object
240 subject: subject of the email
241 message: email body text; depends on type of passed object:
242 if 2-list or 2-tuple is passed: first element will be
243 source of plain text while second of html text;
244 otherwise: object will be the only source of plain text
245 and html source will be set to None;
246 If text or html source is:
247 None: content part will be ignored,
248 string: content part will be set to it,
249 file-like object: content part will be fetched from
250 it using it's read() method
251 attachments: list or tuple of Mail.Attachment objects; will also
252 accept single object
253 cc: list or tuple of carbon copy receiver addresses; will also
254 accept single object
255 bcc: list or tuple of blind carbon copy receiver addresses; will
256 also accept single object
257 reply_to: address to which reply should be composed
258 encoding: encoding of all strings passed to this method (including
259 message bodies)
260
261 Examples::
262
263 #Send plain text message to single address:
264 mail.send('you@example.com',
265 'Message subject',
266 'Plain text body of the message')
267
268 #Send html message to single address:
269 mail.send('you@example.com',
270 'Message subject',
271 '<html>Plain text body of the message</html>')
272
273 #Send text and html message to three addresses (two in cc):
274 mail.send('you@example.com',
275 'Message subject',
276 ('Plain text body', '<html>html body</html>'),
277 cc=['other1@example.com', 'other2@example.com'])
278
279 #Send html only message with image attachment available from
280 the message by 'photo' content id:
281 mail.send('you@example.com',
282 'Message subject',
283 (None, '<html><img src="cid:photo" /></html>'),
284 Mail.Attachment('/path/to/photo.jpg'
285 content_id='photo'))
286
287 #Send email with two attachments and no body text
288 mail.send('you@example.com,
289 'Message subject',
290 None,
291 [Mail.Attachment('/path/to/fist.file'),
292 Mail.Attachment('/path/to/second.file')])
293
294 Returns True on success, False on failure.
295
296 Before return, method updates two object's fields:
297 self.result: return value of smtplib.SMTP.sendmail() or GAE's
298 mail.send_mail() method
299 self.error: Exception message or None if above was successful
300 """
301
302 def encode_header(key):
303 if [c for c in key if 32>ord(c) or ord(c)>127]:
304 return Header.Header(key.encode('utf-8'),'utf-8')
305 else:
306 return key
307
308 if not isinstance(self.settings.server, str):
309 raise Exception('Server address not specified')
310 if not isinstance(self.settings.sender, str):
311 raise Exception('Sender address not specified')
312 payload_in = MIMEMultipart.MIMEMultipart('mixed')
313 if to:
314 if not isinstance(to, (list,tuple)):
315 to = [to]
316 else:
317 raise Exception('Target receiver address not specified')
318 if cc:
319 if not isinstance(cc, (list, tuple)):
320 cc = [cc]
321 if bcc:
322 if not isinstance(bcc, (list, tuple)):
323 bcc = [bcc]
324 if message == None:
325 text = html = None
326 elif isinstance(message, (list, tuple)):
327 text, html = message
328 elif message.strip().startswith('<html') and message.strip().endswith('</html>'):
329 text = self.settings.server=='gae' and message or None
330 html = message
331 else:
332 text = message
333 html = None
334 if text != None or html != None:
335 attachment = MIMEMultipart.MIMEMultipart('alternative')
336 if text != None:
337 if isinstance(text, basestring):
338 text = text.decode(encoding).encode('utf-8')
339 else:
340 text = text.read().decode(encoding).encode('utf-8')
341 attachment.attach(MIMEText.MIMEText(text,_charset='utf-8'))
342 if html != None:
343 if isinstance(html, basestring):
344 html = html.decode(encoding).encode('utf-8')
345 else:
346 html = html.read().decode(encoding).encode('utf-8')
347 attachment.attach(MIMEText.MIMEText(html, 'html',_charset='utf-8'))
348 payload_in.attach(attachment)
349 if attachments == None:
350 pass
351 elif isinstance(attachments, (list, tuple)):
352 for attachment in attachments:
353 payload_in.attach(attachment)
354 else:
355 payload_in.attach(attachments)
356
357
358
359
360
361 cipher_type = self.settings.cipher_type
362 sign = self.settings.sign
363 sign_passphrase = self.settings.sign_passphrase
364 encrypt = self.settings.encrypt
365
366
367
368 if cipher_type == 'gpg':
369 if not sign and not encrypt:
370 self.error="No sign and no encrypt is set but cipher type to gpg"
371 return False
372
373
374 from pyme import core, errors
375 from pyme.constants.sig import mode
376
377
378
379 if sign:
380 import string
381 core.check_version(None)
382 pin=string.replace(payload_in.as_string(),'\n','\r\n')
383 plain = core.Data(pin)
384 sig = core.Data()
385 c = core.Context()
386 c.set_armor(1)
387 c.signers_clear()
388
389 for sigkey in c.op_keylist_all(self.settings.sender, 1):
390 if sigkey.can_sign:
391 c.signers_add(sigkey)
392 if not c.signers_enum(0):
393 self.error='No key for signing [%s]' % self.settings.sender
394 return False
395 c.set_passphrase_cb(lambda x,y,z: sign_passphrase)
396 try:
397
398 c.op_sign(plain,sig,mode.DETACH)
399 sig.seek(0,0)
400
401 payload=MIMEMultipart.MIMEMultipart('signed',
402 boundary=None,
403 _subparts=None,
404 **dict(micalg="pgp-sha1",
405 protocol="application/pgp-signature"))
406
407 payload.attach(payload_in)
408
409 p=MIMEBase.MIMEBase("application",'pgp-signature')
410 p.set_payload(sig.read())
411 payload.attach(p)
412
413 payload_in=payload
414 except errors.GPGMEError, ex:
415 self.error="GPG error: %s" % ex.getstring()
416 return False
417
418
419
420 if encrypt:
421 core.check_version(None)
422 plain = core.Data(payload_in.as_string())
423 cipher = core.Data()
424 c = core.Context()
425 c.set_armor(1)
426
427 recipients=[]
428 rec=to[:]
429 if cc:
430 rec.extend(cc)
431 if bcc:
432 rec.extend(bcc)
433 for addr in rec:
434 c.op_keylist_start(addr,0)
435 r = c.op_keylist_next()
436 if r == None:
437 self.error='No key for [%s]' % addr
438 return False
439 recipients.append(r)
440 try:
441
442 c.op_encrypt(recipients, 1, plain, cipher)
443 cipher.seek(0,0)
444
445 payload=MIMEMultipart.MIMEMultipart('encrypted',
446 boundary=None,
447 _subparts=None,
448 **dict(protocol="application/pgp-encrypted"))
449 p=MIMEBase.MIMEBase("application",'pgp-encrypted')
450 p.set_payload("Version: 1\r\n")
451 payload.attach(p)
452 p=MIMEBase.MIMEBase("application",'octet-stream')
453 p.set_payload(cipher.read())
454 payload.attach(p)
455 except errors.GPGMEError, ex:
456 self.error="GPG error: %s" % ex.getstring()
457 return False
458
459
460
461 elif cipher_type == 'x509':
462 if not sign and not encrypt:
463 self.error="No sign and no encrypt is set but cipher type to x509"
464 return False
465 x509_sign_keyfile=self.settings.x509_sign_keyfile
466 if self.settings.x509_sign_certfile:
467 x509_sign_certfile=self.settings.x509_sign_certfile
468 else:
469
470
471 x509_sign_certfile=self.settings.x509_sign_keyfile
472
473 x509_crypt_certfiles=self.settings.x509_crypt_certfiles
474
475
476
477 from M2Crypto import BIO, SMIME, X509
478 msg_bio = BIO.MemoryBuffer(payload_in.as_string())
479 s = SMIME.SMIME()
480
481
482 if sign:
483
484 try:
485 s.load_key(x509_sign_keyfile, x509_sign_certfile, callback=lambda x: sign_passphrase)
486 if encrypt:
487 p7 = s.sign(msg_bio)
488 else:
489 p7 = s.sign(msg_bio,flags=SMIME.PKCS7_DETACHED)
490 msg_bio = BIO.MemoryBuffer(payload_in.as_string())
491 except Exception,e:
492 self.error="Something went wrong on signing: <%s>" %str(e)
493 return False
494
495
496 if encrypt:
497 try:
498 sk = X509.X509_Stack()
499 if not isinstance(x509_crypt_certfiles, (list, tuple)):
500 x509_crypt_certfiles = [x509_crypt_certfiles]
501
502
503 for x in x509_crypt_certfiles:
504 sk.push(X509.load_cert(x))
505 s.set_x509_stack(sk)
506
507 s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
508 tmp_bio = BIO.MemoryBuffer()
509 if sign:
510 s.write(tmp_bio, p7)
511 else:
512 tmp_bio.write(payload_in.as_string())
513 p7 = s.encrypt(tmp_bio)
514 except Exception,e:
515 self.error="Something went wrong on encrypting: <%s>" %str(e)
516 return False
517
518
519 out = BIO.MemoryBuffer()
520 if encrypt:
521 s.write(out, p7)
522 else:
523 if sign:
524 s.write(out, p7, msg_bio, SMIME.PKCS7_DETACHED)
525 else:
526 out.write('\r\n')
527 out.write(payload_in.as_string())
528 out.close()
529 st=str(out.read())
530 payload=message_from_string(st)
531 else:
532
533 payload=payload_in
534 payload['From'] = encode_header(self.settings.sender.decode(encoding))
535 origTo = to[:]
536 if to:
537 payload['To'] = encode_header(', '.join(to).decode(encoding))
538 if reply_to:
539 payload['Reply-To'] = encode_header(reply_to.decode(encoding))
540 if cc:
541 payload['Cc'] = encode_header(', '.join(cc).decode(encoding))
542 to.extend(cc)
543 if bcc:
544 to.extend(bcc)
545 payload['Subject'] = encode_header(subject.decode(encoding))
546 payload['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S +0000",
547 time.gmtime())
548 result = {}
549 try:
550 if self.settings.server == 'logging':
551 logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\n\n%s\n%s\n' % \
552 ('-'*40,self.settings.sender,
553 ', '.join(to),text or html,'-'*40))
554 elif self.settings.server == 'gae':
555 xcc = dict()
556 if cc:
557 xcc['cc'] = cc
558 if bcc:
559 xcc['bcc'] = bcc
560 from google.appengine.api import mail
561 attachments = attachments and [(a.my_filename,a.my_payload) for a in attachments]
562 if attachments:
563 result = mail.send_mail(sender=self.settings.sender, to=origTo,
564 subject=subject, body=text, html=html,
565 attachments=attachments, **xcc)
566 elif html:
567 result = mail.send_mail(sender=self.settings.sender, to=origTo,
568 subject=subject, body=text, html=html, **xcc)
569 else:
570 result = mail.send_mail(sender=self.settings.sender, to=origTo,
571 subject=subject, body=text, **xcc)
572 else:
573 smtp_args = self.settings.server.split(':')
574 if self.settings.ssl:
575 server = smtplib.SMTP_SSL(*smtp_args)
576 else:
577 server = smtplib.SMTP(*smtp_args)
578 if self.settings.tls and not self.settings.ssl:
579 server.ehlo()
580 server.starttls()
581 server.ehlo()
582 if self.settings.login != None:
583 server.login(*self.settings.login.split(':',1))
584 result = server.sendmail(self.settings.sender, to, payload.as_string())
585 server.quit()
586 except Exception, e:
587 logger.warn('Mail.send failure:%s' % e)
588 self.result = result
589 self.error = e
590 return False
591 self.result = result
592 self.error = None
593 return True
594
595
597
598 API_SSL_SERVER = 'https://www.google.com/recaptcha/api'
599 API_SERVER = 'http://www.google.com/recaptcha/api'
600 VERIFY_SERVER = 'http://www.google.com/recaptcha/api/verify'
601
602 - def __init__(
603 self,
604 request,
605 public_key='',
606 private_key='',
607 use_ssl=False,
608 error=None,
609 error_message='invalid',
610 label = 'Verify:',
611 options = ''
612 ):
613 self.remote_addr = request.env.remote_addr
614 self.public_key = public_key
615 self.private_key = private_key
616 self.use_ssl = use_ssl
617 self.error = error
618 self.errors = Storage()
619 self.error_message = error_message
620 self.components = []
621 self.attributes = {}
622 self.label = label
623 self.options = options
624 self.comment = ''
625
627
628
629
630 recaptcha_challenge_field = \
631 self.request_vars.recaptcha_challenge_field
632 recaptcha_response_field = \
633 self.request_vars.recaptcha_response_field
634 private_key = self.private_key
635 remoteip = self.remote_addr
636 if not (recaptcha_response_field and recaptcha_challenge_field
637 and len(recaptcha_response_field)
638 and len(recaptcha_challenge_field)):
639 self.errors['captcha'] = self.error_message
640 return False
641 params = urllib.urlencode({
642 'privatekey': private_key,
643 'remoteip': remoteip,
644 'challenge': recaptcha_challenge_field,
645 'response': recaptcha_response_field,
646 })
647 request = urllib2.Request(
648 url=self.VERIFY_SERVER,
649 data=params,
650 headers={'Content-type': 'application/x-www-form-urlencoded',
651 'User-agent': 'reCAPTCHA Python'})
652 httpresp = urllib2.urlopen(request)
653 return_values = httpresp.read().splitlines()
654 httpresp.close()
655 return_code = return_values[0]
656 if return_code == 'true':
657 del self.request_vars.recaptcha_challenge_field
658 del self.request_vars.recaptcha_response_field
659 self.request_vars.captcha = ''
660 return True
661 self.errors['captcha'] = self.error_message
662 return False
663
665 public_key = self.public_key
666 use_ssl = self.use_ssl
667 error_param = ''
668 if self.error:
669 error_param = '&error=%s' % self.error
670 if use_ssl:
671 server = self.API_SSL_SERVER
672 else:
673 server = self.API_SERVER
674 captcha = DIV(
675 SCRIPT("var RecaptchaOptions = {%s};" % self.options),
676 SCRIPT(_type="text/javascript",
677 _src="%s/challenge?k=%s%s" % (server,public_key,error_param)),
678 TAG.noscript(IFRAME(_src="%s/noscript?k=%s%s" % (server,public_key,error_param),
679 _height="300",_width="500",_frameborder="0"), BR(),
680 INPUT(_type='hidden', _name='recaptcha_response_field',
681 _value='manual_challenge')), _id='recaptcha')
682 if not self.errors.captcha:
683 return XML(captcha).xml()
684 else:
685 captcha.append(DIV(self.errors['captcha'], _class='error'))
686 return XML(captcha).xml()
687
688
689 -def addrow(form,a,b,c,style,_id,position=-1):
690 if style == "divs":
691 form[0].insert(position, DIV(DIV(LABEL(a),_class='w2p_fl'),
692 DIV(b, _class='w2p_fw'),
693 DIV(c, _class='w2p_fc'),
694 _id = _id))
695 elif style == "table2cols":
696 form[0].insert(position, TR(LABEL(a),''))
697 form[0].insert(position+1, TR(b, _colspan=2, _id = _id))
698 elif style == "ul":
699 form[0].insert(position, LI(DIV(LABEL(a),_class='w2p_fl'),
700 DIV(b, _class='w2p_fw'),
701 DIV(c, _class='w2p_fc'),
702 _id = _id))
703 else:
704 form[0].insert(position, TR(LABEL(a),b,c,_id = _id))
705
706
708 """
709 Class for authentication, authorization, role based access control.
710
711 Includes:
712
713 - registration and profile
714 - login and logout
715 - username and password retrieval
716 - event logging
717 - role creation and assignment
718 - user defined group/role based permission
719
720 Authentication Example::
721
722 from contrib.utils import *
723 mail=Mail()
724 mail.settings.server='smtp.gmail.com:587'
725 mail.settings.sender='you@somewhere.com'
726 mail.settings.login='username:password'
727 auth=Auth(globals(), db)
728 auth.settings.mailer=mail
729 # auth.settings....=...
730 auth.define_tables()
731 def authentication():
732 return dict(form=auth())
733
734 exposes:
735
736 - http://.../{application}/{controller}/authentication/login
737 - http://.../{application}/{controller}/authentication/logout
738 - http://.../{application}/{controller}/authentication/register
739 - http://.../{application}/{controller}/authentication/verify_email
740 - http://.../{application}/{controller}/authentication/retrieve_username
741 - http://.../{application}/{controller}/authentication/retrieve_password
742 - http://.../{application}/{controller}/authentication/reset_password
743 - http://.../{application}/{controller}/authentication/profile
744 - http://.../{application}/{controller}/authentication/change_password
745
746 On registration a group with role=new_user.id is created
747 and user is given membership of this group.
748
749 You can create a group with::
750
751 group_id=auth.add_group('Manager', 'can access the manage action')
752 auth.add_permission(group_id, 'access to manage')
753
754 Here \"access to manage\" is just a user defined string.
755 You can give access to a user::
756
757 auth.add_membership(group_id, user_id)
758
759 If user id is omitted, the logged in user is assumed
760
761 Then you can decorate any action::
762
763 @auth.requires_permission('access to manage')
764 def manage():
765 return dict()
766
767 You can restrict a permission to a specific table::
768
769 auth.add_permission(group_id, 'edit', db.sometable)
770 @auth.requires_permission('edit', db.sometable)
771
772 Or to a specific record::
773
774 auth.add_permission(group_id, 'edit', db.sometable, 45)
775 @auth.requires_permission('edit', db.sometable, 45)
776
777 If authorization is not granted calls::
778
779 auth.settings.on_failed_authorization
780
781 Other options::
782
783 auth.settings.mailer=None
784 auth.settings.expiration=3600 # seconds
785
786 ...
787
788 ### these are messages that can be customized
789 ...
790 """
791
792
793 - def url(self, f=None, args=[], vars={}):
794 return URL(c=self.settings.controller,f=f,args=args,vars=vars)
795
796 - def __init__(self, environment=None, db=None,
797 controller='default', cas_provider = None):
798 """
799 auth=Auth(globals(), db)
800
801 - environment is there for legacy but unused (awful)
802 - db has to be the database where to create tables for authentication
803
804 """
805
806 if not db and environment and isinstance(environment,DAL):
807 db = environment
808 self.db = db
809 self.environment = current
810 request = current.request
811 session = current.session
812 auth = session.auth
813 if auth and auth.last_visit and auth.last_visit + \
814 datetime.timedelta(days=0, seconds=auth.expiration) > request.now:
815 self.user = auth.user
816
817 if (request.now - auth.last_visit).seconds > (auth.expiration/10):
818 auth.last_visit = request.now
819 else:
820 self.user = None
821 session.auth = None
822 settings = self.settings = Settings()
823
824
825
826
827
828 settings.hideerror = False
829 settings.cas_domains = [request.env.http_host]
830 settings.cas_provider = cas_provider
831 settings.extra_fields = {}
832 settings.actions_disabled = []
833 settings.reset_password_requires_verification = False
834 settings.registration_requires_verification = False
835 settings.registration_requires_approval = False
836 settings.alternate_requires_registration = False
837 settings.create_user_groups = True
838
839 settings.controller = controller
840 settings.login_url = self.url('user', args='login')
841 settings.logged_url = self.url('user', args='profile')
842 settings.download_url = self.url('download')
843 settings.mailer = None
844 settings.login_captcha = None
845 settings.register_captcha = None
846 settings.retrieve_username_captcha = None
847 settings.retrieve_password_captcha = None
848 settings.captcha = None
849 settings.expiration = 3600
850 settings.long_expiration = 3600*30*24
851 settings.remember_me_form = True
852 settings.allow_basic_login = False
853 settings.allow_basic_login_only = False
854 settings.on_failed_authorization = \
855 self.url('user',args='not_authorized')
856
857 settings.on_failed_authentication = lambda x: redirect(x)
858
859 settings.formstyle = 'table3cols'
860 settings.label_separator = ': '
861
862
863
864 settings.password_field = 'password'
865 settings.table_user_name = 'auth_user'
866 settings.table_group_name = 'auth_group'
867 settings.table_membership_name = 'auth_membership'
868 settings.table_permission_name = 'auth_permission'
869 settings.table_event_name = 'auth_event'
870 settings.table_cas_name = 'auth_cas'
871
872
873
874 settings.table_user = None
875 settings.table_group = None
876 settings.table_membership = None
877 settings.table_permission = None
878 settings.table_event = None
879 settings.table_cas = None
880
881
882
883 settings.showid = False
884
885
886
887 settings.login_next = self.url('index')
888 settings.login_onvalidation = []
889 settings.login_onaccept = []
890 settings.login_methods = [self]
891 settings.login_form = self
892 settings.login_email_validate = True
893 settings.login_userfield = None
894
895 settings.logout_next = self.url('index')
896 settings.logout_onlogout = None
897
898 settings.register_next = self.url('index')
899 settings.register_onvalidation = []
900 settings.register_onaccept = []
901 settings.register_fields = None
902
903 settings.verify_email_next = self.url('user', args='login')
904 settings.verify_email_onaccept = []
905
906 settings.profile_next = self.url('index')
907 settings.profile_onvalidation = []
908 settings.profile_onaccept = []
909 settings.profile_fields = None
910 settings.retrieve_username_next = self.url('index')
911 settings.retrieve_password_next = self.url('index')
912 settings.request_reset_password_next = self.url('user', args='login')
913 settings.reset_password_next = self.url('user', args='login')
914
915 settings.change_password_next = self.url('index')
916 settings.change_password_onvalidation = []
917 settings.change_password_onaccept = []
918
919 settings.retrieve_password_onvalidation = []
920 settings.reset_password_onvalidation = []
921
922 settings.hmac_key = None
923 settings.lock_keys = True
924
925
926
927 messages = self.messages = Messages(current.T)
928 messages.login_button = 'Login'
929 messages.register_button = 'Register'
930 messages.password_reset_button = 'Request reset password'
931 messages.password_change_button = 'Change password'
932 messages.profile_save_button = 'Save profile'
933 messages.submit_button = 'Submit'
934 messages.verify_password = 'Verify Password'
935 messages.delete_label = 'Check to delete:'
936 messages.function_disabled = 'Function disabled'
937 messages.access_denied = 'Insufficient privileges'
938 messages.registration_verifying = 'Registration needs verification'
939 messages.registration_pending = 'Registration is pending approval'
940 messages.login_disabled = 'Login disabled by administrator'
941 messages.logged_in = 'Logged in'
942 messages.email_sent = 'Email sent'
943 messages.unable_to_send_email = 'Unable to send email'
944 messages.email_verified = 'Email verified'
945 messages.logged_out = 'Logged out'
946 messages.registration_successful = 'Registration successful'
947 messages.invalid_email = 'Invalid email'
948 messages.unable_send_email = 'Unable to send email'
949 messages.invalid_login = 'Invalid login'
950 messages.invalid_user = 'Invalid user'
951 messages.invalid_password = 'Invalid password'
952 messages.is_empty = "Cannot be empty"
953 messages.mismatched_password = "Password fields don't match"
954 messages.verify_email = \
955 'Click on the link http://...verify_email/%(key)s to verify your email'
956 messages.verify_email_subject = 'Email verification'
957 messages.username_sent = 'Your username was emailed to you'
958 messages.new_password_sent = 'A new password was emailed to you'
959 messages.password_changed = 'Password changed'
960 messages.retrieve_username = 'Your username is: %(username)s'
961 messages.retrieve_username_subject = 'Username retrieve'
962 messages.retrieve_password = 'Your password is: %(password)s'
963 messages.retrieve_password_subject = 'Password retrieve'
964 messages.reset_password = \
965 'Click on the link http://...reset_password/%(key)s to reset your password'
966 messages.reset_password_subject = 'Password reset'
967 messages.invalid_reset_password = 'Invalid reset password'
968 messages.profile_updated = 'Profile updated'
969 messages.new_password = 'New password'
970 messages.old_password = 'Old password'
971 messages.group_description = \
972 'Group uniquely assigned to user %(id)s'
973
974 messages.register_log = 'User %(id)s Registered'
975 messages.login_log = 'User %(id)s Logged-in'
976 messages.login_failed_log = None
977 messages.logout_log = 'User %(id)s Logged-out'
978 messages.profile_log = 'User %(id)s Profile updated'
979 messages.verify_email_log = 'User %(id)s Verification email sent'
980 messages.retrieve_username_log = 'User %(id)s Username retrieved'
981 messages.retrieve_password_log = 'User %(id)s Password retrieved'
982 messages.reset_password_log = 'User %(id)s Password reset'
983 messages.change_password_log = 'User %(id)s Password changed'
984 messages.add_group_log = 'Group %(group_id)s created'
985 messages.del_group_log = 'Group %(group_id)s deleted'
986 messages.add_membership_log = None
987 messages.del_membership_log = None
988 messages.has_membership_log = None
989 messages.add_permission_log = None
990 messages.del_permission_log = None
991 messages.has_permission_log = None
992 messages.impersonate_log = 'User %(id)s is impersonating %(other_id)s'
993
994 messages.label_first_name = 'First name'
995 messages.label_last_name = 'Last name'
996 messages.label_username = 'Username'
997 messages.label_email = 'E-mail'
998 messages.label_password = 'Password'
999 messages.label_registration_key = 'Registration key'
1000 messages.label_reset_password_key = 'Reset Password key'
1001 messages.label_registration_id = 'Registration identifier'
1002 messages.label_role = 'Role'
1003 messages.label_description = 'Description'
1004 messages.label_user_id = 'User ID'
1005 messages.label_group_id = 'Group ID'
1006 messages.label_name = 'Name'
1007 messages.label_table_name = 'Table name'
1008 messages.label_record_id = 'Record ID'
1009 messages.label_time_stamp = 'Timestamp'
1010 messages.label_client_ip = 'Client IP'
1011 messages.label_origin = 'Origin'
1012 messages.label_remember_me = "Remember me (for 30 days)"
1013 messages['T'] = current.T
1014 messages.verify_password_comment = 'please input your password again'
1015 messages.lock_keys = True
1016
1017
1018 response = current.response
1019 if auth and auth.remember:
1020 response.cookies[response.session_id_name]["expires"] = \
1021 auth.expiration
1022
1023 def lazy_user (auth = self): return auth.user_id
1024 reference_user = 'reference %s' % settings.table_user_name
1025 def represent(id,record=None,s=settings):
1026 try:
1027 user = s.table_user(id)
1028 return '%(first_name)s %(last_name)s' % user
1029 except: return id
1030 self.signature = db.Table(self.db,'auth_signature',
1031 Field('is_active','boolean',default=True),
1032 Field('created_on','datetime',
1033 default=request.now,
1034 writable=False,readable=False),
1035 Field('created_by',
1036 reference_user,
1037 default=lazy_user,represent=represent,
1038 writable=False,readable=False,
1039 ),
1040 Field('modified_on','datetime',
1041 update=request.now,default=request.now,
1042 writable=False,readable=False),
1043 Field('modified_by',
1044 reference_user,represent=represent,
1045 default=lazy_user,update=lazy_user,
1046 writable=False,readable=False))
1047
1048
1049
1051 "accessor for auth.user_id"
1052 return self.user and self.user.id or None
1053 user_id = property(_get_user_id, doc="user.id or None")
1054
1055 - def _HTTP(self, *a, **b):
1056 """
1057 only used in lambda: self._HTTP(404)
1058 """
1059
1060 raise HTTP(*a, **b)
1061
1063 """
1064 usage:
1065
1066 def authentication(): return dict(form=auth())
1067 """
1068
1069 request = current.request
1070 args = request.args
1071 if not args:
1072 redirect(self.url(args='login',vars=request.vars))
1073 elif args[0] in self.settings.actions_disabled:
1074 raise HTTP(404)
1075 if args[0] in ('login','logout','register','verify_email',
1076 'retrieve_username','retrieve_password',
1077 'reset_password','request_reset_password',
1078 'change_password','profile','groups',
1079 'impersonate','not_authorized'):
1080 return getattr(self,args[0])()
1081 elif args[0]=='cas' and not self.settings.cas_provider:
1082 if args(1) == 'login': return self.cas_login(version=2)
1083 if args(1) == 'validate': return self.cas_validate(version=2)
1084 if args(1) == 'logout': return self.logout()
1085 else:
1086 raise HTTP(404)
1087
1088 - def navbar(self,prefix='Welcome',action=None):
1089 request = current.request
1090 T = current.T
1091 if isinstance(prefix,str):
1092 prefix = T(prefix)
1093 if not action:
1094 action=URL(request.application,request.controller,'user')
1095 if prefix:
1096 prefix = prefix.strip()+' '
1097 if self.user_id:
1098 logout=A(T('logout'),_href=action+'/logout')
1099 profile=A(T('profile'),_href=action+'/profile')
1100 password=A(T('password'),_href=action+'/change_password')
1101 bar = SPAN(prefix,self.user.first_name,' [ ', logout, ']',_class='auth_navbar')
1102 if not 'profile' in self.settings.actions_disabled:
1103 bar.insert(4, ' | ')
1104 bar.insert(5, profile)
1105 if not 'change_password' in self.settings.actions_disabled:
1106 bar.insert(-1, ' | ')
1107 bar.insert(-1, password)
1108 else:
1109 login=A(T('login'),_href=action+'/login')
1110 register=A(T('register'),_href=action+'/register')
1111 retrieve_username=A(T('forgot username?'),
1112 _href=action+'/retrieve_username')
1113 lost_password=A(T('lost password?'),
1114 _href=action+'/request_reset_password')
1115 bar = SPAN('[ ',login,' ]',_class='auth_navbar')
1116
1117 if not 'register' in self.settings.actions_disabled:
1118 bar.insert(2, ' | ')
1119 bar.insert(3, register)
1120 if 'username' in self.settings.table_user.fields() and \
1121 not 'retrieve_username' in self.settings.actions_disabled:
1122 bar.insert(-1, ' | ')
1123 bar.insert(-1, retrieve_username)
1124 if not 'request_reset_password' in self.settings.actions_disabled:
1125 bar.insert(-1, ' | ')
1126 bar.insert(-1, lost_password)
1127 return bar
1128
1130
1131 if type(migrate).__name__ == 'str':
1132 return (migrate + tablename + '.table')
1133 elif migrate == False:
1134 return False
1135 else:
1136 return True
1137
1138 - def define_tables(self, username=False, migrate=True, fake_migrate=False):
1139 """
1140 to be called unless tables are defined manually
1141
1142 usages::
1143
1144 # defines all needed tables and table files
1145 # 'myprefix_auth_user.table', ...
1146 auth.define_tables(migrate='myprefix_')
1147
1148 # defines all needed tables without migration/table files
1149 auth.define_tables(migrate=False)
1150
1151 """
1152
1153 db = self.db
1154 settings = self.settings
1155 if not settings.table_user_name in db.tables:
1156 passfield = settings.password_field
1157 if username or settings.cas_provider:
1158 table = db.define_table(
1159 settings.table_user_name,
1160 Field('first_name', length=128, default='',
1161 label=self.messages.label_first_name),
1162 Field('last_name', length=128, default='',
1163 label=self.messages.label_last_name),
1164 Field('username', length=128, default='',
1165 label=self.messages.label_username),
1166 Field('email', length=512, default='',
1167 label=self.messages.label_email),
1168 Field(passfield, 'password', length=512,
1169 readable=False, label=self.messages.label_password),
1170 Field('registration_key', length=512,
1171 writable=False, readable=False, default='',
1172 label=self.messages.label_registration_key),
1173 Field('reset_password_key', length=512,
1174 writable=False, readable=False, default='',
1175 label=self.messages.label_reset_password_key),
1176 Field('registration_id', length=512,
1177 writable=False, readable=False, default='',
1178 label=self.messages.label_registration_id),
1179 *settings.extra_fields.get(settings.table_user_name,[]),
1180 **dict(
1181 migrate=self.__get_migrate(settings.table_user_name,
1182 migrate),
1183 fake_migrate=fake_migrate,
1184 format='%(username)s'))
1185 table.username.requires = (IS_MATCH('[\w\.\-]+'),
1186 IS_NOT_IN_DB(db, table.username))
1187 else:
1188 table = db.define_table(
1189 settings.table_user_name,
1190 Field('first_name', length=128, default='',
1191 label=self.messages.label_first_name),
1192 Field('last_name', length=128, default='',
1193 label=self.messages.label_last_name),
1194 Field('email', length=512, default='',
1195 label=self.messages.label_email),
1196 Field(passfield, 'password', length=512,
1197 readable=False, label=self.messages.label_password),
1198 Field('registration_key', length=512,
1199 writable=False, readable=False, default='',
1200 label=self.messages.label_registration_key),
1201 Field('reset_password_key', length=512,
1202 writable=False, readable=False, default='',
1203 label=self.messages.label_reset_password_key),
1204 *settings.extra_fields.get(settings.table_user_name,[]),
1205 **dict(
1206 migrate=self.__get_migrate(settings.table_user_name,
1207 migrate),
1208 fake_migrate=fake_migrate,
1209 format='%(first_name)s %(last_name)s (%(id)s)'))
1210 table.first_name.requires = \
1211 IS_NOT_EMPTY(error_message=self.messages.is_empty)
1212 table.last_name.requires = \
1213 IS_NOT_EMPTY(error_message=self.messages.is_empty)
1214 table[passfield].requires = [CRYPT(key=settings.hmac_key)]
1215 table.email.requires = \
1216 [IS_EMAIL(error_message=self.messages.invalid_email),
1217 IS_NOT_IN_DB(db, table.email)]
1218 table.registration_key.default = ''
1219 settings.table_user = db[settings.table_user_name]
1220 if not settings.table_group_name in db.tables:
1221 table = db.define_table(
1222 settings.table_group_name,
1223 Field('role', length=512, default='',
1224 label=self.messages.label_role),
1225 Field('description', 'text',
1226 label=self.messages.label_description),
1227 *settings.extra_fields.get(settings.table_group_name,[]),
1228 **dict(
1229 migrate=self.__get_migrate(
1230 settings.table_group_name, migrate),
1231 fake_migrate=fake_migrate,
1232 format = '%(role)s (%(id)s)'))
1233 table.role.requires = IS_NOT_IN_DB(db, '%s.role'
1234 % settings.table_group_name)
1235 settings.table_group = db[settings.table_group_name]
1236 if not settings.table_membership_name in db.tables:
1237 table = db.define_table(
1238 settings.table_membership_name,
1239 Field('user_id', settings.table_user,
1240 label=self.messages.label_user_id),
1241 Field('group_id', settings.table_group,
1242 label=self.messages.label_group_id),
1243 *settings.extra_fields.get(settings.table_membership_name,[]),
1244 **dict(
1245 migrate=self.__get_migrate(
1246 settings.table_membership_name, migrate),
1247 fake_migrate=fake_migrate))
1248 table.user_id.requires = IS_IN_DB(db, '%s.id' %
1249 settings.table_user_name,
1250 '%(first_name)s %(last_name)s (%(id)s)')
1251 table.group_id.requires = IS_IN_DB(db, '%s.id' %
1252 settings.table_group_name,
1253 '%(role)s (%(id)s)')
1254 settings.table_membership = db[settings.table_membership_name]
1255 if not settings.table_permission_name in db.tables:
1256 table = db.define_table(
1257 settings.table_permission_name,
1258 Field('group_id', settings.table_group,
1259 label=self.messages.label_group_id),
1260 Field('name', default='default', length=512,
1261 label=self.messages.label_name),
1262 Field('table_name', length=512,
1263 label=self.messages.label_table_name),
1264 Field('record_id', 'integer',default=0,
1265 label=self.messages.label_record_id),
1266 *settings.extra_fields.get(settings.table_permission_name,[]),
1267 **dict(
1268 migrate=self.__get_migrate(
1269 settings.table_permission_name, migrate),
1270 fake_migrate=fake_migrate))
1271 table.group_id.requires = IS_IN_DB(db, '%s.id' %
1272 settings.table_group_name,
1273 '%(role)s (%(id)s)')
1274 table.name.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1275 table.table_name.requires = IS_EMPTY_OR(IS_IN_SET(self.db.tables))
1276 table.record_id.requires = IS_INT_IN_RANGE(0, 10 ** 9)
1277 settings.table_permission = db[settings.table_permission_name]
1278 if not settings.table_event_name in db.tables:
1279 table = db.define_table(
1280 settings.table_event_name,
1281 Field('time_stamp', 'datetime',
1282 default=current.request.now,
1283 label=self.messages.label_time_stamp),
1284 Field('client_ip',
1285 default=current.request.client,
1286 label=self.messages.label_client_ip),
1287 Field('user_id', settings.table_user, default=None,
1288 label=self.messages.label_user_id),
1289 Field('origin', default='auth', length=512,
1290 label=self.messages.label_origin),
1291 Field('description', 'text', default='',
1292 label=self.messages.label_description),
1293 *settings.extra_fields.get(settings.table_event_name,[]),
1294 **dict(
1295 migrate=self.__get_migrate(
1296 settings.table_event_name, migrate),
1297 fake_migrate=fake_migrate))
1298 table.user_id.requires = IS_IN_DB(db, '%s.id' %
1299 settings.table_user_name,
1300 '%(first_name)s %(last_name)s (%(id)s)')
1301 table.origin.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1302 table.description.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1303 settings.table_event = db[settings.table_event_name]
1304 now = current.request.now
1305 if settings.cas_domains:
1306 if not settings.table_cas_name in db.tables:
1307 table = db.define_table(
1308 settings.table_cas_name,
1309 Field('user_id', settings.table_user, default=None,
1310 label=self.messages.label_user_id),
1311 Field('created_on','datetime',default=now),
1312 Field('url',requires=IS_URL()),
1313 Field('uuid'),
1314 *settings.extra_fields.get(settings.table_cas_name,[]),
1315 **dict(
1316 migrate=self.__get_migrate(
1317 settings.table_event_name, migrate),
1318 fake_migrate=fake_migrate))
1319 table.user_id.requires = IS_IN_DB(db, '%s.id' % \
1320 settings.table_user_name,
1321 '%(first_name)s %(last_name)s (%(id)s)')
1322 settings.table_cas = db[settings.table_cas_name]
1323 if settings.cas_provider:
1324 settings.actions_disabled = \
1325 ['profile','register','change_password','request_reset_password']
1326 from gluon.contrib.login_methods.cas_auth import CasAuth
1327 maps = dict((name,lambda v,n=name:v.get(n,None)) for name in \
1328 settings.table_user.fields if name!='id' \
1329 and settings.table_user[name].readable)
1330 maps['registration_id'] = \
1331 lambda v,p=settings.cas_provider:'%s/%s' % (p,v['user'])
1332 settings.login_form = CasAuth(
1333 casversion = 2,
1334 urlbase = settings.cas_provider,
1335 actions=['login','validate','logout'],
1336 maps=maps)
1337
1338
1339 - def log_event(self, description, origin='auth'):
1340 """
1341 usage::
1342
1343 auth.log_event(description='this happened', origin='auth')
1344 """
1345
1346 if self.is_logged_in():
1347 user_id = self.user.id
1348 else:
1349 user_id = None
1350 self.settings.table_event.insert(description=description,
1351 origin=origin, user_id=user_id)
1352
1354 """
1355 Used for alternate login methods:
1356 If the user exists already then password is updated.
1357 If the user doesn't yet exist, then they are created.
1358 """
1359 table_user = self.settings.table_user
1360 if 'registration_id' in table_user.fields() and \
1361 'registration_id' in keys:
1362 username = 'registration_id'
1363 elif 'username' in table_user.fields():
1364 username = 'username'
1365 elif 'email' in table_user.fields():
1366 username = 'email'
1367 else:
1368 raise SyntaxError, "user must have username or email"
1369 passfield = self.settings.password_field
1370 user = self.db(table_user[username] == keys[username]).select().first()
1371 keys['registration_key']=''
1372 if user:
1373 user.update_record(**table_user._filter_fields(keys))
1374 else:
1375 if not 'first_name' in keys and 'first_name' in table_user.fields:
1376 keys['first_name'] = keys[username]
1377 user_id = table_user.insert(**table_user._filter_fields(keys))
1378 user = self.user = table_user[user_id]
1379 if self.settings.create_user_groups:
1380 group_id = self.add_group("user_%s" % user_id)
1381 self.add_membership(group_id, user_id)
1382 return user
1383
1385 if not self.settings.allow_basic_login:
1386 return False
1387 basic = current.request.env.http_authorization
1388 if not basic or not basic[:6].lower() == 'basic ':
1389 return False
1390 (username, password) = base64.b64decode(basic[6:]).split(':')
1391 return self.login_bare(username, password)
1392
1394 """
1395 logins user
1396 """
1397
1398 request = current.request
1399 session = current.session
1400 table_user = self.settings.table_user
1401 if self.settings.login_userfield:
1402 userfield = self.settings.login_userfield
1403 elif 'username' in table_user.fields:
1404 userfield = 'username'
1405 else:
1406 userfield = 'email'
1407 passfield = self.settings.password_field
1408 user = self.db(table_user[userfield] == username).select().first()
1409 password = table_user[passfield].validate(password)[0]
1410 if user:
1411 if not user.registration_key and user[passfield] == password:
1412 user = Storage(table_user._filter_fields(user, id=True))
1413 session.auth = Storage(user=user, last_visit=request.now,
1414 expiration=self.settings.expiration,
1415 hmac_key = web2py_uuid())
1416 self.user = user
1417 return user
1418 return False
1419
1428 request, session = current.request, current.session
1429 db, table = self.db, self.settings.table_cas
1430 session._cas_service = request.vars.service or session._cas_service
1431 if not request.env.http_host in self.settings.cas_domains or \
1432 not session._cas_service:
1433 raise HTTP(403,'not authorized')
1434 def allow_access():
1435 row = table(url=session._cas_service,user_id=self.user.id)
1436 if row:
1437 row.update_record(created_on=request.now)
1438 uuid = row.uuid
1439 else:
1440 uuid = web2py_uuid()
1441 table.insert(url=session._cas_service, user_id=self.user.id,
1442 uuid=uuid, created_on=request.now)
1443 url = session._cas_service
1444 del session._cas_service
1445 redirect(url+"?ticket="+uuid)
1446 if self.is_logged_in():
1447 allow_access()
1448 def cas_onaccept(form, onaccept=onaccept):
1449 if onaccept!=DEFAULT: onaccept(form)
1450 allow_access()
1451 return self.login(next,onvalidation,cas_onaccept,log)
1452
1453
1455 request = current.request
1456 db, table = self.db, self.settings.table_cas
1457 current.response.headers['Content-Type']='text'
1458 ticket = table(uuid=request.vars.ticket)
1459 url = request.env.path_info.rsplit('/',1)[0]
1460 if ticket:
1461 user = self.settings.table_user(ticket.user_id)
1462 fullname = user.first_name+' '+user.last_name
1463 if version==1:
1464 raise HTTP(200,'yes\n%s:%s:%s'%(user.id,user.email,fullname))
1465
1466 username = user.get('username',user.email)
1467 raise HTTP(200,'<?xml version="1.0" encoding="UTF-8"?>\n'+\
1468 TAG['cas:serviceResponse'](
1469 TAG['cas:authenticationSuccess'](
1470 TAG['cas:user'](username),
1471 *[TAG['cas:'+field.name](user[field.name]) \
1472 for field in self.settings.table_user \
1473 if field.readable]),
1474 **{'_xmlns:cas':'http://www.yale.edu/tp/cas'}).xml())
1475 if version==1:
1476 raise HTTP(200,'no\n')
1477
1478 raise HTTP(200,'<?xml version="1.0" encoding="UTF-8"?>\n'+\
1479 TAG['cas:serviceResponse'](
1480 TAG['cas:authenticationFailure'](
1481 'Ticket %s not recognized' % ticket,
1482 _code='INVALID TICKET'),
1483 **{'_xmlns:cas':'http://www.yale.edu/tp/cas'}).xml())
1484
1485
1493 """
1494 returns a login form
1495
1496 .. method:: Auth.login([next=DEFAULT [, onvalidation=DEFAULT
1497 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1498
1499 """
1500
1501 table_user = self.settings.table_user
1502 if self.settings.login_userfield:
1503 username = self.settings.login_userfield
1504 elif 'username' in table_user.fields:
1505 username = 'username'
1506 else:
1507 username = 'email'
1508 if 'username' in table_user.fields or not self.settings.login_email_validate:
1509 tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1510 else:
1511 tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email)
1512 old_requires = table_user[username].requires
1513 table_user[username].requires = tmpvalidator
1514
1515 request = current.request
1516 response = current.response
1517 session = current.session
1518
1519 passfield = self.settings.password_field
1520 if next == DEFAULT:
1521 next = request.get_vars._next \
1522 or request.post_vars._next \
1523 or self.settings.login_next
1524 if onvalidation == DEFAULT:
1525 onvalidation = self.settings.login_onvalidation
1526 if onaccept == DEFAULT:
1527 onaccept = self.settings.login_onaccept
1528 if log == DEFAULT:
1529 log = self.messages.login_log
1530
1531 user = None
1532
1533
1534 if self.settings.login_form == self:
1535 form = SQLFORM(
1536 table_user,
1537 fields=[username, passfield],
1538 hidden=dict(_next=next),
1539 showid=self.settings.showid,
1540 submit_button=self.messages.login_button,
1541 delete_label=self.messages.delete_label,
1542 formstyle=self.settings.formstyle,
1543 separator=self.settings.label_separator
1544 )
1545
1546 if self.settings.remember_me_form:
1547
1548 addrow(form,XML(" "),
1549 DIV(XML(" "),
1550 INPUT(_type='checkbox',
1551 _class='checkbox',
1552 _id="auth_user_remember",
1553 _name="remember",
1554 ),
1555 XML(" "),
1556 LABEL(
1557 self.messages.label_remember_me,
1558 _for="auth_user_remember",
1559 )),"",
1560 self.settings.formstyle,
1561 'auth_user_remember__row')
1562
1563 captcha = self.settings.login_captcha or \
1564 (self.settings.login_captcha!=False and self.settings.captcha)
1565 if captcha:
1566 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row')
1567 accepted_form = False
1568
1569 if form.accepts(request, session,
1570 formname='login', dbio=False,
1571 onvalidation=onvalidation,
1572 hideerror=self.settings.hideerror):
1573
1574 accepted_form = True
1575
1576 user = self.db(table_user[username] == form.vars[username]).select().first()
1577 if user:
1578
1579 temp_user = user
1580 if temp_user.registration_key == 'pending':
1581 response.flash = self.messages.registration_pending
1582 return form
1583 elif temp_user.registration_key in ('disabled','blocked'):
1584 response.flash = self.messages.login_disabled
1585 return form
1586 elif temp_user.registration_key!=None and \
1587 temp_user.registration_key.strip():
1588 response.flash = \
1589 self.messages.registration_verifying
1590 return form
1591
1592
1593 user = None
1594 for login_method in self.settings.login_methods:
1595 if login_method != self and \
1596 login_method(request.vars[username],
1597 request.vars[passfield]):
1598 if not self in self.settings.login_methods:
1599
1600 form.vars[passfield] = None
1601 user = self.get_or_create_user(form.vars)
1602 break
1603 if not user:
1604
1605 if self.settings.login_methods[0] == self:
1606
1607 if temp_user[passfield] == form.vars.get(passfield, ''):
1608
1609 user = temp_user
1610 else:
1611
1612 if not self.settings.alternate_requires_registration:
1613
1614 for login_method in self.settings.login_methods:
1615 if login_method != self and \
1616 login_method(request.vars[username],
1617 request.vars[passfield]):
1618 if not self in self.settings.login_methods:
1619
1620 form.vars[passfield] = None
1621 user = self.get_or_create_user(form.vars)
1622 break
1623 if not user:
1624 if self.settings.login_failed_log:
1625 self.log_event(self.settings.login_failed_log % request.post_vars)
1626
1627 session.flash = self.messages.invalid_login
1628 redirect(self.url(args=request.args,vars=request.get_vars))
1629
1630 else:
1631
1632 cas = self.settings.login_form
1633 cas_user = cas.get_user()
1634
1635 if cas_user:
1636 cas_user[passfield] = None
1637 user = self.get_or_create_user(table_user._filter_fields(cas_user))
1638 elif hasattr(cas,'login_form'):
1639 return cas.login_form()
1640 else:
1641
1642 next = self.url('user',args='login',vars=dict(_next=next))
1643 redirect(cas.login_url(next))
1644
1645
1646
1647 if user:
1648 user = Storage(table_user._filter_fields(user, id=True))
1649
1650 if log:
1651 self.log_event(log % user)
1652
1653
1654
1655 session.auth = Storage(
1656 user = user,
1657 last_visit = request.now,
1658 expiration = self.settings.long_expiration,
1659 remember = request.vars.has_key("remember"),
1660 hmac_key = web2py_uuid()
1661 )
1662
1663 self.user = user
1664 session.flash = self.messages.logged_in
1665
1666
1667 if self.settings.login_form == self:
1668 if accepted_form:
1669 callback(onaccept,form)
1670 if isinstance(next, (list, tuple)):
1671
1672 next = next[0]
1673 if next and not next[0] == '/' and next[:4] != 'http':
1674 next = self.url(next.replace('[id]', str(form.vars.id)))
1675 redirect(next)
1676 table_user[username].requires = old_requires
1677 return form
1678 elif user:
1679 callback(onaccept,None)
1680 redirect(next)
1681
1683 """
1684 logout and redirects to login
1685
1686 .. method:: Auth.logout ([next=DEFAULT[, onlogout=DEFAULT[,
1687 log=DEFAULT]]])
1688
1689 """
1690
1691 if next == DEFAULT:
1692 next = self.settings.logout_next
1693 if onlogout == DEFAULT:
1694 onlogout = self.settings.logout_onlogout
1695 if onlogout:
1696 onlogout(self.user)
1697 if log == DEFAULT:
1698 log = self.messages.logout_log
1699 if log and self.user:
1700 self.log_event(log % self.user)
1701
1702 if self.settings.login_form != self:
1703 cas = self.settings.login_form
1704 cas_user = cas.get_user()
1705 if cas_user:
1706 next = cas.logout_url(next)
1707
1708 current.session.auth = None
1709 current.session.flash = self.messages.logged_out
1710 if next:
1711 redirect(next)
1712
1720 """
1721 returns a registration form
1722
1723 .. method:: Auth.register([next=DEFAULT [, onvalidation=DEFAULT
1724 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1725
1726 """
1727
1728 table_user = self.settings.table_user
1729 request = current.request
1730 response = current.response
1731 session = current.session
1732 if self.is_logged_in():
1733 redirect(self.settings.logged_url)
1734 if next == DEFAULT:
1735 next = request.get_vars._next \
1736 or request.post_vars._next \
1737 or self.settings.register_next
1738 if onvalidation == DEFAULT:
1739 onvalidation = self.settings.register_onvalidation
1740 if onaccept == DEFAULT:
1741 onaccept = self.settings.register_onaccept
1742 if log == DEFAULT:
1743 log = self.messages.register_log
1744
1745 passfield = self.settings.password_field
1746 formstyle = self.settings.formstyle
1747 form = SQLFORM(table_user,
1748 fields = self.settings.register_fields,
1749 hidden=dict(_next=next),
1750 showid=self.settings.showid,
1751 submit_button=self.messages.register_button,
1752 delete_label=self.messages.delete_label,
1753 formstyle=formstyle,
1754 separator=self.settings.label_separator
1755 )
1756 for i, row in enumerate(form[0].components):
1757 item = row.element('input',_name=passfield)
1758 if item:
1759 form.custom.widget.password_two = \
1760 INPUT(_name="password_two", _type="password",
1761 requires=IS_EXPR('value==%s' % \
1762 repr(request.vars.get(passfield, None)),
1763 error_message=self.messages.mismatched_password))
1764
1765 addrow(form, self.messages.verify_password + ':',
1766 form.custom.widget.password_two,
1767 self.messages.verify_password_comment,
1768 formstyle,
1769 '%s_%s__row' % (table_user, 'password_two'),
1770 position=i+1)
1771 break
1772 captcha = self.settings.register_captcha or self.settings.captcha
1773 if captcha:
1774 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row')
1775
1776 table_user.registration_key.default = key = web2py_uuid()
1777 if form.accepts(request, session, formname='register',
1778 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1779 description = self.messages.group_description % form.vars
1780 if self.settings.create_user_groups:
1781 group_id = self.add_group("user_%s" % form.vars.id, description)
1782 self.add_membership(group_id, form.vars.id)
1783 if self.settings.registration_requires_verification:
1784 if not self.settings.mailer or \
1785 not self.settings.mailer.send(to=form.vars.email,
1786 subject=self.messages.verify_email_subject,
1787 message=self.messages.verify_email
1788 % dict(key=key)):
1789 self.db.rollback()
1790 response.flash = self.messages.unable_send_email
1791 return form
1792 session.flash = self.messages.email_sent
1793 elif self.settings.registration_requires_approval:
1794 table_user[form.vars.id] = dict(registration_key='pending')
1795 session.flash = self.messages.registration_pending
1796 else:
1797 table_user[form.vars.id] = dict(registration_key='')
1798 session.flash = self.messages.registration_successful
1799 table_user = self.settings.table_user
1800 if 'username' in table_user.fields:
1801 username = 'username'
1802 else:
1803 username = 'email'
1804 user = self.db(table_user[username] == form.vars[username]).select().first()
1805 user = Storage(table_user._filter_fields(user, id=True))
1806 session.auth = Storage(user=user, last_visit=request.now,
1807 expiration=self.settings.expiration,
1808 hmac_key = web2py_uuid())
1809 self.user = user
1810 session.flash = self.messages.logged_in
1811 if log:
1812 self.log_event(log % form.vars)
1813 callback(onaccept,form)
1814 if not next:
1815 next = self.url(args = request.args)
1816 elif isinstance(next, (list, tuple)):
1817 next = next[0]
1818 elif next and not next[0] == '/' and next[:4] != 'http':
1819 next = self.url(next.replace('[id]', str(form.vars.id)))
1820 redirect(next)
1821 return form
1822
1824 """
1825 checks if the user is logged in and returns True/False.
1826 if so user is in auth.user as well as in session.auth.user
1827 """
1828
1829 if self.user:
1830 return True
1831 return False
1832
1839 """
1840 action user to verify the registration email, XXXXXXXXXXXXXXXX
1841
1842 .. method:: Auth.verify_email([next=DEFAULT [, onvalidation=DEFAULT
1843 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1844
1845 """
1846
1847 key = current.request.args[-1]
1848 table_user = self.settings.table_user
1849 user = self.db(table_user.registration_key == key).select().first()
1850 if not user:
1851 redirect(self.settings.login_url)
1852 if self.settings.registration_requires_approval:
1853 user.update_record(registration_key = 'pending')
1854 current.session.flash = self.messages.registration_pending
1855 else:
1856 user.update_record(registration_key = '')
1857 current.session.flash = self.messages.email_verified
1858 if log == DEFAULT:
1859 log = self.messages.verify_email_log
1860 if next == DEFAULT:
1861 next = self.settings.verify_email_next
1862 if onaccept == DEFAULT:
1863 onaccept = self.settings.verify_email_onaccept
1864 if log:
1865 self.log_event(log % user)
1866 callback(onaccept,user)
1867 redirect(next)
1868
1876 """
1877 returns a form to retrieve the user username
1878 (only if there is a username field)
1879
1880 .. method:: Auth.retrieve_username([next=DEFAULT
1881 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1882
1883 """
1884
1885 table_user = self.settings.table_user
1886 if not 'username' in table_user.fields:
1887 raise HTTP(404)
1888 request = current.request
1889 response = current.response
1890 session = current.session
1891 captcha = self.settings.retrieve_username_captcha or \
1892 (self.settings.retrieve_username_captcha!=False and self.settings.captcha)
1893 if not self.settings.mailer:
1894 response.flash = self.messages.function_disabled
1895 return ''
1896 if next == DEFAULT:
1897 next = request.get_vars._next \
1898 or request.post_vars._next \
1899 or self.settings.retrieve_username_next
1900 if onvalidation == DEFAULT:
1901 onvalidation = self.settings.retrieve_username_onvalidation
1902 if onaccept == DEFAULT:
1903 onaccept = self.settings.retrieve_username_onaccept
1904 if log == DEFAULT:
1905 log = self.messages.retrieve_username_log
1906 old_requires = table_user.email.requires
1907 table_user.email.requires = [IS_IN_DB(self.db, table_user.email,
1908 error_message=self.messages.invalid_email)]
1909 form = SQLFORM(table_user,
1910 fields=['email'],
1911 hidden=dict(_next=next),
1912 showid=self.settings.showid,
1913 submit_button=self.messages.submit_button,
1914 delete_label=self.messages.delete_label,
1915 formstyle=self.settings.formstyle,
1916 separator=self.settings.label_separator
1917 )
1918 if captcha:
1919 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row')
1920
1921 if form.accepts(request, session,
1922 formname='retrieve_username', dbio=False,
1923 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1924 user = self.db(table_user.email == form.vars.email).select().first()
1925 if not user:
1926 current.session.flash = \
1927 self.messages.invalid_email
1928 redirect(self.url(args=request.args))
1929 username = user.username
1930 self.settings.mailer.send(to=form.vars.email,
1931 subject=self.messages.retrieve_username_subject,
1932 message=self.messages.retrieve_username
1933 % dict(username=username))
1934 session.flash = self.messages.email_sent
1935 if log:
1936 self.log_event(log % user)
1937 callback(onaccept,form)
1938 if not next:
1939 next = self.url(args = request.args)
1940 elif isinstance(next, (list, tuple)):
1941 next = next[0]
1942 elif next and not next[0] == '/' and next[:4] != 'http':
1943 next = self.url(next.replace('[id]', str(form.vars.id)))
1944 redirect(next)
1945 table_user.email.requires = old_requires
1946 return form
1947
1949 import string
1950 import random
1951 password = ''
1952 specials=r'!#$*'
1953 for i in range(0,3):
1954 password += random.choice(string.lowercase)
1955 password += random.choice(string.uppercase)
1956 password += random.choice(string.digits)
1957 password += random.choice(specials)
1958 return ''.join(random.sample(password,len(password)))
1959
1967 """
1968 returns a form to reset the user password (deprecated)
1969
1970 .. method:: Auth.reset_password_deprecated([next=DEFAULT
1971 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1972
1973 """
1974
1975 table_user = self.settings.table_user
1976 request = current.request
1977 response = current.response
1978 session = current.session
1979 if not self.settings.mailer:
1980 response.flash = self.messages.function_disabled
1981 return ''
1982 if next == DEFAULT:
1983 next = request.get_vars._next \
1984 or request.post_vars._next \
1985 or self.settings.retrieve_password_next
1986 if onvalidation == DEFAULT:
1987 onvalidation = self.settings.retrieve_password_onvalidation
1988 if onaccept == DEFAULT:
1989 onaccept = self.settings.retrieve_password_onaccept
1990 if log == DEFAULT:
1991 log = self.messages.retrieve_password_log
1992 old_requires = table_user.email.requires
1993 table_user.email.requires = [IS_IN_DB(self.db, table_user.email,
1994 error_message=self.messages.invalid_email)]
1995 form = SQLFORM(table_user,
1996 fields=['email'],
1997 hidden=dict(_next=next),
1998 showid=self.settings.showid,
1999 submit_button=self.messages.submit_button,
2000 delete_label=self.messages.delete_label,
2001 formstyle=self.settings.formstyle,
2002 separator=self.settings.label_separator
2003 )
2004 if form.accepts(request, session,
2005 formname='retrieve_password', dbio=False,
2006 onvalidation=onvalidation,hideerror=self.settings.hideerror):
2007 user = self.db(table_user.email == form.vars.email).select().first()
2008 if not user:
2009 current.session.flash = \
2010 self.messages.invalid_email
2011 redirect(self.url(args=request.args))
2012 elif user.registration_key in ('pending','disabled','blocked'):
2013 current.session.flash = \
2014 self.messages.registration_pending
2015 redirect(self.url(args=request.args))
2016 password = self.random_password()
2017 passfield = self.settings.password_field
2018 d = {passfield: table_user[passfield].validate(password)[0],
2019 'registration_key': ''}
2020 user.update_record(**d)
2021 if self.settings.mailer and \
2022 self.settings.mailer.send(to=form.vars.email,
2023 subject=self.messages.retrieve_password_subject,
2024 message=self.messages.retrieve_password \
2025 % dict(password=password)):
2026 session.flash = self.messages.email_sent
2027 else:
2028 session.flash = self.messages.unable_to_send_email
2029 if log:
2030 self.log_event(log % user)
2031 callback(onaccept,form)
2032 if not next:
2033 next = self.url(args = request.args)
2034 elif isinstance(next, (list, tuple)):
2035 next = next[0]
2036 elif next and not next[0] == '/' and next[:4] != 'http':
2037 next = self.url(next.replace('[id]', str(form.vars.id)))
2038 redirect(next)
2039 table_user.email.requires = old_requires
2040 return form
2041
2049 """
2050 returns a form to reset the user password
2051
2052 .. method:: Auth.reset_password([next=DEFAULT
2053 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
2054
2055 """
2056
2057 table_user = self.settings.table_user
2058 request = current.request
2059
2060 session = current.session
2061
2062 if next == DEFAULT:
2063 next = request.get_vars._next \
2064 or request.post_vars._next \
2065 or self.settings.reset_password_next
2066
2067 try:
2068 key = request.vars.key or request.args[-1]
2069 t0 = int(key.split('-')[0])
2070 if time.time()-t0 > 60*60*24: raise Exception
2071 user = self.db(table_user.reset_password_key == key).select().first()
2072 if not user: raise Exception
2073 except Exception:
2074 session.flash = self.messages.invalid_reset_password
2075 redirect(next)
2076 passfield = self.settings.password_field
2077 form = SQLFORM.factory(
2078 Field('new_password', 'password',
2079 label=self.messages.new_password,
2080 requires=self.settings.table_user[passfield].requires),
2081 Field('new_password2', 'password',
2082 label=self.messages.verify_password,
2083 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
2084 self.messages.mismatched_password)]),
2085 submit_button=self.messages.password_reset_button,
2086 formstyle=self.settings.formstyle,
2087 separator=self.settings.label_separator
2088 )
2089 if form.accepts(request,session,hideerror=self.settings.hideerror):
2090 user.update_record(**{passfield:form.vars.new_password,
2091 'registration_key':'',
2092 'reset_password_key':''})
2093 session.flash = self.messages.password_changed
2094 redirect(next)
2095 return form
2096
2104 """
2105 returns a form to reset the user password
2106
2107 .. method:: Auth.reset_password([next=DEFAULT
2108 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
2109
2110 """
2111
2112 table_user = self.settings.table_user
2113 request = current.request
2114 response = current.response
2115 session = current.session
2116 captcha = self.settings.retrieve_password_captcha or \
2117 (self.settings.retrieve_password_captcha!=False and self.settings.captcha)
2118
2119 if next == DEFAULT:
2120 next = request.get_vars._next \
2121 or request.post_vars._next \
2122 or self.settings.request_reset_password_next
2123
2124 if not self.settings.mailer:
2125 response.flash = self.messages.function_disabled
2126 return ''
2127 if onvalidation == DEFAULT:
2128 onvalidation = self.settings.reset_password_onvalidation
2129 if onaccept == DEFAULT:
2130 onaccept = self.settings.reset_password_onaccept
2131 if log == DEFAULT:
2132 log = self.messages.reset_password_log
2133
2134 table_user.email.requires = [
2135 IS_EMAIL(error_message=self.messages.invalid_email),
2136 IS_IN_DB(self.db, table_user.email,
2137 error_message=self.messages.invalid_email)]
2138 form = SQLFORM(table_user,
2139 fields=['email'],
2140 hidden=dict(_next=next),
2141 showid=self.settings.showid,
2142 submit_button=self.messages.password_reset_button,
2143 delete_label=self.messages.delete_label,
2144 formstyle=self.settings.formstyle,
2145 separator=self.settings.label_separator
2146 )
2147 if captcha:
2148 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row')
2149 if form.accepts(request, session,
2150 formname='reset_password', dbio=False,
2151 onvalidation=onvalidation,
2152 hideerror=self.settings.hideerror):
2153 user = self.db(table_user.email == form.vars.email).select().first()
2154 if not user:
2155 session.flash = self.messages.invalid_email
2156 redirect(self.url(args=request.args))
2157 elif user.registration_key in ('pending','disabled','blocked'):
2158 session.flash = self.messages.registration_pending
2159 redirect(self.url(args=request.args))
2160 reset_password_key = str(int(time.time()))+'-' + web2py_uuid()
2161
2162 if self.settings.mailer.send(to=form.vars.email,
2163 subject=self.messages.reset_password_subject,
2164 message=self.messages.reset_password % \
2165 dict(key=reset_password_key)):
2166 session.flash = self.messages.email_sent
2167 user.update_record(reset_password_key=reset_password_key)
2168 else:
2169 session.flash = self.messages.unable_to_send_email
2170 if log:
2171 self.log_event(log % user)
2172 callback(onaccept,form)
2173 if not next:
2174 next = self.url(args = request.args)
2175 elif isinstance(next, (list, tuple)):
2176 next = next[0]
2177 elif next and not next[0] == '/' and next[:4] != 'http':
2178 next = self.url(next.replace('[id]', str(form.vars.id)))
2179 redirect(next)
2180
2181 return form
2182
2194
2202 """
2203 returns a form that lets the user change password
2204
2205 .. method:: Auth.change_password([next=DEFAULT[, onvalidation=DEFAULT[,
2206 onaccept=DEFAULT[, log=DEFAULT]]]])
2207 """
2208
2209 if not self.is_logged_in():
2210 redirect(self.settings.login_url)
2211 db = self.db
2212 table_user = self.settings.table_user
2213 usern = self.settings.table_user_name
2214 s = db(table_user.id == self.user.id)
2215
2216 request = current.request
2217 session = current.session
2218 if next == DEFAULT:
2219 next = request.get_vars._next \
2220 or request.post_vars._next \
2221 or self.settings.change_password_next
2222 if onvalidation == DEFAULT:
2223 onvalidation = self.settings.change_password_onvalidation
2224 if onaccept == DEFAULT:
2225 onaccept = self.settings.change_password_onaccept
2226 if log == DEFAULT:
2227 log = self.messages.change_password_log
2228 passfield = self.settings.password_field
2229 form = SQLFORM.factory(
2230 Field('old_password', 'password',
2231 label=self.messages.old_password,
2232 requires=validators(
2233 table_user[passfield].requires,
2234 IS_IN_DB(s, '%s.%s' % (usern, passfield),
2235 error_message=self.messages.invalid_password))),
2236 Field('new_password', 'password',
2237 label=self.messages.new_password,
2238 requires=table_user[passfield].requires),
2239 Field('new_password2', 'password',
2240 label=self.messages.verify_password,
2241 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
2242 self.messages.mismatched_password)]),
2243 submit_button=self.messages.password_change_button,
2244 formstyle = self.settings.formstyle,
2245 separator=self.settings.label_separator
2246 )
2247 if form.accepts(request, session,
2248 formname='change_password',
2249 onvalidation=onvalidation,
2250 hideerror=self.settings.hideerror):
2251 d = {passfield: form.vars.new_password}
2252 s.update(**d)
2253 session.flash = self.messages.password_changed
2254 if log:
2255 self.log_event(log % self.user)
2256 callback(onaccept,form)
2257 if not next:
2258 next = self.url(args=request.args)
2259 elif isinstance(next, (list, tuple)):
2260 next = next[0]
2261 elif next and not next[0] == '/' and next[:4] != 'http':
2262 next = self.url(next.replace('[id]', str(form.vars.id)))
2263 redirect(next)
2264 return form
2265
2273 """
2274 returns a form that lets the user change his/her profile
2275
2276 .. method:: Auth.profile([next=DEFAULT [, onvalidation=DEFAULT
2277 [, onaccept=DEFAULT [, log=DEFAULT]]]])
2278
2279 """
2280
2281 table_user = self.settings.table_user
2282 if not self.is_logged_in():
2283 redirect(self.settings.login_url)
2284 passfield = self.settings.password_field
2285 self.settings.table_user[passfield].writable = False
2286 request = current.request
2287 session = current.session
2288 if next == DEFAULT:
2289 next = request.get_vars._next \
2290 or request.post_vars._next \
2291 or self.settings.profile_next
2292 if onvalidation == DEFAULT:
2293 onvalidation = self.settings.profile_onvalidation
2294 if onaccept == DEFAULT:
2295 onaccept = self.settings.profile_onaccept
2296 if log == DEFAULT:
2297 log = self.messages.profile_log
2298 form = SQLFORM(
2299 table_user,
2300 self.user.id,
2301 fields = self.settings.profile_fields,
2302 hidden = dict(_next=next),
2303 showid = self.settings.showid,
2304 submit_button = self.messages.profile_save_button,
2305 delete_label = self.messages.delete_label,
2306 upload = self.settings.download_url,
2307 formstyle = self.settings.formstyle,
2308 separator=self.settings.label_separator
2309 )
2310 if form.accepts(request, session,
2311 formname='profile',
2312 onvalidation=onvalidation,hideerror=self.settings.hideerror):
2313 self.user.update(table_user._filter_fields(form.vars))
2314 session.flash = self.messages.profile_updated
2315 if log:
2316 self.log_event(log % self.user)
2317 callback(onaccept,form)
2318 if not next:
2319 next = self.url(args=request.args)
2320 elif isinstance(next, (list, tuple)):
2321 next = next[0]
2322 elif next and not next[0] == '/' and next[:4] != 'http':
2323 next = self.url(next.replace('[id]', str(form.vars.id)))
2324 redirect(next)
2325 return form
2326
2328 return current.session.auth.impersonator
2329
2331 """
2332 usage: POST TO http://..../impersonate request.post_vars.user_id=<id>
2333 set request.post_vars.user_id to 0 to restore original user.
2334
2335 requires impersonator is logged in and
2336 has_permission('impersonate', 'auth_user', user_id)
2337 """
2338 request = current.request
2339 session = current.session
2340 auth = session.auth
2341 if not self.is_logged_in():
2342 raise HTTP(401, "Not Authorized")
2343 current_id = auth.user.id
2344 requested_id = user_id
2345 if user_id == DEFAULT:
2346 user_id = current.request.post_vars.user_id
2347 if user_id and user_id != self.user.id and user_id != '0':
2348 if not self.has_permission('impersonate',
2349 self.settings.table_user_name,
2350 user_id):
2351 raise HTTP(403, "Forbidden")
2352 user = self.settings.table_user(user_id)
2353 if not user:
2354 raise HTTP(401, "Not Authorized")
2355 auth.impersonator = cPickle.dumps(session)
2356 auth.user.update(
2357 self.settings.table_user._filter_fields(user, True))
2358 self.user = auth.user
2359 if self.settings.login_onaccept:
2360 form = Storage(dict(vars=self.user))
2361 self.settings.login_onaccept(form)
2362 log = self.messages.impersonate_log
2363 if log:
2364 self.log_event(log % dict(id=current_id,other_id=auth.user.id))
2365 elif user_id in (0, '0') and self.is_impersonating():
2366 session.clear()
2367 session.update(cPickle.loads(auth.impersonator))
2368 self.user = session.auth.user
2369 if requested_id == DEFAULT and not request.post_vars:
2370 return SQLFORM.factory(Field('user_id','integer'))
2371 return self.user
2372
2374 """
2375 displays the groups and their roles for the logged in user
2376 """
2377
2378 if not self.is_logged_in():
2379 redirect(self.settings.login_url)
2380 memberships = self.db(self.settings.table_membership.user_id
2381 == self.user.id).select()
2382 table = TABLE()
2383 for membership in memberships:
2384 groups = self.db(self.settings.table_group.id
2385 == membership.group_id).select()
2386 if groups:
2387 group = groups[0]
2388 table.append(TR(H3(group.role, '(%s)' % group.id)))
2389 table.append(TR(P(group.description)))
2390 if not memberships:
2391 return None
2392 return table
2393
2395 """
2396 you can change the view for this page to make it look as you like
2397 """
2398
2399 return 'ACCESS DENIED'
2400
2402 """
2403 decorator that prevents access to action if not logged in
2404 """
2405
2406 def decorator(action):
2407
2408 def f(*a, **b):
2409
2410 if self.settings.allow_basic_login_only and not self.basic():
2411 if current.request.is_restful:
2412 raise HTTP(403,"Not authorized")
2413 return call_or_redirect(self.settings.on_failed_authorization)
2414
2415 if not condition:
2416 if current.request.is_restful:
2417 raise HTTP(403,"Not authorized")
2418 if not self.basic() and not self.is_logged_in():
2419 request = current.request
2420 next = URL(r=request,args=request.args,
2421 vars=request.get_vars)
2422 current.session.flash = current.response.flash
2423 return call_or_redirect(
2424 self.settings.on_failed_authentication,
2425 self.settings.login_url + '?_next='+urllib.quote(next))
2426 else:
2427 current.session.flash = self.messages.access_denied
2428 return call_or_redirect(self.settings.on_failed_authorization)
2429 return action(*a, **b)
2430 f.__doc__ = action.__doc__
2431 f.__name__ = action.__name__
2432 f.__dict__.update(action.__dict__)
2433 return f
2434
2435 return decorator
2436
2438 """
2439 decorator that prevents access to action if not logged in
2440 """
2441
2442 def decorator(action):
2443
2444 def f(*a, **b):
2445
2446 if self.settings.allow_basic_login_only and not self.basic():
2447 if current.request.is_restful:
2448 raise HTTP(403,"Not authorized")
2449 return call_or_redirect(self.settings.on_failed_authorization)
2450
2451 if not self.basic() and not self.is_logged_in():
2452 if current.request.is_restful:
2453 raise HTTP(403,"Not authorized")
2454 request = current.request
2455 next = URL(r=request,args=request.args,
2456 vars=request.get_vars)
2457 current.session.flash = current.response.flash
2458 return call_or_redirect(
2459 self.settings.on_failed_authentication,
2460 self.settings.login_url + '?_next='+urllib.quote(next)
2461 )
2462 return action(*a, **b)
2463 f.__doc__ = action.__doc__
2464 f.__name__ = action.__name__
2465 f.__dict__.update(action.__dict__)
2466 return f
2467
2468 return decorator
2469
2471 """
2472 decorator that prevents access to action if not logged in or
2473 if user logged in is not a member of group_id.
2474 If role is provided instead of group_id then the
2475 group_id is calculated.
2476 """
2477
2478 def decorator(action):
2479 def f(*a, **b):
2480 if self.settings.allow_basic_login_only and not self.basic():
2481 if current.request.is_restful:
2482 raise HTTP(403,"Not authorized")
2483 return call_or_redirect(self.settings.on_failed_authorization)
2484
2485 if not self.basic() and not self.is_logged_in():
2486 if current.request.is_restful:
2487 raise HTTP(403,"Not authorized")
2488 request = current.request
2489 next = URL(r=request,args=request.args,
2490 vars=request.get_vars)
2491 current.session.flash = current.response.flash
2492 return call_or_redirect(
2493 self.settings.on_failed_authentication,
2494 self.settings.login_url + '?_next='+urllib.quote(next)
2495 )
2496 if not self.has_membership(group_id=group_id, role=role):
2497 current.session.flash = self.messages.access_denied
2498 return call_or_redirect(self.settings.on_failed_authorization)
2499 return action(*a, **b)
2500 f.__doc__ = action.__doc__
2501 f.__name__ = action.__name__
2502 f.__dict__.update(action.__dict__)
2503 return f
2504
2505 return decorator
2506
2507
2514 """
2515 decorator that prevents access to action if not logged in or
2516 if user logged in is not a member of any group (role) that
2517 has 'name' access to 'table_name', 'record_id'.
2518 """
2519
2520 def decorator(action):
2521
2522 def f(*a, **b):
2523 if self.settings.allow_basic_login_only and not self.basic():
2524 if current.request.is_restful:
2525 raise HTTP(403,"Not authorized")
2526 return call_or_redirect(self.settings.on_failed_authorization)
2527
2528 if not self.basic() and not self.is_logged_in():
2529 if current.request.is_restful:
2530 raise HTTP(403,"Not authorized")
2531 request = current.request
2532 next = URL(r=request,args=request.args,
2533 vars=request.get_vars)
2534 current.session.flash = current.response.flash
2535 return call_or_redirect(
2536 self.settings.on_failed_authentication,
2537 self.settings.login_url + '?_next='+urllib.quote(next)
2538 )
2539 if not self.has_permission(name, table_name, record_id):
2540 current.session.flash = self.messages.access_denied
2541 return call_or_redirect(self.settings.on_failed_authorization)
2542 return action(*a, **b)
2543 f.__doc__ = action.__doc__
2544 f.__name__ = action.__name__
2545 f.__dict__.update(action.__dict__)
2546 return f
2547
2548 return decorator
2549
2551 """
2552 decorator that prevents access to action if not logged in or
2553 if user logged in is not a member of group_id.
2554 If role is provided instead of group_id then the
2555 group_id is calculated.
2556 """
2557
2558 def decorator(action):
2559 def f(*a, **b):
2560 if self.settings.allow_basic_login_only and not self.basic():
2561 if current.request.is_restful:
2562 raise HTTP(403,"Not authorized")
2563 return call_or_redirect(self.settings.on_failed_authorization)
2564
2565 if not self.basic() and not self.is_logged_in():
2566 if current.request.is_restful:
2567 raise HTTP(403,"Not authorized")
2568 request = current.request
2569 next = URL(r=request,args=request.args,
2570 vars=request.get_vars)
2571 current.session.flash = current.response.flash
2572 return call_or_redirect(
2573 self.settings.on_failed_authentication,
2574 self.settings.login_url + '?_next='+urllib.quote(next)
2575 )
2576 if not URL.verify(current.request,user_signature=True):
2577 current.session.flash = self.messages.access_denied
2578 return call_or_redirect(self.settings.on_failed_authorization)
2579 return action(*a, **b)
2580 f.__doc__ = action.__doc__
2581 f.__name__ = action.__name__
2582 f.__dict__.update(action.__dict__)
2583 return f
2584
2585 return decorator
2586
2588 """
2589 creates a group associated to a role
2590 """
2591
2592 group_id = self.settings.table_group.insert(role=role,
2593 description=description)
2594 log = self.messages.add_group_log
2595 if log:
2596 self.log_event(log % dict(group_id=group_id, role=role))
2597 return group_id
2598
2600 """
2601 deletes a group
2602 """
2603
2604 self.db(self.settings.table_group.id == group_id).delete()
2605 self.db(self.settings.table_membership.group_id
2606 == group_id).delete()
2607 self.db(self.settings.table_permission.group_id
2608 == group_id).delete()
2609 log = self.messages.del_group_log
2610 if log:
2611 self.log_event(log % dict(group_id=group_id))
2612
2614 """
2615 returns the group_id of the group specified by the role
2616 """
2617 rows = self.db(self.settings.table_group.role == role).select()
2618 if not rows:
2619 return None
2620 return rows[0].id
2621
2623 """
2624 returns the group_id of the group uniquely associated to this user
2625 i.e. role=user:[user_id]
2626 """
2627 if not user_id and self.user:
2628 user_id = self.user.id
2629 role = 'user_%s' % user_id
2630 return self.id_group(role)
2631
2633 """
2634 checks if user is member of group_id or role
2635 """
2636
2637 group_id = group_id or self.id_group(role)
2638 try:
2639 group_id = int(group_id)
2640 except:
2641 group_id = self.id_group(group_id)
2642 if not user_id and self.user:
2643 user_id = self.user.id
2644 membership = self.settings.table_membership
2645 if self.db((membership.user_id == user_id)
2646 & (membership.group_id == group_id)).select():
2647 r = True
2648 else:
2649 r = False
2650 log = self.messages.has_membership_log
2651 if log:
2652 self.log_event(log % dict(user_id=user_id,
2653 group_id=group_id, check=r))
2654 return r
2655
2657 """
2658 gives user_id membership of group_id or role
2659 if user_id==None than user_id is that of current logged in user
2660 """
2661
2662 group_id = group_id or self.id_group(role)
2663 try:
2664 group_id = int(group_id)
2665 except:
2666 group_id = self.id_group(group_id)
2667 if not user_id and self.user:
2668 user_id = self.user.id
2669 membership = self.settings.table_membership
2670 record = membership(user_id = user_id,group_id = group_id)
2671 if record:
2672 return record.id
2673 else:
2674 id = membership.insert(group_id=group_id, user_id=user_id)
2675 log = self.messages.add_membership_log
2676 if log:
2677 self.log_event(log % dict(user_id=user_id,
2678 group_id=group_id))
2679 return id
2680
2682 """
2683 revokes membership from group_id to user_id
2684 if user_id==None than user_id is that of current logged in user
2685 """
2686
2687 group_id = group_id or self.id_group(role)
2688 if not user_id and self.user:
2689 user_id = self.user.id
2690 membership = self.settings.table_membership
2691 log = self.messages.del_membership_log
2692 if log:
2693 self.log_event(log % dict(user_id=user_id,
2694 group_id=group_id))
2695 return self.db(membership.user_id
2696 == user_id)(membership.group_id
2697 == group_id).delete()
2698
2699 - def has_permission(
2700 self,
2701 name='any',
2702 table_name='',
2703 record_id=0,
2704 user_id=None,
2705 group_id=None,
2706 ):
2707 """
2708 checks if user_id or current logged in user is member of a group
2709 that has 'name' permission on 'table_name' and 'record_id'
2710 if group_id is passed, it checks whether the group has the permission
2711 """
2712
2713 if not user_id and not group_id and self.user:
2714 user_id = self.user.id
2715 if user_id:
2716 membership = self.settings.table_membership
2717 rows = self.db(membership.user_id
2718 == user_id).select(membership.group_id)
2719 groups = set([row.group_id for row in rows])
2720 if group_id