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

Source Code for Module gluon.tools

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