Package web2py :: Package gluon :: Module tools
[hide private]
[frames] | no frames]

Source Code for Module web2py.gluon.tools

   1  #!/bin/python 
   2  # -*- coding: utf-8 -*- 
   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      # try stdlib (Python 2.6) 
  42      import json as json_parser 
  43  except ImportError: 
  44      try: 
  45          # try external module 
  46          import simplejson as json_parser 
  47      except: 
  48          # fallback to pure-Python module 
  49          import contrib.simplejson as json_parser 
  50   
  51  __all__ = ['Mail', 'Auth', 'Recaptcha', 'Crud', 'Service', 'Wiki', 
  52             'PluginManager', 'fetch', 'geocode', 'prettydate'] 
  53   
  54  ### mind there are two loggers here (logger and crud.settings.logger)! 
  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
79 -def validators(*a):
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
89 -def call_or_redirect(f, *args):
90 if callable(f): 91 redirect(f(*args)) 92 else: 93 redirect(f)
94 95
96 -def replace_id(url, form):
97 if url: 98 url = url.replace('[id]', str(form.vars.id)) 99 if url[0] == '/' or url[:4] == 'http': 100 return url 101 return URL(url)
102 103
104 -class Mail(object):
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
112 - class Attachment(MIMEBase.MIMEBase):
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 # encoded or raw text 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 # no encoding configuration for raw messages 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 # No charset passed to avoid transport encoding 378 # NOTE: some unicode encoded strings will produce 379 # unreadable mail contents. 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 # CIPHER # 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 # GPGME # 436 ####################################################### 437 if cipher_type == 'gpg': 438 if self.settings.gpg_home: 439 # Set GNUPGHOME environment variable to set home of gnupg 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 # need a python-pyme package and gpgme lib 447 from pyme import core, errors 448 from pyme.constants.sig import mode 449 ############################################ 450 # sign # 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 # search for signing key for From: 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 # make a signature 471 c.op_sign(plain, sig, mode.DETACH) 472 sig.seek(0, 0) 473 # make it part of the email 474 payload = MIMEMultipart.MIMEMultipart('signed', 475 boundary=None, 476 _subparts=None, 477 **dict( 478 micalg="pgp-sha1", 479 protocol="application/pgp-signature")) 480 # insert the origin payload 481 payload.attach(payload_in) 482 # insert the detached signature 483 p = MIMEBase.MIMEBase("application", 'pgp-signature') 484 p.set_payload(sig.read()) 485 payload.attach(p) 486 # it's just a trick to handle the no encryption case 487 payload_in = payload 488 except errors.GPGMEError, ex: 489 self.error = "GPG error: %s" % ex.getstring() 490 return False 491 ############################################ 492 # encrypt # 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 # collect the public keys for encryption 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 # make the encryption 516 c.op_encrypt(recipients, 1, plain, cipher) 517 cipher.seek(0, 0) 518 # make it a part of the email 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 # X.509 # 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 # if there is no sign certfile we'll assume the 544 # cert is in keyfile 545 x509_sign_certfile = self.settings.x509_sign_keyfile 546 # crypt certfiles could be a string or a list 547 x509_crypt_certfiles = self.settings.x509_crypt_certfiles 548 x509_nocerts = self.settings.x509_nocerts 549 550 # need m2crypto 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 # SIGN 560 if sign: 561 #key for signing 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 )) # Recreate coz sign() has consumed it. 578 except Exception, e: 579 self.error = "Something went wrong on signing: <%s> %s" % ( 580 str(e), str(flags)) 581 return False 582 583 # ENCRYPT 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 # make an encryption cert's stack 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 # Final stage in sign and encryption 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 # no cryptography process as usual 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
695 -class Recaptcha(DIV):
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
737 - def _validate(self):
738 739 # for local testing: 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 # In case we get an error code, store it so we can get an error message 774 # from the /api/challenge URL as described in the reCAPTCHA api docs. 775 self.error = return_values[1] 776 self.errors['captcha'] = self.error_message 777 return False
778
779 - def xml(self):
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
835 -class Auth(object):
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, # one hour 855 long_expiration=3600 * 30 * 24, # one month 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 # ## these are messages that can be customized 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
1057 - def get_or_create_key(filename=None, alg='sha512'):
1058 request = current.request 1059 if not filename: 1060 filename = os.path.join(request.folder, 'private', 'auth.key') 1061 if os.path.exists(filename): 1062 key = open(filename, 'r').read().strip() 1063 else: 1064 key = alg + ':' + web2py_uuid() 1065 open(filename, 'w').write(key) 1066 return key
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
1076 - def here(self):
1077 return URL(args=current.request.args, vars=current.request.vars)
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 ## next two lines for backward compatibility 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 # this is a trick to speed up sessions 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 # ## what happens after login? 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 # ## what happens after registration? 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 # ## these are messages that can be customized 1173 messages = self.messages = Messages(current.T) 1174 messages.update(Auth.default_messages) 1175 messages.lock_keys = True 1176 1177 # for "remember me" option 1178 response = current.response 1179 if auth and auth.remember: 1180 # when user wants to be logged in for longer 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
1188 - def _get_user_id(self):
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
1194 - def table_user(self):
1195 return self.db[self.settings.table_user_name]
1196
1197 - def table_group(self):
1198 return self.db[self.settings.table_group_name]
1199
1200 - def table_membership(self):
1201 return self.db[self.settings.table_membership_name]
1202
1203 - def table_permission(self):
1204 return self.db[self.settings.table_permission_name]
1205
1206 - def table_event(self):
1207 return self.db[self.settings.table_event_name]
1208
1209 - def table_cas(self):
1210 return self.db[self.settings.table_cas_name]
1211
1212 - def _HTTP(self, *a, **b):
1213 """ 1214 only used in lambda: self._HTTP(404) 1215 """ 1216 1217 raise HTTP(*a, **b)
1218
1219 - def __call__(self):
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)))) # the space before T('Logout') is intentional. It creates a gap between icon and text 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 # logout will be the last item in list 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'))) # the space before T('Login') is intentional. It creates a gap between icon and text 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 # login will be the last item in list 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
1358 - def __get_migrate(self, tablename, migrate=True):
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
1406 - def define_signature(self):
1407 db = self.db 1408 settings = self.settings 1409 request = current.request 1410 T = current.T 1411 reference_user = 'reference %s' % settings.table_user_name 1412 1413 def lazy_user(auth=self): 1414 return auth.user_id
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: # THIS IS NOT LAZY 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 # user unknown 1694 vars = vars or {} 1695 self.table_event().insert( 1696 description=str(description % vars), 1697 origin=origin, user_id=user_id)
1698
1699 - def get_or_create_user(self, keys, update_fields=['email']):
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 # make a guess about who this user is 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 # if we think we found the user but registration_id does not match, 1722 # make new user 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 # THINK MORE ABOUT THIS? DO WE TRUST OPENID PROVIDER? 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
1748 - def basic(self):
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
1762 - def login_user(self, user):
1763 """ 1764 login the user = db.auth_user(id) 1765 """ 1766 from gluon.settings import global_settings 1767 if global_settings.web2py_runtime_gae: 1768 user = Row(self.db.auth_user._filter_fields(user, id=True)) 1769 delattr(user,'password') 1770 else: 1771 user = Row(user) 1772 for key,value in user.items(): 1773 if callable(value) or key=='password': 1774 delattr(user,key) 1775 current.session.auth = Storage( 1776 user = user, 1777 last_visit=current.request.now, 1778 expiration=self.settings.expiration, 1779 hmac_key=web2py_uuid()) 1780 self.user = user 1781 self.update_groups()
1782
1783 - def login_bare(self, username, password):
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 # user not in database try other login methods 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
1809 - def cas_login( 1810 self, 1811 next=DEFAULT, 1812 onvalidation=DEFAULT, 1813 onaccept=DEFAULT, 1814 log=DEFAULT, 1815 version=2, 1816 ):
1817 request = current.request 1818 response = current.response 1819 session = current.session 1820 db, table = self.db, self.table_cas() 1821 session._cas_service = request.vars.service or session._cas_service 1822 if not request.env.http_host in self.settings.cas_domains or \ 1823 not session._cas_service: 1824 raise HTTP(403, 'not authorized') 1825 1826 def allow_access(interactivelogin=False): 1827 row = table(service=session._cas_service, user_id=self.user.id) 1828 if row: 1829 ticket = row.ticket 1830 else: 1831 ticket = 'ST-' + web2py_uuid() 1832 table.insert(service=session._cas_service, 1833 user_id=self.user.id, 1834 ticket=ticket, 1835 created_on=request.now, 1836 renew=interactivelogin) 1837 service = session._cas_service 1838 query_sep = '&' if '?' in service else '?' 1839 del session._cas_service 1840 if 'warn' in request.vars and not interactivelogin: 1841 response.headers[ 1842 'refresh'] = "5;URL=%s" % service + query_sep + "ticket=" + ticket 1843 return A("Continue to %s" % service, 1844 _href=service + query_sep + "ticket=" + ticket) 1845 else: 1846 redirect(service + query_sep + "ticket=" + ticket)
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
1858 - def cas_validate(self, version=2, proxy=False):
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 # If ticket is a service Ticket and RENEW flag respected 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: # assume version 2 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
1907 - def login( 1908 self, 1909 next=DEFAULT, 1910 onvalidation=DEFAULT, 1911 onaccept=DEFAULT, 1912 log=DEFAULT, 1913 ):
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 ### use session for federated login 1948 if self.next: 1949 session._auth_next = self.next 1950 elif session._auth_next: 1951 self.next = session._auth_next 1952 ### pass 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 # default 1966 1967 # do we use our own login form, or from a central source? 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 ## adds a new input checkbox "remember me for longer" 1982 if self.settings.formstyle != 'bootstrap': 1983 addrow(form, XML("&nbsp;"), 1984 DIV(XML("&nbsp;"), 1985 INPUT(_type='checkbox', 1986 _class='checkbox', 1987 _id="auth_user_remember", 1988 _name="remember", 1989 ), 1990 XML("&nbsp;&nbsp;"), 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 # check for username in db 2024 user = self.db(table_user[username] 2025 == form.vars[username]).select().first() 2026 if user: 2027 # user in db, check if registration pending or disabled 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 # try alternate logins 1st as these have the 2041 # current version of the password 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 # do not store password in db 2049 form.vars[passfield] = None 2050 user = self.get_or_create_user(form.vars) 2051 break 2052 if not user: 2053 # alternates have failed, maybe because service inaccessible 2054 if self.settings.login_methods[0] == self: 2055 # try logging in locally using cached credentials 2056 if form.vars.get(passfield, '') == temp_user[passfield]: 2057 # success 2058 user = temp_user 2059 else: 2060 # user not in db 2061 if not self.settings.alternate_requires_registration: 2062 # we're allowed to auto-register users from external systems 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 # do not store password in db 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 # invalid login 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 # use a central authentication server 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 # we need to pass through login again before going on 2095 next = self.url(self.settings.function, args='login') 2096 redirect(cas.login_url(next), client_side=True) 2097 2098 # process authenticated users 2099 if user: 2100 user = Row(table_user._filter_fields(user, id=True)) 2101 # process authenticated users 2102 # user wants to be logged in for longer 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 # how to continue 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
2130 - def logout(self, next=DEFAULT, onlogout=DEFAULT, log=DEFAULT):
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
2160 - def register( 2161 self, 2162 next=DEFAULT, 2163 onvalidation=DEFAULT, 2164 onaccept=DEFAULT, 2165 log=DEFAULT, 2166 ):
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 # Ensure the username field is unique. 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
2299 - def is_logged_in(self):
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
2309 - def verify_email( 2310 self, 2311 next=DEFAULT, 2312 onaccept=DEFAULT, 2313 log=DEFAULT, 2314 ):
2315 """ 2316 action user to verify the registration email, XXXXXXXXXXXXXXXX 2317 2318 method: Auth.verify_email([next=DEFAULT [, onvalidation=DEFAULT 2319 [, onaccept=DEFAULT [, log=DEFAULT]]]]) 2320 2321 """ 2322 2323 key = getarg(-1) 2324 table_user = self.table_user() 2325 user = table_user(registration_key=key) 2326 if not user: 2327 redirect(self.settings.login_url) 2328 if self.settings.registration_requires_approval: 2329 user.update_record(registration_key='pending') 2330 current.session.flash = self.messages.registration_pending 2331 else: 2332 user.update_record(registration_key='') 2333 current.session.flash = self.messages.email_verified 2334 # make sure session has same user.registrato_key as db record 2335 if current.session.auth and current.session.auth.user: 2336 current.session.auth.user.registration_key = user.registration_key 2337 if log is DEFAULT: 2338 log = self.messages.verify_email_log 2339 if next is DEFAULT: 2340 next = self.settings.verify_email_next 2341 if onaccept is DEFAULT: 2342 onaccept = self.settings.verify_email_onaccept 2343 self.log_event(log, user) 2344 callback(onaccept, user) 2345 redirect(next)
2346
2347 - def retrieve_username( 2348 self, 2349 next=DEFAULT, 2350 onvalidation=DEFAULT, 2351 onaccept=DEFAULT, 2352 log=DEFAULT, 2353 ):
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
2422 - def random_password(self):
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
2434 - def reset_password_deprecated( 2435 self, 2436 next=DEFAULT, 2437 onvalidation=DEFAULT, 2438 onaccept=DEFAULT, 2439 log=DEFAULT, 2440 ):
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
2512 - def reset_password( 2513 self, 2514 next=DEFAULT, 2515 onvalidation=DEFAULT, 2516 onaccept=DEFAULT, 2517 log=DEFAULT, 2518 ):
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 # response = current.response 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
2572 - def request_reset_password( 2573 self, 2574 next=DEFAULT, 2575 onvalidation=DEFAULT, 2576 onaccept=DEFAULT, 2577 log=DEFAULT, 2578 ):
2579 """ 2580 returns a form to reset the user password 2581 2582 method: Auth.reset_password([next=DEFAULT 2583 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]]) 2584 2585 """ 2586 table_user = self.table_user() 2587 request = current.request 2588 response = current.response 2589 session = current.session 2590 captcha = self.settings.retrieve_password_captcha or \ 2591 (self.settings.retrieve_password_captcha != False and self.settings.captcha)