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