1
2
3
4 """
5 This file is part of the web2py Web Framework
6 Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
7 License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
8 """
9
10 import base64
11 import cPickle
12 import datetime
13 import thread
14 import logging
15 import sys
16 import glob
17 import os
18 import re
19 import time
20 import smtplib
21 import urllib
22 import urllib2
23 import Cookie
24 import cStringIO
25 from email import MIMEBase, MIMEMultipart, MIMEText, Encoders, Header, message_from_string
26
27 from contenttype import contenttype
28 from storage import Storage, PickleableStorage, StorageList, Settings, Messages
29 from utils import web2py_uuid
30 from fileutils import read_file
31 from gluon import *
32
33 import serializers
34
35 try:
36 import json as json_parser
37 except ImportError:
38 try:
39 import simplejson as json_parser
40 except:
41 import contrib.simplejson as json_parser
42
43 __all__ = ['Mail', 'Auth', 'Recaptcha', 'Crud', 'Service',
44 'PluginManager', 'fetch', 'geocode', 'prettydate']
45
46
47 logger = logging.getLogger("web2py")
48
49 DEFAULT = lambda: None
50
51 -def callback(actions,form,tablename=None):
52 if actions:
53 if tablename and isinstance(actions,dict):
54 actions = actions.get(tablename, [])
55 if not isinstance(actions,(list, tuple)):
56 actions = [actions]
57 [action(form) for action in actions]
58
60 b = []
61 for item in a:
62 if isinstance(item, (list, tuple)):
63 b = b + list(item)
64 else:
65 b.append(item)
66 return b
67
73
75 if url and not url[0] == '/' and url[:4] != 'http':
76 return URL(url.replace('[id]', str(form.vars.id)))
77 return url
78
80 """
81 Class for configuring and sending emails with alternative text / html
82 body, multiple attachments and encryption support
83
84 Works with SMTP and Google App Engine.
85 """
86
88 """
89 Email attachment
90
91 Arguments:
92
93 payload: path to file or file-like object with read() method
94 filename: name of the attachment stored in message; if set to
95 None, it will be fetched from payload path; file-like
96 object payload must have explicit filename specified
97 content_id: id of the attachment; automatically contained within
98 < and >
99 content_type: content type of the attachment; if set to None,
100 it will be fetched from filename using gluon.contenttype
101 module
102 encoding: encoding of all strings passed to this function (except
103 attachment body)
104
105 Content ID is used to identify attachments within the html body;
106 in example, attached image with content ID 'photo' may be used in
107 html message as a source of img tag <img src="cid:photo" />.
108
109 Examples:
110
111 #Create attachment from text file:
112 attachment = Mail.Attachment('/path/to/file.txt')
113
114 Content-Type: text/plain
115 MIME-Version: 1.0
116 Content-Disposition: attachment; filename="file.txt"
117 Content-Transfer-Encoding: base64
118
119 SOMEBASE64CONTENT=
120
121 #Create attachment from image file with custom filename and cid:
122 attachment = Mail.Attachment('/path/to/file.png',
123 filename='photo.png',
124 content_id='photo')
125
126 Content-Type: image/png
127 MIME-Version: 1.0
128 Content-Disposition: attachment; filename="photo.png"
129 Content-Id: <photo>
130 Content-Transfer-Encoding: base64
131
132 SOMEOTHERBASE64CONTENT=
133 """
134
135 - def __init__(
136 self,
137 payload,
138 filename=None,
139 content_id=None,
140 content_type=None,
141 encoding='utf-8'):
142 if isinstance(payload, str):
143 if filename is None:
144 filename = os.path.basename(payload)
145 payload = read_file(payload, 'rb')
146 else:
147 if filename is None:
148 raise Exception('Missing attachment name')
149 payload = payload.read()
150 filename = filename.encode(encoding)
151 if content_type is None:
152 content_type = contenttype(filename)
153 self.my_filename = filename
154 self.my_payload = payload
155 MIMEBase.MIMEBase.__init__(self, *content_type.split('/', 1))
156 self.set_payload(payload)
157 self['Content-Disposition'] = 'attachment; filename="%s"' % filename
158 if not content_id is None:
159 self['Content-Id'] = '<%s>' % content_id.encode(encoding)
160 Encoders.encode_base64(self)
161
162 - def __init__(self, server=None, sender=None, login=None, tls=True):
163 """
164 Main Mail object
165
166 Arguments:
167
168 server: SMTP server address in address:port notation
169 sender: sender email address
170 login: sender login name and password in login:password notation
171 or None if no authentication is required
172 tls: enables/disables encryption (True by default)
173
174 In Google App Engine use:
175
176 server='gae'
177
178 For sake of backward compatibility all fields are optional and default
179 to None, however, to be able to send emails at least server and sender
180 must be specified. They are available under following fields:
181
182 mail.settings.server
183 mail.settings.sender
184 mail.settings.login
185
186 When server is 'logging', email is logged but not sent (debug mode)
187
188 Optionally you can use PGP encryption or X509:
189
190 mail.settings.cipher_type = None
191 mail.settings.sign = True
192 mail.settings.sign_passphrase = None
193 mail.settings.encrypt = True
194 mail.settings.x509_sign_keyfile = None
195 mail.settings.x509_sign_certfile = None
196 mail.settings.x509_crypt_certfiles = None
197
198 cipher_type : None
199 gpg - need a python-pyme package and gpgme lib
200 x509 - smime
201 sign : sign the message (True or False)
202 sign_passphrase : passphrase for key signing
203 encrypt : encrypt the message
204 ... x509 only ...
205 x509_sign_keyfile : the signers private key filename (PEM format)
206 x509_sign_certfile: the signers certificate filename (PEM format)
207 x509_crypt_certfiles: the certificates file to encrypt the messages
208 with can be a file name or a list of
209 file names (PEM format)
210
211 Examples:
212
213 #Create Mail object with authentication data for remote server:
214 mail = Mail('example.com:25', 'me@example.com', 'me:password')
215 """
216
217 settings = self.settings = Settings()
218 settings.server = server
219 settings.sender = sender
220 settings.login = login
221 settings.tls = tls
222 settings.ssl = False
223 settings.cipher_type = None
224 settings.sign = True
225 settings.sign_passphrase = None
226 settings.encrypt = True
227 settings.x509_sign_keyfile = None
228 settings.x509_sign_certfile = None
229 settings.x509_crypt_certfiles = None
230 settings.debug = False
231 settings.lock_keys = True
232 self.result = {}
233 self.error = None
234
235 - def send(
236 self,
237 to,
238 subject='None',
239 message='None',
240 attachments=None,
241 cc=None,
242 bcc=None,
243 reply_to=None,
244 encoding='utf-8',
245 raw=False,
246 headers={}
247 ):
248 """
249 Sends an email using data specified in constructor
250
251 Arguments:
252
253 to: list or tuple of receiver addresses; will also accept single
254 object
255 subject: subject of the email
256 message: email body text; depends on type of passed object:
257 if 2-list or 2-tuple is passed: first element will be
258 source of plain text while second of html text;
259 otherwise: object will be the only source of plain text
260 and html source will be set to None;
261 If text or html source is:
262 None: content part will be ignored,
263 string: content part will be set to it,
264 file-like object: content part will be fetched from
265 it using it's read() method
266 attachments: list or tuple of Mail.Attachment objects; will also
267 accept single object
268 cc: list or tuple of carbon copy receiver addresses; will also
269 accept single object
270 bcc: list or tuple of blind carbon copy receiver addresses; will
271 also accept single object
272 reply_to: address to which reply should be composed
273 encoding: encoding of all strings passed to this method (including
274 message bodies)
275 headers: dictionary of headers to refine the headers just before
276 sending mail, e.g. {'Return-Path' : 'bounces@example.org'}
277
278 Examples:
279
280 #Send plain text message to single address:
281 mail.send('you@example.com',
282 'Message subject',
283 'Plain text body of the message')
284
285 #Send html message to single address:
286 mail.send('you@example.com',
287 'Message subject',
288 '<html>Plain text body of the message</html>')
289
290 #Send text and html message to three addresses (two in cc):
291 mail.send('you@example.com',
292 'Message subject',
293 ('Plain text body', '<html>html body</html>'),
294 cc=['other1@example.com', 'other2@example.com'])
295
296 #Send html only message with image attachment available from
297 the message by 'photo' content id:
298 mail.send('you@example.com',
299 'Message subject',
300 (None, '<html><img src="cid:photo" /></html>'),
301 Mail.Attachment('/path/to/photo.jpg'
302 content_id='photo'))
303
304 #Send email with two attachments and no body text
305 mail.send('you@example.com,
306 'Message subject',
307 None,
308 [Mail.Attachment('/path/to/fist.file'),
309 Mail.Attachment('/path/to/second.file')])
310
311 Returns True on success, False on failure.
312
313 Before return, method updates two object's fields:
314 self.result: return value of smtplib.SMTP.sendmail() or GAE's
315 mail.send_mail() method
316 self.error: Exception message or None if above was successful
317 """
318
319 def encode_header(key):
320 if [c for c in key if 32>ord(c) or ord(c)>127]:
321 return Header.Header(key.encode('utf-8'),'utf-8')
322 else:
323 return key
324
325
326 def encoded_or_raw(text):
327 if raw:
328 text = encode_header(text)
329 return text
330
331 if not isinstance(self.settings.server, str):
332 raise Exception('Server address not specified')
333 if not isinstance(self.settings.sender, str):
334 raise Exception('Sender address not specified')
335
336 if not raw:
337 payload_in = MIMEMultipart.MIMEMultipart('mixed')
338 else:
339
340 if isinstance(message, basestring):
341 text = message.decode(encoding).encode('utf-8')
342 else:
343 text = message.read().decode(encoding).encode('utf-8')
344
345
346
347 payload_in = MIMEText.MIMEText(text)
348 if to:
349 if not isinstance(to, (list,tuple)):
350 to = [to]
351 else:
352 raise Exception('Target receiver address not specified')
353 if cc:
354 if not isinstance(cc, (list, tuple)):
355 cc = [cc]
356 if bcc:
357 if not isinstance(bcc, (list, tuple)):
358 bcc = [bcc]
359 if message is None:
360 text = html = None
361 elif isinstance(message, (list, tuple)):
362 text, html = message
363 elif message.strip().startswith('<html') and message.strip().endswith('</html>'):
364 text = self.settings.server=='gae' and message or None
365 html = message
366 else:
367 text = message
368 html = None
369
370 if (not text is None or not html is None) and (not raw):
371 attachment = MIMEMultipart.MIMEMultipart('alternative')
372 if not text is None:
373 if isinstance(text, basestring):
374 text = text.decode(encoding).encode('utf-8')
375 else:
376 text = text.read().decode(encoding).encode('utf-8')
377 attachment.attach(MIMEText.MIMEText(text,_charset='utf-8'))
378 if not html is None:
379 if isinstance(html, basestring):
380 html = html.decode(encoding).encode('utf-8')
381 else:
382 html = html.read().decode(encoding).encode('utf-8')
383 attachment.attach(MIMEText.MIMEText(html, 'html',_charset='utf-8'))
384 payload_in.attach(attachment)
385 if (attachments is None) or raw:
386 pass
387 elif isinstance(attachments, (list, tuple)):
388 for attachment in attachments:
389 payload_in.attach(attachment)
390 else:
391 payload_in.attach(attachments)
392
393
394
395
396
397 cipher_type = self.settings.cipher_type
398 sign = self.settings.sign
399 sign_passphrase = self.settings.sign_passphrase
400 encrypt = self.settings.encrypt
401
402
403
404 if cipher_type == 'gpg':
405 if not sign and not encrypt:
406 self.error="No sign and no encrypt is set but cipher type to gpg"
407 return False
408
409
410 from pyme import core, errors
411 from pyme.constants.sig import mode
412
413
414
415 if sign:
416 import string
417 core.check_version(None)
418 pin=string.replace(payload_in.as_string(),'\n','\r\n')
419 plain = core.Data(pin)
420 sig = core.Data()
421 c = core.Context()
422 c.set_armor(1)
423 c.signers_clear()
424
425 for sigkey in c.op_keylist_all(self.settings.sender, 1):
426 if sigkey.can_sign:
427 c.signers_add(sigkey)
428 if not c.signers_enum(0):
429 self.error='No key for signing [%s]' % self.settings.sender
430 return False
431 c.set_passphrase_cb(lambda x,y,z: sign_passphrase)
432 try:
433
434 c.op_sign(plain,sig,mode.DETACH)
435 sig.seek(0,0)
436
437 payload=MIMEMultipart.MIMEMultipart('signed',
438 boundary=None,
439 _subparts=None,
440 **dict(micalg="pgp-sha1",
441 protocol="application/pgp-signature"))
442
443 payload.attach(payload_in)
444
445 p=MIMEBase.MIMEBase("application",'pgp-signature')
446 p.set_payload(sig.read())
447 payload.attach(p)
448
449 payload_in=payload
450 except errors.GPGMEError, ex:
451 self.error="GPG error: %s" % ex.getstring()
452 return False
453
454
455
456 if encrypt:
457 core.check_version(None)
458 plain = core.Data(payload_in.as_string())
459 cipher = core.Data()
460 c = core.Context()
461 c.set_armor(1)
462
463 recipients=[]
464 rec=to[:]
465 if cc:
466 rec.extend(cc)
467 if bcc:
468 rec.extend(bcc)
469 for addr in rec:
470 c.op_keylist_start(addr,0)
471 r = c.op_keylist_next()
472 if r is None:
473 self.error='No key for [%s]' % addr
474 return False
475 recipients.append(r)
476 try:
477
478 c.op_encrypt(recipients, 1, plain, cipher)
479 cipher.seek(0,0)
480
481 payload=MIMEMultipart.MIMEMultipart('encrypted',
482 boundary=None,
483 _subparts=None,
484 **dict(protocol="application/pgp-encrypted"))
485 p=MIMEBase.MIMEBase("application",'pgp-encrypted')
486 p.set_payload("Version: 1\r\n")
487 payload.attach(p)
488 p=MIMEBase.MIMEBase("application",'octet-stream')
489 p.set_payload(cipher.read())
490 payload.attach(p)
491 except errors.GPGMEError, ex:
492 self.error="GPG error: %s" % ex.getstring()
493 return False
494
495
496
497 elif cipher_type == 'x509':
498 if not sign and not encrypt:
499 self.error="No sign and no encrypt is set but cipher type to x509"
500 return False
501 x509_sign_keyfile=self.settings.x509_sign_keyfile
502 if self.settings.x509_sign_certfile:
503 x509_sign_certfile=self.settings.x509_sign_certfile
504 else:
505
506
507 x509_sign_certfile=self.settings.x509_sign_keyfile
508
509 x509_crypt_certfiles=self.settings.x509_crypt_certfiles
510
511
512
513 from M2Crypto import BIO, SMIME, X509
514 msg_bio = BIO.MemoryBuffer(payload_in.as_string())
515 s = SMIME.SMIME()
516
517
518 if sign:
519
520 try:
521 s.load_key(x509_sign_keyfile, x509_sign_certfile, callback=lambda x: sign_passphrase)
522 if encrypt:
523 p7 = s.sign(msg_bio)
524 else:
525 p7 = s.sign(msg_bio,flags=SMIME.PKCS7_DETACHED)
526 msg_bio = BIO.MemoryBuffer(payload_in.as_string())
527 except Exception,e:
528 self.error="Something went wrong on signing: <%s>" %str(e)
529 return False
530
531
532 if encrypt:
533 try:
534 sk = X509.X509_Stack()
535 if not isinstance(x509_crypt_certfiles, (list, tuple)):
536 x509_crypt_certfiles = [x509_crypt_certfiles]
537
538
539 for x in x509_crypt_certfiles:
540 sk.push(X509.load_cert(x))
541 s.set_x509_stack(sk)
542
543 s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
544 tmp_bio = BIO.MemoryBuffer()
545 if sign:
546 s.write(tmp_bio, p7)
547 else:
548 tmp_bio.write(payload_in.as_string())
549 p7 = s.encrypt(tmp_bio)
550 except Exception,e:
551 self.error="Something went wrong on encrypting: <%s>" %str(e)
552 return False
553
554
555 out = BIO.MemoryBuffer()
556 if encrypt:
557 s.write(out, p7)
558 else:
559 if sign:
560 s.write(out, p7, msg_bio, SMIME.PKCS7_DETACHED)
561 else:
562 out.write('\r\n')
563 out.write(payload_in.as_string())
564 out.close()
565 st=str(out.read())
566 payload=message_from_string(st)
567 else:
568
569 payload=payload_in
570
571 payload['From'] = encoded_or_raw(self.settings.sender.decode(encoding))
572 origTo = to[:]
573 if to:
574 payload['To'] = encoded_or_raw(', '.join(to).decode(encoding))
575 if reply_to:
576 payload['Reply-To'] = encoded_or_raw(reply_to.decode(encoding))
577 if cc:
578 payload['Cc'] = encoded_or_raw(', '.join(cc).decode(encoding))
579 to.extend(cc)
580 if bcc:
581 to.extend(bcc)
582 payload['Subject'] = encoded_or_raw(subject.decode(encoding))
583 payload['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S +0000",
584 time.gmtime())
585 for k,v in headers.iteritems():
586 payload[k] = encoded_or_raw(v.decode(encoding))
587 result = {}
588 try:
589 if self.settings.server == 'logging':
590 logger.warn('email not sent\n%s\nFrom: %s\nTo: %s\nSubject: %s\n\n%s\n%s\n' % \
591 ('-'*40,self.settings.sender,
592 ', '.join(to),subject,
593 text or html,'-'*40))
594 elif self.settings.server == 'gae':
595 xcc = dict()
596 if cc:
597 xcc['cc'] = cc
598 if bcc:
599 xcc['bcc'] = bcc
600 from google.appengine.api import mail
601 attachments = attachments and [(a.my_filename,a.my_payload) for a in attachments if not raw]
602 if attachments:
603 result = mail.send_mail(sender=self.settings.sender, to=origTo,
604 subject=subject, body=text, html=html,
605 attachments=attachments, **xcc)
606 elif html and (not raw):
607 result = mail.send_mail(sender=self.settings.sender, to=origTo,
608 subject=subject, body=text, html=html, **xcc)
609 else:
610 result = mail.send_mail(sender=self.settings.sender, to=origTo,
611 subject=subject, body=text, **xcc)
612 else:
613 smtp_args = self.settings.server.split(':')
614 if self.settings.ssl:
615 server = smtplib.SMTP_SSL(*smtp_args)
616 else:
617 server = smtplib.SMTP(*smtp_args)
618 if self.settings.tls and not self.settings.ssl:
619 server.ehlo()
620 server.starttls()
621 server.ehlo()
622 if not self.settings.login is None:
623 server.login(*self.settings.login.split(':',1))
624 result = server.sendmail(self.settings.sender, to, payload.as_string())
625 server.quit()
626 except Exception, e:
627 logger.warn('Mail.send failure:%s' % e)
628 self.result = result
629 self.error = e
630 return False
631 self.result = result
632 self.error = None
633 return True
634
635
637
638 API_SSL_SERVER = 'https://www.google.com/recaptcha/api'
639 API_SERVER = 'http://www.google.com/recaptcha/api'
640 VERIFY_SERVER = 'http://www.google.com/recaptcha/api/verify'
641
642 - def __init__(
643 self,
644 request,
645 public_key='',
646 private_key='',
647 use_ssl=False,
648 error=None,
649 error_message='invalid',
650 label = 'Verify:',
651 options = ''
652 ):
653 self.remote_addr = request.env.remote_addr
654 self.public_key = public_key
655 self.private_key = private_key
656 self.use_ssl = use_ssl
657 self.error = error
658 self.errors = Storage()
659 self.error_message = error_message
660 self.components = []
661 self.attributes = {}
662 self.label = label
663 self.options = options
664 self.comment = ''
665
667
668
669
670 recaptcha_challenge_field = \
671 self.request_vars.recaptcha_challenge_field
672 recaptcha_response_field = \
673 self.request_vars.recaptcha_response_field
674 private_key = self.private_key
675 remoteip = self.remote_addr
676 if not (recaptcha_response_field and recaptcha_challenge_field
677 and len(recaptcha_response_field)
678 and len(recaptcha_challenge_field)):
679 self.errors['captcha'] = self.error_message
680 return False
681 params = urllib.urlencode({
682 'privatekey': private_key,
683 'remoteip': remoteip,
684 'challenge': recaptcha_challenge_field,
685 'response': recaptcha_response_field,
686 })
687 request = urllib2.Request(
688 url=self.VERIFY_SERVER,
689 data=params,
690 headers={'Content-type': 'application/x-www-form-urlencoded',
691 'User-agent': 'reCAPTCHA Python'})
692 httpresp = urllib2.urlopen(request)
693 return_values = httpresp.read().splitlines()
694 httpresp.close()
695 return_code = return_values[0]
696 if return_code == 'true':
697 del self.request_vars.recaptcha_challenge_field
698 del self.request_vars.recaptcha_response_field
699 self.request_vars.captcha = ''
700 return True
701 self.errors['captcha'] = self.error_message
702 return False
703
705 public_key = self.public_key
706 use_ssl = self.use_ssl
707 error_param = ''
708 if self.error:
709 error_param = '&error=%s' % self.error
710 if use_ssl:
711 server = self.API_SSL_SERVER
712 else:
713 server = self.API_SERVER
714 captcha = DIV(
715 SCRIPT("var RecaptchaOptions = {%s};" % self.options),
716 SCRIPT(_type="text/javascript",
717 _src="%s/challenge?k=%s%s" % (server,public_key,error_param)),
718 TAG.noscript(IFRAME(_src="%s/noscript?k=%s%s" % (server,public_key,error_param),
719 _height="300",_width="500",_frameborder="0"), BR(),
720 INPUT(_type='hidden', _name='recaptcha_response_field',
721 _value='manual_challenge')), _id='recaptcha')
722 if not self.errors.captcha:
723 return XML(captcha).xml()
724 else:
725 captcha.append(DIV(self.errors['captcha'], _class='error'))
726 return XML(captcha).xml()
727
728
729 -def addrow(form, a, b, c, style, _id, position=-1):
730 if style == "divs":
731 form[0].insert(position, DIV(DIV(LABEL(a),_class='w2p_fl'),
732 DIV(b, _class='w2p_fw'),
733 DIV(c, _class='w2p_fc'),
734 _id = _id))
735 elif style == "table2cols":
736 form[0].insert(position, TR(TD(LABEL(a),_class='w2p_fl'),
737 TD(c,_class='w2p_fc')))
738 form[0].insert(position+1, TR(TD(b,_class='w2p_fw'),
739 _colspan=2, _id = _id))
740 elif style == "ul":
741 form[0].insert(position, LI(DIV(LABEL(a),_class='w2p_fl'),
742 DIV(b, _class='w2p_fw'),
743 DIV(c, _class='w2p_fc'),
744 _id = _id))
745 else:
746 form[0].insert(position, TR(TD(LABEL(a),_class='w2p_fl'),
747 TD(b,_class='w2p_fw'),
748 TD(c,_class='w2p_fc'),_id = _id))
749
750
752 """
753 Class for authentication, authorization, role based access control.
754
755 Includes:
756
757 - registration and profile
758 - login and logout
759 - username and password retrieval
760 - event logging
761 - role creation and assignment
762 - user defined group/role based permission
763
764 Authentication Example:
765
766 from contrib.utils import *
767 mail=Mail()
768 mail.settings.server='smtp.gmail.com:587'
769 mail.settings.sender='you@somewhere.com'
770 mail.settings.login='username:password'
771 auth=Auth(db)
772 auth.settings.mailer=mail
773 # auth.settings....=...
774 auth.define_tables()
775 def authentication():
776 return dict(form=auth())
777
778 exposes:
779
780 - http://.../{application}/{controller}/authentication/login
781 - http://.../{application}/{controller}/authentication/logout
782 - http://.../{application}/{controller}/authentication/register
783 - http://.../{application}/{controller}/authentication/verify_email
784 - http://.../{application}/{controller}/authentication/retrieve_username
785 - http://.../{application}/{controller}/authentication/retrieve_password
786 - http://.../{application}/{controller}/authentication/reset_password
787 - http://.../{application}/{controller}/authentication/profile
788 - http://.../{application}/{controller}/authentication/change_password
789
790 On registration a group with role=new_user.id is created
791 and user is given membership of this group.
792
793 You can create a group with:
794
795 group_id=auth.add_group('Manager', 'can access the manage action')
796 auth.add_permission(group_id, 'access to manage')
797
798 Here \"access to manage\" is just a user defined string.
799 You can give access to a user:
800
801 auth.add_membership(group_id, user_id)
802
803 If user id is omitted, the logged in user is assumed
804
805 Then you can decorate any action:
806
807 @auth.requires_permission('access to manage')
808 def manage():
809 return dict()
810
811 You can restrict a permission to a specific table:
812
813 auth.add_permission(group_id, 'edit', db.sometable)
814 @auth.requires_permission('edit', db.sometable)
815
816 Or to a specific record:
817
818 auth.add_permission(group_id, 'edit', db.sometable, 45)
819 @auth.requires_permission('edit', db.sometable, 45)
820
821 If authorization is not granted calls:
822
823 auth.settings.on_failed_authorization
824
825 Other options:
826
827 auth.settings.mailer=None
828 auth.settings.expiration=3600 # seconds
829
830 ...
831
832 ### these are messages that can be customized
833 ...
834 """
835
836 @staticmethod
847
848 - def url(self, f=None, args=None, vars=None):
849 if args is None: args=[]
850 if vars is None: vars={}
851 return URL(c=self.settings.controller, f=f, args=args, vars=vars)
852
855
856 - def __init__(self, environment=None, db=None, mailer=True,
857 hmac_key=None, controller='default', function='user', cas_provider=None):
858 """
859 auth=Auth(db)
860
861 - environment is there for legacy but unused (awful)
862 - db has to be the database where to create tables for authentication
863 - mailer=Mail(...) or None (no mailed) or True (make a mailer)
864 - hmac_key can be a hmac_key or hmac_key=Auth.get_or_create_key()
865 - controller (where is the user action?)
866 - cas_provider (delegate authentication to the URL, CAS2)
867 """
868
869 if not db and environment and isinstance(environment,DAL):
870 db = environment
871 self.db = db
872 self.environment = current
873 request = current.request
874 session = current.session
875 auth = session.auth
876 self.user_groups = auth and auth.user_groups or {}
877 if auth and auth.last_visit and auth.last_visit + \
878 datetime.timedelta(days=0, seconds=auth.expiration) > request.now:
879 self.user = auth.user
880
881 if (request.now - auth.last_visit).seconds > (auth.expiration/10):
882 auth.last_visit = request.now
883 else:
884 self.user = None
885 session.auth = None
886 settings = self.settings = Settings()
887
888
889
890 self.next = current.request.vars._next
891 if isinstance(self.next,(list,tuple)):
892 self.next = self.next[0]
893
894
895
896 settings.hideerror = False
897 settings.password_min_length = 4
898 settings.cas_domains = [request.env.http_host]
899 settings.cas_provider = cas_provider
900 settings.cas_actions = {'login':'login',
901 'validate':'validate',
902 'servicevalidate':'serviceValidate',
903 'proxyvalidate':'proxyValidate',
904 'logout':'logout'}
905 settings.cas_maps = None
906 settings.extra_fields = {}
907 settings.actions_disabled = []
908 settings.reset_password_requires_verification = False
909 settings.registration_requires_verification = False
910 settings.registration_requires_approval = False
911 settings.login_after_registration = False
912 settings.alternate_requires_registration = False
913 settings.create_user_groups = True
914
915 settings.controller = controller
916 settings.function = function
917 settings.login_url = self.url(function, args='login')
918 settings.logged_url = self.url(function, args='profile')
919 settings.download_url = self.url('download')
920 settings.mailer = (mailer==True) and Mail() or mailer
921 settings.login_captcha = None
922 settings.register_captcha = None
923 settings.retrieve_username_captcha = None
924 settings.retrieve_password_captcha = None
925 settings.captcha = None
926 settings.expiration = 3600
927 settings.long_expiration = 3600*30*24
928 settings.remember_me_form = True
929 settings.allow_basic_login = False
930 settings.allow_basic_login_only = False
931 settings.on_failed_authorization = \
932 self.url(function, args='not_authorized')
933
934 settings.on_failed_authentication = lambda x: redirect(x)
935
936 settings.formstyle = 'table3cols'
937 settings.label_separator = ': '
938
939
940
941 settings.password_field = 'password'
942 settings.table_user_name = 'auth_user'
943 settings.table_group_name = 'auth_group'
944 settings.table_membership_name = 'auth_membership'
945 settings.table_permission_name = 'auth_permission'
946 settings.table_event_name = 'auth_event'
947 settings.table_cas_name = 'auth_cas'
948
949
950
951 settings.table_user = None
952 settings.table_group = None
953 settings.table_membership = None
954 settings.table_permission = None
955 settings.table_event = None
956 settings.table_cas = None
957
958
959
960 settings.showid = False
961
962
963
964 settings.login_next = self.url('index')
965 settings.login_onvalidation = []
966 settings.login_onaccept = []
967 settings.login_methods = [self]
968 settings.login_form = self
969 settings.login_email_validate = True
970 settings.login_userfield = None
971
972 settings.logout_next = self.url('index')
973 settings.logout_onlogout = None
974
975 settings.register_next = self.url('index')
976 settings.register_onvalidation = []
977 settings.register_onaccept = []
978 settings.register_fields = None
979 settings.register_verify_password = True
980
981 settings.verify_email_next = self.url(function, args='login')
982 settings.verify_email_onaccept = []
983
984 settings.profile_next = self.url('index')
985 settings.profile_onvalidation = []
986 settings.profile_onaccept = []
987 settings.profile_fields = None
988 settings.retrieve_username_next = self.url('index')
989 settings.retrieve_password_next = self.url('index')
990 settings.request_reset_password_next = self.url(function, args='login')
991 settings.reset_password_next = self.url(function, args='login')
992
993 settings.change_password_next = self.url('index')
994 settings.change_password_onvalidation = []
995 settings.change_password_onaccept = []
996
997 settings.retrieve_password_onvalidation = []
998 settings.reset_password_onvalidation = []
999
1000 settings.hmac_key = hmac_key
1001 settings.lock_keys = True
1002
1003
1004 messages = self.messages = Messages(current.T)
1005 messages.login_button = 'Login'
1006 messages.register_button = 'Register'
1007 messages.password_reset_button = 'Request reset password'
1008 messages.password_change_button = 'Change password'
1009 messages.profile_save_button = 'Save profile'
1010 messages.submit_button = 'Submit'
1011 messages.verify_password = 'Verify Password'
1012 messages.delete_label = 'Check to delete'
1013 messages.function_disabled = 'Function disabled'
1014 messages.access_denied = 'Insufficient privileges'
1015 messages.registration_verifying = 'Registration needs verification'
1016 messages.registration_pending = 'Registration is pending approval'
1017 messages.login_disabled = 'Login disabled by administrator'
1018 messages.logged_in = 'Logged in'
1019 messages.email_sent = 'Email sent'
1020 messages.unable_to_send_email = 'Unable to send email'
1021 messages.email_verified = 'Email verified'
1022 messages.logged_out = 'Logged out'
1023 messages.registration_successful = 'Registration successful'
1024 messages.invalid_email = 'Invalid email'
1025 messages.unable_send_email = 'Unable to send email'
1026 messages.invalid_login = 'Invalid login'
1027 messages.invalid_user = 'Invalid user'
1028 messages.invalid_password = 'Invalid password'
1029 messages.is_empty = "Cannot be empty"
1030 messages.mismatched_password = "Password fields don't match"
1031 messages.verify_email = \
1032 'Click on the link http://' + current.request.env.http_host + \
1033 URL('default','user',args=['verify_email']) + \
1034 '/%(key)s to verify your email'
1035 messages.verify_email_subject = 'Email verification'
1036 messages.username_sent = 'Your username was emailed to you'
1037 messages.new_password_sent = 'A new password was emailed to you'
1038 messages.password_changed = 'Password changed'
1039 messages.retrieve_username = 'Your username is: %(username)s'
1040 messages.retrieve_username_subject = 'Username retrieve'
1041 messages.retrieve_password = 'Your password is: %(password)s'
1042 messages.retrieve_password_subject = 'Password retrieve'
1043 messages.reset_password = \
1044 'Click on the link http://' + current.request.env.http_host + \
1045 URL('default','user',args=['reset_password']) + \
1046 '/%(key)s to reset your password'
1047 messages.reset_password_subject = 'Password reset'
1048 messages.invalid_reset_password = 'Invalid reset password'
1049 messages.profile_updated = 'Profile updated'
1050 messages.new_password = 'New password'
1051 messages.old_password = 'Old password'
1052 messages.group_description = \
1053 'Group uniquely assigned to user %(id)s'
1054
1055 messages.register_log = 'User %(id)s Registered'
1056 messages.login_log = 'User %(id)s Logged-in'
1057 messages.login_failed_log = None
1058 messages.logout_log = 'User %(id)s Logged-out'
1059 messages.profile_log = 'User %(id)s Profile updated'
1060 messages.verify_email_log = 'User %(id)s Verification email sent'
1061 messages.retrieve_username_log = 'User %(id)s Username retrieved'
1062 messages.retrieve_password_log = 'User %(id)s Password retrieved'
1063 messages.reset_password_log = 'User %(id)s Password reset'
1064 messages.change_password_log = 'User %(id)s Password changed'
1065 messages.add_group_log = 'Group %(group_id)s created'
1066 messages.del_group_log = 'Group %(group_id)s deleted'
1067 messages.add_membership_log = None
1068 messages.del_membership_log = None
1069 messages.has_membership_log = None
1070 messages.add_permission_log = None
1071 messages.del_permission_log = None
1072 messages.has_permission_log = None
1073 messages.impersonate_log = 'User %(id)s is impersonating %(other_id)s'
1074
1075 messages.label_first_name = 'First name'
1076 messages.label_last_name = 'Last name'
1077 messages.label_username = 'Username'
1078 messages.label_email = 'E-mail'
1079 messages.label_password = 'Password'
1080 messages.label_registration_key = 'Registration key'
1081 messages.label_reset_password_key = 'Reset Password key'
1082 messages.label_registration_id = 'Registration identifier'
1083 messages.label_role = 'Role'
1084 messages.label_description = 'Description'
1085 messages.label_user_id = 'User ID'
1086 messages.label_group_id = 'Group ID'
1087 messages.label_name = 'Name'
1088 messages.label_table_name = 'Object or table name'
1089 messages.label_record_id = 'Record ID'
1090 messages.label_time_stamp = 'Timestamp'
1091 messages.label_client_ip = 'Client IP'
1092 messages.label_origin = 'Origin'
1093 messages.label_remember_me = "Remember me (for 30 days)"
1094 messages['T'] = current.T
1095 messages.verify_password_comment = 'please input your password again'
1096 messages.lock_keys = True
1097
1098
1099 response = current.response
1100 if auth and auth.remember:
1101 response.cookies[response.session_id_name]["expires"] = \
1102 auth.expiration
1103
1104 def lazy_user (auth = self): return auth.user_id
1105 reference_user = 'reference %s' % settings.table_user_name
1106 def represent(id,record=None,s=settings):
1107 try:
1108 user = s.table_user(id)
1109 return '%(first_name)s %(last_name)s' % user
1110 except: return id
1111 self.signature = db.Table(self.db,'auth_signature',
1112 Field('is_active','boolean',default=True),
1113 Field('created_on','datetime',
1114 default=request.now,
1115 writable=False,readable=False),
1116 Field('created_by',
1117 reference_user,
1118 default=lazy_user,represent=represent,
1119 writable=False,readable=False,
1120 ),
1121 Field('modified_on','datetime',
1122 update=request.now,default=request.now,
1123 writable=False,readable=False),
1124 Field('modified_by',
1125 reference_user,represent=represent,
1126 default=lazy_user,update=lazy_user,
1127 writable=False,readable=False))
1128
1129
1130
1132 "accessor for auth.user_id"
1133 return self.user and self.user.id or None
1134 user_id = property(_get_user_id, doc="user.id or None")
1135
1136 - def _HTTP(self, *a, **b):
1137 """
1138 only used in lambda: self._HTTP(404)
1139 """
1140
1141 raise HTTP(*a, **b)
1142
1144 """
1145 usage:
1146
1147 def authentication(): return dict(form=auth())
1148 """
1149
1150 request = current.request
1151 args = request.args
1152 if not args:
1153 redirect(self.url(args='login',vars=request.vars))
1154 elif args[0] in self.settings.actions_disabled:
1155 raise HTTP(404)
1156 if args[0] in ('login','logout','register','verify_email',
1157 'retrieve_username','retrieve_password',
1158 'reset_password','request_reset_password',
1159 'change_password','profile','groups',
1160 'impersonate','not_authorized'):
1161 return getattr(self,args[0])()
1162 elif args[0]=='cas' and not self.settings.cas_provider:
1163 if args(1) == self.settings.cas_actions['login']:
1164 return self.cas_login(version=2)
1165 elif args(1) == self.settings.cas_actions['validate']:
1166 return self.cas_validate(version=1)
1167 elif args(1) == self.settings.cas_actions['servicevalidate']:
1168 return self.cas_validate(version=2, proxy=False)
1169 elif args(1) == self.settings.cas_actions['proxyvalidate']:
1170 return self.cas_validate(version=2, proxy=True)
1171 elif args(1) == self.settings.cas_actions['logout']:
1172 return self.logout(next=request.vars.service or DEFAULT)
1173 else:
1174 raise HTTP(404)
1175
1176 - def navbar(self, prefix='Welcome', action=None, separators=(' [ ',' | ',' ] ')):
1177 request = current.request
1178 T = current.T
1179 if isinstance(prefix,str):
1180 prefix = T(prefix)
1181 if not action:
1182 action=self.url(self.settings.function)
1183 if prefix:
1184 prefix = prefix.strip()+' '
1185 s1,s2,s3 = separators
1186 if URL() == action:
1187 next = ''
1188 else:
1189 next = '?_next='+urllib.quote(URL(args=request.args,vars=request.vars))
1190
1191 li_next = '?_next='+urllib.quote(self.settings.login_next)
1192 lo_next = '?_next='+urllib.quote(self.settings.logout_next)
1193
1194 if self.user_id:
1195 logout=A(T('Logout'),_href=action+'/logout'+lo_next)
1196 profile=A(T('Profile'),_href=action+'/profile'+next)
1197 password=A(T('Password'),_href=action+'/change_password'+next)
1198 bar = SPAN(prefix,self.user.first_name,s1, logout,s3,_class='auth_navbar')
1199 if not 'profile' in self.settings.actions_disabled:
1200 bar.insert(4, s2)
1201 bar.insert(5, profile)
1202 if not 'change_password' in self.settings.actions_disabled:
1203 bar.insert(-1, s2)
1204 bar.insert(-1, password)
1205 else:
1206 login=A(T('Login'),_href=action+'/login'+li_next)
1207 register=A(T('Register'),_href=action+'/register'+next)
1208 retrieve_username=A(T('forgot username?'),
1209 _href=action+'/retrieve_username'+next)
1210 lost_password=A(T('Lost password?'),
1211 _href=action+'/request_reset_password'+next)
1212 bar = SPAN(s1, login, s3, _class='auth_navbar')
1213
1214 if not 'register' in self.settings.actions_disabled:
1215 bar.insert(2, s2)
1216 bar.insert(3, register)
1217 if 'username' in self.settings.table_user.fields() and \
1218 not 'retrieve_username' in self.settings.actions_disabled:
1219 bar.insert(-1, s2)
1220 bar.insert(-1, retrieve_username)
1221 if not 'request_reset_password' in self.settings.actions_disabled:
1222 bar.insert(-1, s2)
1223 bar.insert(-1, lost_password)
1224 return bar
1225
1227
1228 if type(migrate).__name__ == 'str':
1229 return (migrate + tablename + '.table')
1230 elif migrate == False:
1231 return False
1232 else:
1233 return True
1234
1235 - def define_tables(self, username=False, migrate=True, fake_migrate=False):
1236 """
1237 to be called unless tables are defined manually
1238
1239 usages:
1240
1241 # defines all needed tables and table files
1242 # 'myprefix_auth_user.table', ...
1243 auth.define_tables(migrate='myprefix_')
1244
1245 # defines all needed tables without migration/table files
1246 auth.define_tables(migrate=False)
1247
1248 """
1249
1250 db = self.db
1251 settings = self.settings
1252 if not settings.table_user_name in db.tables:
1253 passfield = settings.password_field
1254 if username or settings.cas_provider:
1255 table = db.define_table(
1256 settings.table_user_name,
1257 Field('first_name', length=128, default='',
1258 label=self.messages.label_first_name),
1259 Field('last_name', length=128, default='',
1260 label=self.messages.label_last_name),
1261 Field('email', length=512, default='',
1262 label=self.messages.label_email),
1263 Field('username', length=128, default='',
1264 label=self.messages.label_username),
1265 Field(passfield, 'password', length=512,
1266 readable=False, label=self.messages.label_password),
1267 Field('registration_key', length=512,
1268 writable=False, readable=False, default='',
1269 label=self.messages.label_registration_key),
1270 Field('reset_password_key', length=512,
1271 writable=False, readable=False, default='',
1272 label=self.messages.label_reset_password_key),
1273 Field('registration_id', length=512,
1274 writable=False, readable=False, default='',
1275 label=self.messages.label_registration_id),
1276 *settings.extra_fields.get(settings.table_user_name,[]),
1277 **dict(
1278 migrate=self.__get_migrate(settings.table_user_name,
1279 migrate),
1280 fake_migrate=fake_migrate,
1281 format='%(username)s'))
1282 table.username.requires = (IS_MATCH('[\w\.\-]+'),
1283 IS_NOT_IN_DB(db, table.username))
1284 else:
1285 table = db.define_table(
1286 settings.table_user_name,
1287 Field('first_name', length=128, default='',
1288 label=self.messages.label_first_name),
1289 Field('last_name', length=128, default='',
1290 label=self.messages.label_last_name),
1291 Field('email', length=512, default='',
1292 label=self.messages.label_email),
1293 Field(passfield, 'password', length=512,
1294 readable=False, label=self.messages.label_password),
1295 Field('registration_key', length=512,
1296 writable=False, readable=False, default='',
1297 label=self.messages.label_registration_key),
1298 Field('reset_password_key', length=512,
1299 writable=False, readable=False, default='',
1300 label=self.messages.label_reset_password_key),
1301 Field('registration_id', length=512,
1302 writable=False, readable=False, default='',
1303 label=self.messages.label_registration_id),
1304 *settings.extra_fields.get(settings.table_user_name,[]),
1305 **dict(
1306 migrate=self.__get_migrate(settings.table_user_name,
1307 migrate),
1308 fake_migrate=fake_migrate,
1309 format='%(first_name)s %(last_name)s (%(id)s)'))
1310 table.first_name.requires = \
1311 IS_NOT_EMPTY(error_message=self.messages.is_empty)
1312 table.last_name.requires = \
1313 IS_NOT_EMPTY(error_message=self.messages.is_empty)
1314 table[passfield].requires = [
1315 CRYPT(key=settings.hmac_key,
1316 min_length=self.settings.password_min_length)]
1317 table.email.requires = \
1318 [IS_EMAIL(error_message=self.messages.invalid_email),
1319 IS_NOT_IN_DB(db, table.email)]
1320 table.registration_key.default = ''
1321 settings.table_user = db[settings.table_user_name]
1322 if not settings.table_group_name in db.tables:
1323 table = db.define_table(
1324 settings.table_group_name,
1325 Field('role', length=512, default='',
1326 label=self.messages.label_role),
1327 Field('description', 'text',
1328 label=self.messages.label_description),
1329 *settings.extra_fields.get(settings.table_group_name,[]),
1330 **dict(
1331 migrate=self.__get_migrate(
1332 settings.table_group_name, migrate),
1333 fake_migrate=fake_migrate,
1334 format = '%(role)s (%(id)s)'))
1335 table.role.requires = IS_NOT_IN_DB(db, '%s.role'
1336 % settings.table_group_name)
1337 settings.table_group = db[settings.table_group_name]
1338 if not settings.table_membership_name in db.tables:
1339 table = db.define_table(
1340 settings.table_membership_name,
1341 Field('user_id', settings.table_user,
1342 label=self.messages.label_user_id),
1343 Field('group_id', settings.table_group,
1344 label=self.messages.label_group_id),
1345 *settings.extra_fields.get(settings.table_membership_name,[]),
1346 **dict(
1347 migrate=self.__get_migrate(
1348 settings.table_membership_name, migrate),
1349 fake_migrate=fake_migrate))
1350 table.user_id.requires = IS_IN_DB(db, '%s.id' %
1351 settings.table_user_name,
1352 '%(first_name)s %(last_name)s (%(id)s)')
1353 table.group_id.requires = IS_IN_DB(db, '%s.id' %
1354 settings.table_group_name,
1355 '%(role)s (%(id)s)')
1356 settings.table_membership = db[settings.table_membership_name]
1357 if not settings.table_permission_name in db.tables:
1358 table = db.define_table(
1359 settings.table_permission_name,
1360 Field('group_id', settings.table_group,
1361 label=self.messages.label_group_id),
1362 Field('name', default='default', length=512,
1363 label=self.messages.label_name),
1364 Field('table_name', length=512,
1365 label=self.messages.label_table_name),
1366 Field('record_id', 'integer',default=0,
1367 label=self.messages.label_record_id),
1368 *settings.extra_fields.get(settings.table_permission_name,[]),
1369 **dict(
1370 migrate=self.__get_migrate(
1371 settings.table_permission_name, migrate),
1372 fake_migrate=fake_migrate))
1373 table.group_id.requires = IS_IN_DB(db, '%s.id' %
1374 settings.table_group_name,
1375 '%(role)s (%(id)s)')
1376 table.name.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1377
1378 table.record_id.requires = IS_INT_IN_RANGE(0, 10 ** 9)
1379 settings.table_permission = db[settings.table_permission_name]
1380 if not settings.table_event_name in db.tables:
1381 table = db.define_table(
1382 settings.table_event_name,
1383 Field('time_stamp', 'datetime',
1384 default=current.request.now,
1385 label=self.messages.label_time_stamp),
1386 Field('client_ip',
1387 default=current.request.client,
1388 label=self.messages.label_client_ip),
1389 Field('user_id', settings.table_user, default=None,
1390 label=self.messages.label_user_id),
1391 Field('origin', default='auth', length=512,
1392 label=self.messages.label_origin),
1393 Field('description', 'text', default='',
1394 label=self.messages.label_description),
1395 *settings.extra_fields.get(settings.table_event_name,[]),
1396 **dict(
1397 migrate=self.__get_migrate(
1398 settings.table_event_name, migrate),
1399 fake_migrate=fake_migrate))
1400 table.user_id.requires = IS_IN_DB(db, '%s.id' %
1401 settings.table_user_name,
1402 '%(first_name)s %(last_name)s (%(id)s)')
1403 table.origin.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1404 table.description.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1405 settings.table_event = db[settings.table_event_name]
1406 now = current.request.now
1407 if settings.cas_domains:
1408 if not settings.table_cas_name in db.tables:
1409 table = db.define_table(
1410 settings.table_cas_name,
1411 Field('user_id', settings.table_user, default=None,
1412 label=self.messages.label_user_id),
1413 Field('created_on','datetime',default=now),
1414 Field('service',requires=IS_URL()),
1415 Field('ticket'),
1416 Field('renew', 'boolean', default=False),
1417 *settings.extra_fields.get(settings.table_cas_name,[]),
1418 **dict(
1419 migrate=self.__get_migrate(
1420 settings.table_event_name, migrate),
1421 fake_migrate=fake_migrate))
1422 table.user_id.requires = IS_IN_DB(db, '%s.id' % \
1423 settings.table_user_name,
1424 '%(first_name)s %(last_name)s (%(id)s)')
1425 settings.table_cas = db[settings.table_cas_name]
1426 if settings.cas_provider:
1427 settings.actions_disabled = \
1428 ['profile','register','change_password','request_reset_password']
1429 from gluon.contrib.login_methods.cas_auth import CasAuth
1430 maps = self.settings.cas_maps
1431 if not maps:
1432 maps = dict((name,lambda v,n=name:v.get(n,None)) for name in \
1433 settings.table_user.fields if name!='id' \
1434 and settings.table_user[name].readable)
1435 maps['registration_id'] = \
1436 lambda v,p=settings.cas_provider:'%s/%s' % (p,v['user'])
1437 actions = [self.settings.cas_actions['login'],
1438 self.settings.cas_actions['servicevalidate'],
1439 self.settings.cas_actions['logout']]
1440 settings.login_form = CasAuth(
1441 casversion = 2,
1442 urlbase = settings.cas_provider,
1443 actions=actions,
1444 maps=maps)
1445
1446
1447 - def log_event(self, description, vars=None, origin='auth'):
1448 """
1449 usage:
1450
1451 auth.log_event(description='this happened', origin='auth')
1452 """
1453 if not description:
1454 return
1455 elif self.is_logged_in():
1456 user_id = self.user.id
1457 else:
1458 user_id = None
1459 vars = vars or {}
1460 self.settings.table_event.insert(description=description % vars,
1461 origin=origin, user_id=user_id)
1462
1464 """
1465 Used for alternate login methods:
1466 If the user exists already then password is updated.
1467 If the user doesn't yet exist, then they are created.
1468 """
1469 table_user = self.settings.table_user
1470 user = None
1471 checks = []
1472
1473 for fieldname in ['registration_id','username','email']:
1474 if fieldname in table_user.fields() and keys.get(fieldname,None):
1475 checks.append(fieldname)
1476 user = user or table_user(**{fieldname:keys[fieldname]})
1477
1478 if 'registration_id' in checks and user and user.registration_id and user.registration_id!=keys.get('registration_id',None):
1479 user = None
1480 keys['registration_key']=''
1481 if user:
1482 user.update_record(**table_user._filter_fields(keys))
1483 elif checks:
1484 if not 'first_name' in keys and 'first_name' in table_user.fields:
1485 keys['first_name'] = keys.get('username',keys.get('email','anonymous')).split('@')[0]
1486 user_id = table_user.insert(**table_user._filter_fields(keys))
1487 user = self.user = table_user[user_id]
1488 if self.settings.create_user_groups:
1489 group_id = self.add_group("user_%s" % user_id)
1490 self.add_membership(group_id, user_id)
1491 return user
1492
1494 if not self.settings.allow_basic_login:
1495 return (False,False,False)
1496 basic = current.request.env.http_authorization
1497 if not basic or not basic[:6].lower() == 'basic ':
1498 return (True, False, False)
1499 (username, password) = base64.b64decode(basic[6:]).split(':')
1500 return (True, True, self.login_bare(username, password))
1501
1503 """
1504 logins user
1505 """
1506
1507 request = current.request
1508 session = current.session
1509 table_user = self.settings.table_user
1510 if self.settings.login_userfield:
1511 userfield = self.settings.login_userfield
1512 elif 'username' in table_user.fields:
1513 userfield = 'username'
1514 else:
1515 userfield = 'email'
1516 passfield = self.settings.password_field
1517 user = self.db(table_user[userfield] == username).select().first()
1518 if user:
1519 password = table_user[passfield].validate(password)[0]
1520 if not user.registration_key and user[passfield] == password:
1521 user = Storage(table_user._filter_fields(user, id=True))
1522 session.auth = Storage(user=user, last_visit=request.now,
1523 expiration=self.settings.expiration,
1524 hmac_key = web2py_uuid())
1525 self.user = user
1526 self.update_groups()
1527 return user
1528 else:
1529
1530 for login_method in self.settings.login_methods:
1531 if login_method != self and login_method(username, password):
1532 self.user = username
1533 return username
1534 return False
1535
1571 if self.is_logged_in() and not request.vars.has_key('renew'):
1572 return allow_access()
1573 elif not self.is_logged_in() and request.vars.has_key('gateway'):
1574 redirect(service)
1575 def cas_onaccept(form, onaccept=onaccept):
1576 if not onaccept is DEFAULT: onaccept(form)
1577 return allow_access(interactivelogin=True)
1578 return self.login(next,onvalidation,cas_onaccept,log)
1579
1580
1582 request = current.request
1583 db, table = self.db, self.settings.table_cas
1584 current.response.headers['Content-Type']='text'
1585 ticket = request.vars.ticket
1586 renew = True if request.vars.has_key('renew') else False
1587 row = table(ticket=ticket)
1588 success = False
1589 if row:
1590 if self.settings.login_userfield:
1591 userfield = self.settings.login_userfield
1592 elif 'username' in table.fields:
1593 userfield = 'username'
1594 else:
1595 userfield = 'email'
1596
1597 if ticket[0:3] == 'ST-' and \
1598 not ((row.renew and renew) ^ renew):
1599 user = self.settings.table_user(row.user_id)
1600 row.delete_record()
1601 success = True
1602 def build_response(body):
1603 return '<?xml version="1.0" encoding="UTF-8"?>\n'+\
1604 TAG['cas:serviceResponse'](
1605 body,**{'_xmlns:cas':'http://www.yale.edu/tp/cas'}).xml()
1606 if success:
1607 if version == 1:
1608 message = 'yes\n%s' % user[userfield]
1609 else:
1610 username = user.get('username',user[userfield])
1611 message = build_response(
1612 TAG['cas:authenticationSuccess'](
1613 TAG['cas:user'](username),
1614 *[TAG['cas:'+field.name](user[field.name]) \
1615 for field in self.settings.table_user \
1616 if field.readable]))
1617 else:
1618 if version == 1:
1619 message = 'no\n'
1620 elif row:
1621 message = build_response(TAG['cas:authenticationFailure']())
1622 else:
1623 message = build_response(
1624 TAG['cas:authenticationFailure'](
1625 'Ticket %s not recognized' % ticket,
1626 _code='INVALID TICKET'))
1627 raise HTTP(200,message)
1628
1636 """
1637 returns a login form
1638
1639 method: Auth.login([next=DEFAULT [, onvalidation=DEFAULT
1640 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1641
1642 """
1643
1644 table_user = self.settings.table_user
1645 if self.settings.login_userfield:
1646 username = self.settings.login_userfield
1647 elif 'username' in table_user.fields:
1648 username = 'username'
1649 else:
1650 username = 'email'
1651 if 'username' in table_user.fields or \
1652 not self.settings.login_email_validate:
1653 tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1654 else:
1655 tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email)
1656 old_requires = table_user[username].requires
1657 table_user[username].requires = tmpvalidator
1658
1659 request = current.request
1660 response = current.response
1661 session = current.session
1662
1663 passfield = self.settings.password_field
1664 try: table_user[passfield].requires[-1].min_length = 0
1665 except: pass
1666
1667
1668 if self.next:
1669 session._auth_next = self.next
1670 elif session._auth_next:
1671 self.next = session._auth_next
1672
1673
1674 if next is DEFAULT:
1675 next = self.next or self.settings.login_next
1676 if onvalidation is DEFAULT:
1677 onvalidation = self.settings.login_onvalidation
1678 if onaccept is DEFAULT:
1679 onaccept = self.settings.login_onaccept
1680 if log is DEFAULT:
1681 log = self.messages.login_log
1682
1683 user = None
1684
1685
1686 if self.settings.login_form == self:
1687 form = SQLFORM(
1688 table_user,
1689 fields=[username, passfield],
1690 hidden = dict(_next=next),
1691 showid=self.settings.showid,
1692 submit_button=self.messages.login_button,
1693 delete_label=self.messages.delete_label,
1694 formstyle=self.settings.formstyle,
1695 separator=self.settings.label_separator
1696 )
1697
1698 if self.settings.remember_me_form:
1699
1700 addrow(form,XML(" "),
1701 DIV(XML(" "),
1702 INPUT(_type='checkbox',
1703 _class='checkbox',
1704 _id="auth_user_remember",
1705 _name="remember",
1706 ),
1707 XML(" "),
1708 LABEL(
1709 self.messages.label_remember_me,
1710 _for="auth_user_remember",
1711 )),"",
1712 self.settings.formstyle,
1713 'auth_user_remember__row')
1714
1715 captcha = self.settings.login_captcha or \
1716 (self.settings.login_captcha!=False and self.settings.captcha)
1717 if captcha:
1718 addrow(form, captcha.label, captcha, captcha.comment,
1719 self.settings.formstyle,'captcha__row')
1720 accepted_form = False
1721
1722 if form.accepts(request, session,
1723 formname='login', dbio=False,
1724 onvalidation=onvalidation,
1725 hideerror=self.settings.hideerror):
1726
1727 accepted_form = True
1728
1729 user = self.db(table_user[username] == form.vars[username]).select().first()
1730 if user:
1731
1732 temp_user = user
1733 if temp_user.registration_key == 'pending':
1734 response.flash = self.messages.registration_pending
1735 return form
1736 elif temp_user.registration_key in ('disabled','blocked'):
1737 response.flash = self.messages.login_disabled
1738 return form
1739 elif not temp_user.registration_key is None and \
1740 temp_user.registration_key.strip():
1741 response.flash = \
1742 self.messages.registration_verifying
1743 return form
1744
1745
1746 user = None
1747 for login_method in self.settings.login_methods:
1748 if login_method != self and \
1749 login_method(request.vars[username],
1750 request.vars[passfield]):
1751 if not self in self.settings.login_methods:
1752
1753 form.vars[passfield] = None
1754 user = self.get_or_create_user(form.vars)
1755 break
1756 if not user:
1757
1758 if self.settings.login_methods[0] == self:
1759
1760 if temp_user[passfield] == form.vars.get(passfield, ''):
1761
1762 user = temp_user
1763 else:
1764
1765 if not self.settings.alternate_requires_registration:
1766
1767 for login_method in self.settings.login_methods:
1768 if login_method != self and \
1769 login_method(request.vars[username],
1770 request.vars[passfield]):
1771 if not self in self.settings.login_methods:
1772
1773 form.vars[passfield] = None
1774 user = self.get_or_create_user(form.vars)
1775 break
1776 if not user:
1777 self.log_event(self.settings.login_failed_log,
1778 request.post_vars)
1779
1780 session.flash = self.messages.invalid_login
1781 redirect(self.url(args=request.args,vars=request.get_vars))
1782
1783 else:
1784
1785 cas = self.settings.login_form
1786 cas_user = cas.get_user()
1787
1788 if cas_user:
1789 cas_user[passfield] = None
1790 user = self.get_or_create_user(table_user._filter_fields(cas_user))
1791 elif hasattr(cas,'login_form'):
1792 return cas.login_form()
1793 else:
1794
1795 next = self.url(self.settings.function, args='login')
1796 redirect(cas.login_url(next))
1797
1798
1799 if user:
1800 user = Storage(table_user._filter_fields(user, id=True))
1801
1802
1803
1804 session.auth = Storage(
1805 user = user,
1806 last_visit = request.now,
1807 expiration = request.vars.get("remember",False) and \
1808 self.settings.long_expiration or self.settings.expiration,
1809 remember = request.vars.has_key("remember"),
1810 hmac_key = web2py_uuid()
1811 )
1812
1813 self.user = user
1814 self.log_event(log, user)
1815 session.flash = self.messages.logged_in
1816
1817 self.update_groups()
1818
1819
1820 if self.settings.login_form == self:
1821 if accepted_form:
1822 callback(onaccept,form)
1823 if next == session._auth_next:
1824 session._auth_next = None
1825 next = replace_id(next, form)
1826 redirect(next)
1827 table_user[username].requires = old_requires
1828 return form
1829 elif user:
1830 callback(onaccept,None)
1831 if next == session._auth_next:
1832 del session._auth_next
1833 redirect(next)
1834
1836 """
1837 logout and redirects to login
1838
1839 method: Auth.logout ([next=DEFAULT[, onlogout=DEFAULT[,
1840 log=DEFAULT]]])
1841
1842 """
1843
1844 if next is DEFAULT:
1845 next = self.settings.logout_next
1846 if onlogout is DEFAULT:
1847 onlogout = self.settings.logout_onlogout
1848 if onlogout:
1849 onlogout(self.user)
1850 if log is DEFAULT:
1851 log = self.messages.logout_log
1852 if self.user:
1853 self.log_event(log, self.user)
1854 if self.settings.login_form != self:
1855 cas = self.settings.login_form
1856 cas_user = cas.get_user()
1857 if cas_user:
1858 next = cas.logout_url(next)
1859
1860 current.session.auth = None
1861 current.session.flash = self.messages.logged_out
1862 redirect(next)
1863
1871 """
1872 returns a registration form
1873
1874 method: Auth.register([next=DEFAULT [, onvalidation=DEFAULT
1875 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1876
1877 """
1878
1879 table_user = self.settings.table_user
1880 request = current.request
1881 response = current.response
1882 session = current.session
1883 if self.is_logged_in():
1884 redirect(self.settings.logged_url)
1885 if next is DEFAULT:
1886 next = self.next or self.settings.register_next
1887 if onvalidation is DEFAULT:
1888 onvalidation = self.settings.register_onvalidation
1889 if onaccept is DEFAULT:
1890 onaccept = self.settings.register_onaccept
1891 if log is DEFAULT:
1892 log = self.messages.register_log
1893
1894 passfield = self.settings.password_field
1895 formstyle = self.settings.formstyle
1896 form = SQLFORM(table_user,
1897 fields = self.settings.register_fields,
1898 hidden = dict(_next=next),
1899 showid=self.settings.showid,
1900 submit_button=self.messages.register_button,
1901 delete_label=self.messages.delete_label,
1902 formstyle=formstyle,
1903 separator=self.settings.label_separator
1904 )
1905 if self.settings.register_verify_password:
1906 for i, row in enumerate(form[0].components):
1907 item = row.element('input',_name=passfield)
1908 if item:
1909 form.custom.widget.password_two = \
1910 INPUT(_name="password_two", _type="password",
1911 requires=IS_EXPR(
1912 'value==%s' % \
1913 repr(request.vars.get(passfield, None)),
1914 error_message=self.messages.mismatched_password))
1915
1916 addrow(form, self.messages.verify_password + self.settings.label_separator,
1917 form.custom.widget.password_two,
1918 self.messages.verify_password_comment,
1919 formstyle,
1920 '%s_%s__row' % (table_user, 'password_two'),
1921 position=i+1)
1922 break
1923 captcha = self.settings.register_captcha or self.settings.captcha
1924 if captcha:
1925 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row')
1926
1927 table_user.registration_key.default = key = web2py_uuid()
1928 if form.accepts(request, session, formname='register',
1929 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1930 description = self.messages.group_description % form.vars
1931 if self.settings.create_user_groups:
1932 group_id = self.add_group("user_%s" % form.vars.id, description)
1933 self.add_membership(group_id, form.vars.id)
1934 if self.settings.registration_requires_verification:
1935 if not self.settings.mailer or \
1936 not self.settings.mailer.send(to=form.vars.email,
1937 subject=self.messages.verify_email_subject,
1938 message=self.messages.verify_email
1939 % dict(key=key)):
1940 self.db.rollback()
1941 response.flash = self.messages.unable_send_email
1942 return form
1943 session.flash = self.messages.email_sent
1944 if self.settings.registration_requires_approval and \
1945 not self.settings.registration_requires_verification:
1946 table_user[form.vars.id] = dict(registration_key='pending')
1947 session.flash = self.messages.registration_pending
1948 elif (not self.settings.registration_requires_verification or \
1949 self.settings.login_after_registration):
1950 if not self.settings.registration_requires_verification:
1951 table_user[form.vars.id] = dict(registration_key='')
1952 session.flash = self.messages.registration_successful
1953 table_user = self.settings.table_user
1954 if 'username' in table_user.fields:
1955 username = 'username'
1956 else:
1957 username = 'email'
1958 user = self.db(table_user[username] == form.vars[username]).select().first()
1959 user = Storage(table_user._filter_fields(user, id=True))
1960 session.auth = Storage(user=user, last_visit=request.now,
1961 expiration=self.settings.expiration,
1962 hmac_key = web2py_uuid())
1963 self.user = user
1964 self.update_groups()
1965 session.flash = self.messages.logged_in
1966 self.log_event(log, form.vars)
1967 callback(onaccept,form)
1968 if not next:
1969 next = self.url(args = request.args)
1970 else:
1971 next = replace_id(next, form)
1972 redirect(next)
1973 return form
1974
1976 """
1977 checks if the user is logged in and returns True/False.
1978 if so user is in auth.user as well as in session.auth.user
1979 """
1980
1981 if self.user:
1982 return True
1983 return False
1984
2022
2030 """
2031 returns a form to retrieve the user username
2032 (only if there is a username field)
2033
2034 method: Auth.retrieve_username([next=DEFAULT
2035 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
2036
2037 """
2038
2039 table_user = self.settings.table_user
2040 if not 'username' in table_user.fields:
2041 raise HTTP(404)
2042 request = current.request
2043 response = current.response
2044 session = current.session
2045 captcha = self.settings.retrieve_username_captcha or \
2046 (self.settings.retrieve_username_captcha!=False and self.settings.captcha)
2047 if not self.settings.mailer:
2048 response.flash = self.messages.function_disabled
2049 return ''
2050 if next is DEFAULT:
2051 next = self.next or self.settings.retrieve_username_next
2052 if onvalidation is DEFAULT:
2053 onvalidation = self.settings.retrieve_username_onvalidation
2054 if onaccept is DEFAULT:
2055 onaccept = self.settings.retrieve_username_onaccept
2056 if log is DEFAULT:
2057 log = self.messages.retrieve_username_log
2058 old_requires = table_user.email.requires
2059 table_user.email.requires = [IS_IN_DB(self.db, table_user.email,
2060 error_message=self.messages.invalid_email)]
2061 form = SQLFORM(table_user,
2062 fields=['email'],
2063 hidden = dict(_next=next),
2064 showid=self.settings.showid,
2065 submit_button=self.messages.submit_button,
2066 delete_label=self.messages.delete_label,
2067 formstyle=self.settings.formstyle,
2068 separator=self.settings.label_separator
2069 )
2070 if captcha:
2071 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row')
2072
2073 if form.accepts(request, session,
2074 formname='retrieve_username', dbio=False,
2075 onvalidation=onvalidation,hideerror=self.settings.hideerror):
2076 user = self.db(table_user.email == form.vars.email).select().first()
2077 if not user:
2078 current.session.flash = \
2079 self.messages.invalid_email
2080 redirect(self.url(args=request.args))
2081 username = user.username
2082 self.settings.mailer.send(to=form.vars.email,
2083 subject=self.messages.retrieve_username_subject,
2084 message=self.messages.retrieve_username
2085 % dict(username=username))
2086 session.flash = self.messages.email_sent
2087 self.log_event(log, user)
2088 callback(onaccept,form)
2089 if not next:
2090 next = self.url(args = request.args)
2091 else:
2092 next = replace_id(next, form)
2093 redirect(next)
2094 table_user.email.requires = old_requires
2095 return form
2096
2098 import string
2099 import random
2100 password = ''
2101 specials=r'!#$*'
2102 for i in range(0,3):
2103 password += random.choice(string.lowercase)
2104 password += random.choice(string.uppercase)
2105 password += random.choice(string.digits)
2106 password += random.choice(specials)
2107 return ''.join(random.sample(password,len(password)))
2108
2116 """
2117 returns a form to reset the user password (deprecated)
2118
2119 method: Auth.reset_password_deprecated([next=DEFAULT
2120 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
2121
2122 """
2123
2124 table_user = self.settings.table_user
2125 request = current.request
2126 response = current.response
2127 session = current.session
2128 if not self.settings.mailer:
2129 response.flash = self.messages.function_disabled
2130 return ''
2131 if next is DEFAULT:
2132 next = self.next or self.settings.retrieve_password_next
2133 if onvalidation is DEFAULT:
2134 onvalidation = self.settings.retrieve_password_onvalidation
2135 if onaccept is DEFAULT:
2136 onaccept = self.settings.retrieve_password_onaccept
2137 if log is DEFAULT:
2138 log = self.messages.retrieve_password_log
2139 old_requires = table_user.email.requires
2140 table_user.email.requires = [IS_IN_DB(self.db, table_user.email,
2141 error_message=self.messages.invalid_email)]
2142 form = SQLFORM(table_user,
2143 fields=['email'],
2144 hidden = dict(_next=next),
2145 showid=self.settings.showid,
2146 submit_button=self.messages.submit_button,
2147 delete_label=self.messages.delete_label,
2148 formstyle=self.settings.formstyle,
2149 separator=self.settings.label_separator
2150 )
2151 if form.accepts(request, session,
2152 formname='retrieve_password', dbio=False,
2153 onvalidation=onvalidation,hideerror=self.settings.hideerror):
2154 user = self.db(table_user.email == form.vars.email).select().first()
2155 if not user:
2156 current.session.flash = \
2157 self.messages.invalid_email
2158 redirect(self.url(args=request.args))
2159 elif user.registration_key in ('pending','disabled','blocked'):
2160 current.session.flash = \
2161 self.messages.registration_pending
2162 redirect(self.url(args=request.args))
2163 password = self.random_password()
2164 passfield = self.settings.password_field
2165 d = {passfield: table_user[passfield].validate(password)[0],
2166 'registration_key': ''}
2167 user.update_record(**d)
2168 if self.settings.mailer and \
2169 self.settings.mailer.send(to=form.vars.email,
2170 subject=self.messages.retrieve_password_subject,
2171 message=self.messages.retrieve_password \
2172 % dict(password=password)):
2173 session.flash = self.messages.email_sent
2174 else:
2175 session.flash = self.messages.unable_to_send_email
2176 self.log_event(log, user)
2177 callback(onaccept,form)
2178 if not next:
2179 next = self.url(args = request.args)
2180 else:
2181 next = replace_id(next, form)
2182 redirect(next)
2183 table_user.email.requires = old_requires
2184 return form
2185
2193 """
2194 returns a form to reset the user password
2195
2196 method: Auth.reset_password([next=DEFAULT
2197 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
2198
2199 """
2200
2201 table_user = self.settings.table_user
2202 request = current.request
2203
2204 session = current.session
2205
2206 if next is DEFAULT:
2207 next = self.next or self.settings.reset_password_next
2208 try:
2209 key = request.vars.key or request.args[-1]
2210 t0 = int(key.split('-')[0])
2211 if time.time()-t0 > 60*60*24: raise Exception
2212 user = self.db(table_user.reset_password_key == key).select().first()
2213 if not user: raise Exception
2214 except Exception:
2215 session.flash = self.messages.invalid_reset_password
2216 redirect(next)
2217 passfield = self.settings.password_field
2218 form = SQLFORM.factory(
2219 Field('new_password', 'password',
2220 label=self.messages.new_password,
2221 requires=self.settings.table_user[passfield].requires),
2222 Field('new_password2', 'password',
2223 label=self.messages.verify_password,
2224 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
2225 self.messages.mismatched_password)]),
2226 submit_button=self.messages.password_reset_button,
2227 hidden = dict(_next=next),
2228 formstyle=self.settings.formstyle,
2229 separator=self.settings.label_separator
2230 )
2231 if form.accepts(request,session,hideerror=self.settings.hideerror):
2232 user.update_record(**{passfield:form.vars.new_password,
2233 'registration_key':'',
2234 'reset_password_key':''})
2235 session.flash = self.messages.password_changed
2236 redirect(next)
2237 return form
2238
2246 """
2247 returns a form to reset the user password
2248
2249 method: Auth.reset_password([next=DEFAULT
2250 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
2251
2252 """
2253
2254 table_user = self.settings.table_user
2255 request = current.request
2256 response = current.response
2257 session = current.session
2258 captcha = self.settings.retrieve_password_captcha or \
2259 (self.settings.retrieve_password_captcha!=False and self.settings.captcha)
2260
2261 if next is DEFAULT:
2262 next = self.next or self.settings.request_reset_password_next
2263 if not self.settings.mailer:
2264 response.flash = self.messages.function_disabled
2265 return ''
2266 if onvalidation is DEFAULT:
2267 onvalidation = self.settings.reset_password_onvalidation
2268 if onaccept is DEFAULT:
2269 onaccept = self.settings.reset_password_onaccept
2270 if log is DEFAULT:
2271 log = self.messages.reset_password_log
2272 table_user.email.requires = [
2273 IS_EMAIL(error_message=self.messages.invalid_email),
2274 IS_IN_DB(self.db, table_user.email,
2275 error_message=self.messages.invalid_email)]
2276 form = SQLFORM(table_user,
2277 fields=['email'],
2278 hidden = dict(_next=next),
2279 showid=self.settings.showid,
2280 submit_button=self.messages.password_reset_button,
2281 delete_label=self.messages.delete_label,
2282 formstyle=self.settings.formstyle,
2283 separator=self.settings.label_separator
2284 )
2285 if captcha:
2286 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row')
2287 if form.accepts(request, session,
2288 formname='reset_password', dbio=False,
2289 onvalidation=onvalidation,
2290 hideerror=self.settings.hideerror):
2291 user = self.db(table_user.email == form.vars.email).select().first()
2292 if not user:
2293 session.flash = self.messages.invalid_email
2294 redirect(self.url(args=request.args))
2295 elif user.registration_key in ('pending','disabled','blocked'):
2296 session.flash = self.messages.registration_pending
2297 redirect(self.url(args=request.args))
2298 reset_password_key = str(int(time.time()))+'-' + web2py_uuid()
2299
2300 if self.settings.mailer.send(to=form.vars.email,
2301 subject=self.messages.reset_password_subject,
2302 message=self.messages.reset_password % \
2303 dict(key=reset_password_key)):
2304 session.flash = self.messages.email_sent
2305 user.update_record(reset_password_key=reset_password_key)
2306 else:
2307 session.flash = self.messages.unable_to_send_email
2308 self.log_event(log, user)
2309 callback(onaccept,form)
2310 if not next:
2311 next = self.url(args = request.args)
2312 else:
2313 next = replace_id(next, form)
2314 redirect(next)
2315
2316 return form
2317
2329
2337 """
2338 returns a form that lets the user change password
2339
2340 method: Auth.change_password([next=DEFAULT[, onvalidation=DEFAULT[,
2341 onaccept=DEFAULT[, log=DEFAULT]]]])
2342 """
2343
2344 if not self.is_logged_in():
2345 redirect(self.settings.login_url)
2346 db = self.db
2347 table_user = self.settings.table_user
2348 usern = self.settings.table_user_name
2349 s = db(table_user.id == self.user.id)
2350
2351 request = current.request
2352 session = current.session
2353 if next is DEFAULT:
2354 next = self.next or self.settings.change_password_next
2355 if onvalidation is DEFAULT:
2356 onvalidation = self.settings.change_password_onvalidation
2357 if onaccept is DEFAULT:
2358 onaccept = self.settings.change_password_onaccept
2359 if log is DEFAULT:
2360 log = self.messages.change_password_log
2361 passfield = self.settings.password_field
2362 form = SQLFORM.factory(
2363 Field('old_password', 'password',
2364 label=self.messages.old_password,
2365 requires=validators(
2366 table_user[passfield].requires,
2367 IS_IN_DB(s, '%s.%s' % (usern, passfield),
2368 error_message=self.messages.invalid_password))),
2369 Field('new_password', 'password',
2370 label=self.messages.new_password,
2371 requires=table_user[passfield].requires),
2372 Field('new_password2', 'password',
2373 label=self.messages.verify_password,
2374 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
2375 self.messages.mismatched_password)]),
2376 submit_button=self.messages.password_change_button,
2377 hidden = dict(_next=next),
2378 formstyle = self.settings.formstyle,
2379 separator=self.settings.label_separator
2380 )
2381 if form.accepts(request, session,
2382 formname='change_password',
2383 onvalidation=onvalidation,
2384 hideerror=self.settings.hideerror):
2385 d = {passfield: form.vars.new_password}
2386 s.update(**d)
2387 session.flash = self.messages.password_changed
2388 self.log_event(log, self.user)
2389 callback(onaccept,form)
2390 if not next:
2391 next = self.url(args=request.args)
2392 else:
2393 next = replace_id(next, form)
2394 redirect(next)
2395 return form
2396
2404 """
2405 returns a form that lets the user change his/her profile
2406
2407 method: Auth.profile([next=DEFAULT [, onvalidation=DEFAULT
2408 [, onaccept=DEFAULT [, log=DEFAULT]]]])
2409
2410 """
2411
2412 table_user = self.settings.table_user
2413 if not self.is_logged_in():
2414 redirect(self.settings.login_url)
2415 passfield = self.settings.password_field
2416 self.settings.table_user[passfield].writable = False
2417 request = current.request
2418 session = current.session
2419 if next is DEFAULT:
2420 next = self.next or self.settings.profile_next
2421 if onvalidation is DEFAULT:
2422 onvalidation = self.settings.profile_onvalidation
2423 if onaccept is DEFAULT:
2424 onaccept = self.settings.profile_onaccept
2425 if log is DEFAULT:
2426 log = self.messages.profile_log
2427 form = SQLFORM(
2428 table_user,
2429 self.user.id,
2430 fields = self.settings.profile_fields,
2431 hidden = dict(_next=next),
2432 showid = self.settings.showid,
2433 submit_button = self.messages.profile_save_button,
2434 delete_label = self.messages.delete_label,
2435 upload = self.settings.download_url,
2436 formstyle = self.settings.formstyle,
2437 separator=self.settings.label_separator
2438 )
2439 if form.accepts(request, session,
2440 formname='profile',
2441 onvalidation=onvalidation, hideerror=self.settings.hideerror):
2442 self.user.update(table_user._filter_fields(form.vars))
2443 session.flash = self.messages.profile_updated
2444 self.log_event(log,self.user)
2445 callback(onaccept,form)
2446 if not next:
2447 next = self.url(args=request.args)
2448 else:
2449 next = replace_id(next, form)
2450 redirect(next)
2451 return form
2452
2456
2458 """
2459 usage: POST TO http://..../impersonate request.post_vars.user_id=<id>
2460 set request.post_vars.user_id to 0 to restore original user.
2461
2462 requires impersonator is logged in and
2463 has_permission('impersonate', 'auth_user', user_id)
2464 """
2465 request = current.request
2466 session = current.session
2467 auth = session.auth
2468 if not self.is_logged_in():
2469 raise HTTP(401, "Not Authorized")
2470 current_id = auth.user.id
2471 requested_id = user_id
2472 if user_id is DEFAULT:
2473 user_id = current.request.post_vars.user_id
2474 if user_id and user_id != self.user.id and user_id != '0':
2475 if not self.has_permission('impersonate',
2476 self.settings.table_user_name,
2477 user_id):
2478 raise HTTP(403, "Forbidden")
2479 user = self.settings.table_user(user_id)
2480 if not user:
2481 raise HTTP(401, "Not Authorized")
2482 auth.impersonator = cPickle.dumps(session)
2483 auth.user.update(
2484 self.settings.table_user._filter_fields(user, True))
2485 self.user = auth.user
2486 if self.settings.login_onaccept:
2487 form = Storage(dict(vars=self.user))
2488 self.settings.login_onaccept(form)
2489 log = self.messages.impersonate_log
2490 self.log_event(log,dict(id=current_id, other_id=auth.user.id))
2491 elif user_id in (0, '0') and self.is_impersonating():
2492 session.clear()
2493 session.update(cPickle.loads(auth.impersonator))
2494 self.user = session.auth.user
2495 if requested_id is DEFAULT and not request.post_vars:
2496 return SQLFORM.factory(Field('user_id', 'integer'))
2497 return self.user
2498
2500 if not self.user:
2501 return
2502 user_groups = self.user_groups = {}
2503 if current.session.auth:
2504 current.session.auth.user_groups = self.user_groups
2505 memberships = self.db(self.settings.table_membership.user_id
2506 == self.user.id).select()
2507 for membership in memberships:
2508 group = self.settings.table_group(membership.group_id)
2509 if group:
2510 user_groups[membership.group_id] = group.role
2511
2513 """
2514 displays the groups and their roles for the logged in user
2515 """
2516
2517 if not self.is_logged_in():
2518 redirect(self.settings.login_url)
2519 memberships = self.db(self.settings.table_membership.user_id
2520 == self.user.id).select()
2521 table = TABLE()
2522 for membership in memberships:
2523 groups = self.db(self.settings.table_group.id
2524 == membership.group_id).select()
2525 if groups:
2526 group = groups[0]
2527 table.append(TR(H3(group.role, '(%s)' % group.id)))
2528 table.append(TR(P(group.description)))
2529 if not memberships:
2530 return None
2531 return table
2532
2534 """
2535 you can change the view for this page to make it look as you like
2536 """
2537 if current.request.ajax:
2538 raise HTTP(403,'ACCESS DENIED')
2539 return 'ACCESS DENIED'
2540
2541 - def requires(self, condition, requires_login=True):
2542 """
2543 decorator that prevents access to action if not logged in
2544 """
2545
2546 def decorator(action):
2547
2548 def f(*a, **b):
2549
2550 basic_allowed,basic_accepted,user = self.basic()
2551 user = user or self.user
2552 if requires_login:
2553 if not user:
2554 if self.settings.allow_basic_login_only or \
2555 basic_accepted or current.request.is_restful:
2556 raise HTTP(403,"Not authorized")
2557 elif current.request.ajax:
2558 return A('login',_href=self.settings.login_url)
2559 else:
2560 next = self.here()
2561 current.session.flash = current.response.flash
2562 return call_or_redirect(
2563 self.settings.on_failed_authentication,
2564 self.settings.login_url+\
2565 '?_next='+urllib.quote(next))
2566
2567 if callable(condition):
2568 flag = condition()
2569 else:
2570 flag = condition
2571 if not flag:
2572 current.session.flash = self.messages.access_denied
2573 return call_or_redirect(
2574 self.settings.on_failed_authorization)
2575 return action(*a, **b)
2576 f.__doc__ = action.__doc__
2577 f.__name__ = action.__name__
2578 f.__dict__.update(action.__dict__)
2579 return f
2580
2581 return decorator
2582
2584 """
2585 decorator that prevents access to action if not logged in
2586 """
2587 return self.requires(True)
2588
2590 """
2591 decorator that prevents access to action if not logged in or
2592 if user logged in is not a member of group_id.
2593 If role is provided instead of group_id then the
2594 group_id is calculated.
2595 """
2596 return self.requires(lambda: self.has_membership(group_id=group_id, role=role))
2597
2599 """
2600 decorator that prevents access to action if not logged in or
2601 if user logged in is not a member of any group (role) that
2602 has 'name' access to 'table_name', 'record_id'.
2603 """
2604 return self.requires(lambda: self.has_permission(name, table_name, record_id))
2605
2607 """
2608 decorator that prevents access to action if not logged in or
2609 if user logged in is not a member of group_id.
2610 If role is provided instead of group_id then the
2611 group_id is calculated.
2612 """
2613 return self.requires(lambda: URL.verify(current.request,user_signature=True))
2614
2616 """
2617 creates a group associated to a role
2618 """
2619
2620 group_id = self.settings.table_group.insert(
2621 role=role, description=description)
2622 self.log_event(self.messages.add_group_log,
2623 dict(group_id=group_id, role=role))
2624 return group_id
2625
2627 """
2628 deletes a group
2629 """
2630
2631 self.db(self.settings.table_group.id == group_id).delete()
2632 self.db(self.settings.table_membership.group_id == group_id).delete()
2633 self.db(self.settings.table_permission.group_id == group_id).delete()
2634 self.update_groups()
2635 self.log_event(self.messages.del_group_log,dict(group_id=group_id))
2636
2638 """
2639 returns the group_id of the group specified by the role
2640 """
2641 rows = self.db(self.settings.table_group.role == role).select()
2642 if not rows:
2643 return None
2644 return rows[0].id
2645
2647 """
2648 returns the group_id of the group uniquely associated to this user
2649 i.e. role=user:[user_id]
2650 """
2651 if not user_id and self.user:
2652 user_id = self.user.id
2653 role = 'user_%s' % user_id
2654 return self.id_group(role)
2655
2657 """
2658 checks if user is member of group_id or role
2659 """
2660
2661 group_id = group_id or self.id_group(role)
2662 try:
2663 group_id = int(group_id)
2664 except:
2665 group_id = self.id_group(group_id)
2666 if not user_id and self.user:
2667 user_id = self.user.id
2668 membership = self.settings.table_membership
2669 if self.db((membership.user_id == user_id)
2670 & (membership.group_id == group_id)).select():
2671 r = True
2672 else:
2673 r = False
2674 self.log_event(self.messages.has_membership_log,
2675 dict(user_id=user_id,group_id=group_id, check=r))
2676 return r
2677
2679 """
2680 gives user_id membership of group_id or role
2681 if user is None than user_id is that of current logged in user
2682 """
2683
2684 group_id = group_id or self.id_group(role)
2685 try:
2686 group_id = int(group_id)
2687 except:
2688 group_id = self.id_group(group_id)
2689 if not user_id and self.user:
2690 user_id = self.user.id
2691 membership = self.settings.table_membership
2692 record = membership(user_id = user_id,group_id = group_id)
2693 if record:
2694 return record.id
2695 else:
2696 id = membership.insert(group_id=group_id, user_id=user_id)
2697 self.update_groups()
2698 self.log_event(self.messages.add_membership_log,
2699 dict(user_id=user_id, group_id=group_id))
2700 return id
2701
2703 """
2704 revokes membership from group_id to user_id
2705 if user_id is None than user_id is that of current logged in user
2706 """
2707
2708 group_id = group_id or self.id_group(role)
2709 if not user_id and self.user:
2710 user_id = self.user.id
2711 membership = self.settings.table_membership
2712 self.log_event(self.messages.del_membership_log,
2713 dict(user_id=user_id,group_id=group_id))
2714 ret = self.db(membership.user_id
2715 == user_id)(membership.group_id
2716 == group_id).delete()
2717 self.update_groups()
2718 return ret
2719
2720 - def has_permission(
2721 self,
2722 name='any',
2723 table_name='',
2724 record_id=0,
2725 user_id=None,
2726 group_id=None,
2727 ):
2728 """
2729 checks if user_id or current logged in user is member of a group
2730 that has 'name' permission on 'table_name' and 'record_id'
2731 if group_id is passed, it checks whether the group has the permission
2732 """
2733
2734 if not user_id and not group_id and self.user:
2735 user_id = self.user.id
2736 if user_id:
2737 membership = self.settings.table_membership
2738 rows = self.db(membership.user_id
2739 == user_id).select(membership.group_id)
2740 groups = set([row.group_id for row in rows])
2741 if group_id and not group_id in groups:
2742 return False
2743 else:
2744 groups = set([group_id])
2745 permission = self.settings.table_permission
2746 rows = self.db(permission.name == name)(permission.table_name
2747 == str(table_name))(permission.record_id
2748 == record_id).select(permission.group_id)
2749 groups_required = set([row.group_id for row in rows])
2750 if record_id:
2751 rows = self.db(permission.name
2752 == name)(permission.table_name
2753 == str(table_name))(permission.record_id
2754 == 0).select(permission.group_id)
2755 groups_required = groups_required.union(set([row.group_id
2756 for row in rows]))
2757 if groups.intersection(groups_required):
2758 r = True
2759 else:
2760 r = False
2761 if user_id:
2762 self.log_event(self.messages.has_permission_log,
2763 dict(user_id=user_id, name=name,
2764 table_name=table_name, record_id=record_id))
2765 return r
2766
2767 - def add_permission(
2768 self,
2769 group_id,
2770 name='any',
2771 table_name='',
2772 record_id=0,
2773 ):
2774 """
2775 gives group_id 'name' access to 'table_name' and 'record_id'
2776 """
2777
2778 permission = self.settings.table_permission
2779 if group_id == 0:
2780 group_id = self.user_group()
2781 id = permission.insert(group_id=group_id, name=name,
2782 table_name=str(table_name),
2783 record_id=long(record_id))
2784 self.log_event(self.messages.add_permission_log,
2785 dict(permission_id=id, group_id=group_id,
2786 name=name, table_name=table_name,
2787 record_id=record_id))
2788 return id
2789
2790 - def del_permission(
2791 self,
2792 group_id,
2793 name='any',
2794 table_name='',
2795 record_id=0,
2796 ):
2797 """
2798 revokes group_id 'name' access to 'table_name' and 'record_id'
2799 """
2800
2801 permission = self.settings.table_permission
2802 self.log_event(self.messages.del_permission_log,
2803 dict(group_id=group_id, name=name,
2804 table_name=table_name, record_id=record_id))
2805 return self.db(permission.group_id == group_id)(permission.name
2806 == name)(permission.table_name
2807 == str(table_name))(permission.record_id
2808 == long(record_id)).delete()
2809
2811 """
2812 returns a query with all accessible records for user_id or
2813 the current logged in user
2814 this method does not work on GAE because uses JOIN and IN
2815
2816 example:
2817
2818 db(auth.accessible_query('read', db.mytable)).select(db.mytable.ALL)
2819
2820 """
2821 if not user_id:
2822 user_id = self.user_id
2823 if self.has_permission(name, table, 0, user_id):
2824 return table.id > 0
2825 db = self.db
2826 membership = self.settings.table_membership
2827 permission = self.settings.table_permission
2828 return table.id.belongs(db(membership.user_id == user_id)\
2829 (membership.group_id == permission.group_id)\
2830 (permission.name == name)\
2831 (permission.table_name == table)\
2832 ._select(permission.record_id))
2833
2834 @staticmethod
2835 - def archive(form,
2836 archive_table=None,
2837 current_record='current_record',
2838 archive_current=False,
2839 fields=None):
2840 """
2841 If you have a table (db.mytable) that needs full revision history you can just do:
2842
2843 form=crud.update(db.mytable,myrecord,onaccept=auth.archive)
2844
2845 or
2846
2847 form=SQLFORM(db.mytable,myrecord).process(onaccept=auth.archive)
2848
2849 crud.archive will define a new table "mytable_archive" and store
2850 a copy of the current record (if archive_current=True)
2851 or a copy of the previous record (if archive_current=False)
2852 in the newly created table including a reference
2853 to the current record.
2854
2855 fields allows to specify extra fields that need to be archived.
2856
2857 If you want to access such table you need to define it yourself
2858 in a model:
2859
2860 db.define_table('mytable_archive',
2861 Field('current_record',db.mytable),
2862 db.mytable)
2863
2864 Notice such table includes all fields of db.mytable plus one: current_record.
2865 crud.archive does not timestamp the stored record unless your original table
2866 has a fields like:
2867
2868 db.define_table(...,
2869 Field('saved_on','datetime',
2870 default=request.now,update=request.now,writable=False),
2871 Field('saved_by',auth.user,
2872 default=auth.user_id,update=auth.user_id,writable=False),
2873
2874 there is nothing special about these fields since they are filled before
2875 the record is archived.
2876
2877 If you want to change the archive table name and the name of the reference field
2878 you can do, for example:
2879
2880 db.define_table('myhistory',
2881 Field('parent_record',db.mytable),
2882 db.mytable)
2883
2884 and use it as:
2885
2886 form=crud.update(db.mytable,myrecord,
2887 onaccept=lambda form:crud.archive(form,
2888 archive_table=db.myhistory,
2889 current_record='parent_record'))
2890
2891 """
2892 if not archive_current and not form.record:
2893 return None
2894 table = form.table
2895 if not archive_table:
2896 archive_table_name = '%s_archive' % table
2897 if archive_table_name in table._db:
2898 archive_table = table._db[archive_table_name]
2899 else:
2900 archive_table = table._db.define_table(archive_table_name,
2901 Field(current_record,table),
2902 table)
2903 new_record = {current_record:form.vars.id}
2904 for fieldname in archive_table.fields:
2905 if not fieldname in ['id',current_record]:
2906 if archive_current and fieldname in form.vars:
2907 new_record[fieldname]=form.vars[fieldname]
2908 elif form.record and fieldname in form.record:
2909 new_record[fieldname]=form.record[fieldname]
2910 if fields:
2911 for key,value in fields.items():
2912 new_record[key] = value
2913 id = archive_table.insert(**new_record)
2914 return id
2915
2916 -class Crud(object):
2917
2918 - def url(self, f=None, args=None, vars=None):
2919 """
2920 this should point to the controller that exposes
2921 download and crud
2922 """
2923 if args is None: args=[]
2924 if vars is None: vars={}
2925 return URL(c=self.settings.controller, f=f, args=args, vars=vars)
2926
2927 - def __init__(self, environment, db=None, controller='default'):
2928 self.db = db
2929 if not db and environment and isinstance(environment,DAL):
2930 self.db = environment
2931 elif not db:
2932 raise SyntaxError, "must pass db as first or second argument"
2933 self.environment = current
2934 settings = self.settings = Settings()
2935 settings.auth = None
2936 settings.logger = None
2937
2938 settings.create_next = None
2939 settings.update_next = None
2940 settings.controller = controller
2941 settings.delete_next = self.url()
2942 settings.download_url = self.url('download')
2943 settings.create_onvalidation = StorageList()
2944 settings.update_onvalidation = StorageList()
2945 settings.delete_onvalidation = StorageList()
2946 settings.create_onaccept = StorageList()
2947 settings.update_onaccept = StorageList()
2948 settings.update_ondelete = StorageList()
2949 settings.delete_onaccept = StorageList()
2950 settings.update_deletable = True
2951 settings.showid = False
2952 settings.keepvalues = False
2953 settings.create_captcha = None
2954 settings.update_captcha = None
2955 settings.captcha = None
2956 settings.formstyle = 'table3cols'
2957 settings.label_separator = ': '
2958 settings.hideerror = False
2959 settings.detect_record_change = True
2960 settings.hmac_key = None
2961 settings.lock_keys = True
2962
2963 messages = self.messages = Messages(current.T)
2964 messages.submit_button = 'Submit'
2965 messages.delete_label = 'Check to delete:'
2966 messages.record_created = 'Record Created'
2967 messages.record_updated = 'Record Updated'
2968 messages.record_deleted = 'Record Deleted'
2969
2970 messages.update_log = 'Record %(id)s updated'
2971 messages.create_log = 'Record %(id)s created'
2972 messages.read_log = 'Record %(id)s read'
2973 messages.delete_log = 'Record %(id)s deleted'
2974
2975 messages.lock_keys = True
2976
2978 args = current.request.args
2979 if len(args) < 1:
2980 raise HTTP(404)
2981 elif args[0] == 'tables':
2982 return self.tables()
2983 elif len(args) > 1 and not args(1) in self.db.tables:
2984 raise HTTP(404)
2985 table = self.db[args(1)]
2986 if args[0] == 'create':
2987 return self.create(table)
2988 elif args[0] == 'select':
2989 return self.select(table,linkto=self.url(args='read'))
2990 elif args[0] == 'search':
2991 form, rows = self.search(table,linkto=self.url(args='read'))
2992 return DIV(form,SQLTABLE(rows))
2993 elif args[0] == 'read':
2994 return self.read(table, args(2))
2995 elif args[0] == 'update':
2996 return self.update(table, args(2))
2997 elif args[0] == 'delete':
2998 return self.delete(table, args(2))
2999 else:
3000 raise HTTP(404)
3001
3005
3007 if not self.settings.auth:
3008 return True
3009 try:
3010 record_id = record.id
3011 except:
3012 record_id = record
3013 return self.settings.auth.has_permission(name, str(table), record_id)
3014
3019
3020 @staticmethod
3021 - def archive(form,archive_table=None,current_record='current_record'):
3022 return Auth.archive(form,archive_table=archive_table,
3023 current_record=current_record)
3024
3025 - def update(
3026 self,
3027 table,
3028 record,
3029 next=DEFAULT,
3030 onvalidation=DEFAULT,
3031 onaccept=DEFAULT,
3032 ondelete=DEFAULT,
3033 log=DEFAULT,
3034 message=DEFAULT,
3035 deletable=DEFAULT,
3036 formname=DEFAULT,
3037 ):
3038 """
3039 method: Crud.update(table, record, [next=DEFAULT
3040 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT
3041 [, message=DEFAULT[, deletable=DEFAULT]]]]]])
3042
3043 """
3044 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
3045 or (isinstance(record, str) and not str(record).isdigit()):
3046 raise HTTP(404)
3047 if not isinstance(table, self.db.Table):
3048 table = self.db[table]
3049 try:
3050 record_id = record.id
3051 except:
3052 record_id = record or 0
3053 if record_id and not self.has_permission('update', table, record_id):
3054 redirect(self.settings.auth.settings.on_failed_authorization)
3055 if not record_id and not self.has_permission('create', table, record_id):
3056 redirect(self.settings.auth.settings.on_failed_authorization)
3057
3058 request = current.request
3059 response = current.response
3060 session = current.session
3061 if request.extension == 'json' and request.vars.json:
3062 request.vars.update(json_parser.loads(request.vars.json))
3063 if next is DEFAULT:
3064 next = request.get_vars._next \
3065 or request.post_vars._next \
3066 or self.settings.update_next
3067 if onvalidation is DEFAULT:
3068 onvalidation = self.settings.update_onvalidation
3069 if onaccept is DEFAULT:
3070 onaccept = self.settings.update_onaccept
3071 if ondelete is DEFAULT:
3072 ondelete = self.settings.update_ondelete
3073 if log is DEFAULT:
3074 log = self.messages.update_log
3075 if deletable is DEFAULT:
3076 deletable = self.settings.update_deletable
3077 if message is DEFAULT:
3078 message = self.messages.record_updated
3079 form = SQLFORM(
3080 table,
3081 record,
3082 hidden=dict(_next=next),
3083 showid=self.settings.showid,
3084 submit_button=self.messages.submit_button,
3085 delete_label=self.messages.delete_label,
3086 deletable=deletable,
3087 upload=self.settings.download_url,
3088 formstyle=self.settings.formstyle,
3089 separator=self.settings.label_separator
3090 )
3091 self.accepted = False
3092 self.deleted = False
3093 captcha = self.settings.update_captcha or self.settings.captcha
3094 if record and captcha:
3095 addrow(form, captcha.label, captcha, captcha.comment,
3096 self.settings.formstyle,'captcha__row')
3097 captcha = self.settings.create_captcha or self.settings.captcha
3098 if not record and captcha:
3099 addrow(form, captcha.label, captcha, captcha.comment,
3100 self.settings.formstyle,'captcha__row')
3101 if not request.extension in ('html','load'):
3102 (_session, _formname) = (None, None)
3103 else:
3104 (_session, _formname) = (session, '%s/%s' % (table._tablename, form.record_id))
3105 if not formname is DEFAULT:
3106 _formname = formname
3107 keepvalues = self.settings.keepvalues
3108 if request.vars.delete_this_record:
3109 keepvalues = False
3110 if isinstance(onvalidation,StorageList):
3111 onvalidation=onvalidation.get(table._tablename, [])
3112 if form.accepts(request, _session, formname=_formname,
3113 onvalidation=onvalidation, keepvalues=keepvalues,
3114 hideerror=self.settings.hideerror,
3115 detect_record_change = self.settings.detect_record_change):
3116 self.accepted = True
3117 response.flash = message
3118 if log:
3119 self.log_event(log, form.vars)
3120 if request.vars.delete_this_record:
3121 self.deleted = True
3122 message = self.messages.record_deleted
3123 callback(ondelete,form,table._tablename)
3124 response.flash = message
3125 callback(onaccept,form,table._tablename)
3126 if not request.extension in ('html','load'):
3127 raise HTTP(200, 'RECORD CREATED/UPDATED')
3128 if isinstance(next, (list, tuple)):
3129 next = next[0]
3130 if next:
3131 next = replace_id(next, form)
3132 session.flash = response.flash
3133 redirect(next)
3134 elif not request.extension in ('html','load'):
3135 raise HTTP(401,serializers.json(dict(errors=form.errors)))
3136 return form
3137
3148 """
3149 method: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT
3150 [, onaccept=DEFAULT [, log=DEFAULT[, message=DEFAULT]]]]])
3151 """
3152
3153 if next is DEFAULT:
3154 next = self.settings.create_next
3155 if onvalidation is DEFAULT:
3156 onvalidation = self.settings.create_onvalidation
3157 if onaccept is DEFAULT:
3158 onaccept = self.settings.create_onaccept
3159 if log is DEFAULT:
3160 log = self.messages.create_log
3161 if message is DEFAULT:
3162 message = self.messages.record_created
3163 return self.update(
3164 table,
3165 None,
3166 next=next,
3167 onvalidation=onvalidation,
3168 onaccept=onaccept,
3169 log=log,
3170 message=message,
3171 deletable=False,
3172 formname=formname,
3173 )
3174
3175 - def read(self, table, record):
3176 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
3177 or (isinstance(record, str) and not str(record).isdigit()):
3178 raise HTTP(404)
3179 if not isinstance(table, self.db.Table):
3180 table = self.db[table]
3181 if not self.has_permission('read', table, record):
3182 redirect(self.settings.auth.settings.on_failed_authorization)
3183 form = SQLFORM(
3184 table,
3185 record,
3186 readonly=True,
3187 comments=False,
3188 upload=self.settings.download_url,
3189 showid=self.settings.showid,
3190 formstyle=self.settings.formstyle,
3191 separator=self.settings.label_separator
3192 )
3193 if not current.request.extension in ('html','load'):
3194 return table._filter_fields(form.record, id=True)
3195 return form
3196
3197 - def delete(
3198 self,
3199 table,
3200 record_id,
3201 next=DEFAULT,
3202 message=DEFAULT,
3203 ):
3204 """
3205 method: Crud.delete(table, record_id, [next=DEFAULT
3206 [, message=DEFAULT]])
3207 """
3208 if not (isinstance(table, self.db.Table) or table in self.db.tables):
3209 raise HTTP(404)
3210 if not isinstance(table, self.db.Table):
3211 table = self.db[table]
3212 if not self.has_permission('delete', table, record_id):
3213 redirect(self.settings.auth.settings.on_failed_authorization)
3214 request = current.request
3215 session = current.session
3216 if next is DEFAULT:
3217 next = request.get_vars._next \
3218 or request.post_vars._next \
3219 or self.settings.delete_next
3220 if message is DEFAULT:
3221 message = self.messages.record_deleted
3222 record = table[record_id]
3223 if record:
3224 callback(self.settings.delete_onvalidation,record)
3225 del table[record_id]
3226 callback(self.settings.delete_onaccept,record,table._tablename)
3227 session.flash = message
3228 redirect(next)
3229
3230 - def rows(
3231 self,
3232 table,
3233 query=None,
3234 fields=None,
3235 orderby=None,
3236 limitby=None,
3237 ):
3238 if not (isinstance(table, self.db.Table) or table in self.db.tables):
3239 raise HTTP(404)
3240 if not self.has_permission('select', table):
3241 redirect(self.settings.auth.settings.on_failed_authorization)
3242
3243
3244 if not isinstance(table, self.db.Table):
3245 table = self.db[table]
3246 if not query:
3247 query = table.id > 0
3248 if not fields:
3249 fields = [field for field in table if field.readable]
3250 rows = self.db(query).select(*fields,**dict(orderby=orderby,
3251 limitby=limitby))
3252 return rows
3253
3254 - def select(
3255 self,
3256 table,
3257 query=None,
3258 fields=None,
3259 orderby=None,
3260 limitby=None,
3261 headers=None,
3262 **attr
3263 ):
3264 headers = headers or {}
3265 rows = self.rows(table,query,fields,orderby,limitby)
3266 if not rows:
3267 return None
3268 if not 'upload' in attr:
3269 attr['upload'] = self.url('download')
3270 if not current.request.extension in ('html','load'):
3271 return rows.as_list()
3272 if not headers:
3273 if isinstance(table,str):
3274 table = self.db[table]
3275 headers = dict((str(k),k.label) for k in table)
3276 return SQLTABLE(rows,headers=headers,**attr)
3277
3284
3285 - def get_query(self, field, op, value, refsearch=False):
3286 try:
3287 if refsearch: format = self.get_format(field)
3288 if op == 'equals':
3289 if not refsearch:
3290 return field == value
3291 else:
3292 return lambda row: row[field.name][format] == value
3293 elif op == 'not equal':
3294 if not refsearch:
3295 return field != value
3296 else:
3297 return lambda row: row[field.name][format] != value
3298 elif op == 'greater than':
3299 if not refsearch:
3300 return field > value
3301 else:
3302 return lambda row: row[field.name][format] > value
3303 elif op == 'less than':
3304 if not refsearch:
3305 return field < value
3306 else:
3307 return lambda row: row[field.name][format] < value
3308 elif op == 'starts with':
3309 if not refsearch:
3310 return field.like(value+'%')
3311 else:
3312 return lambda row: str(row[field.name][format]).startswith(value)
3313 elif op == 'ends with':
3314 if not refsearch:
3315 return field.like('%'+value)
3316 else:
3317 return lambda row: str(row[field.name][format]).endswith(value)
3318 elif op == 'contains':
3319 if not refsearch:
3320 return field.like('%'+value+'%')
3321 else:
3322 return lambda row: value in row[field.name][format]
3323 except:
3324 return None
3325
3326 - def search(self, *tables, **args):
3327 """
3328 Creates a search form and its results for a table
3329 Example usage:
3330 form, results = crud.search(db.test,
3331 queries = ['equals', 'not equal', 'contains'],
3332 query_labels={'equals':'Equals',
3333 'not equal':'Not equal'},
3334 fields = ['id','children'],
3335 field_labels = {'id':'ID','children':'Children'},
3336 zero='Please choose',
3337 query = (db.test.id > 0)&(db.test.id != 3) )
3338 """
3339 table = tables[0]
3340 fields = args.get('fields', table.fields)
3341 request = current.request
3342 db = self.db
3343 if not (isinstance(table, db.Table) or table in db.tables):
3344 raise HTTP(404)
3345 attributes = {}
3346 for key in ('orderby','groupby','left','distinct','limitby','cache'):
3347 if key in args: attributes[key]=args[key]
3348 tbl = TABLE()
3349 selected = []; refsearch = []; results = []
3350 showall = args.get('showall', False)
3351 if showall:
3352 selected = fields
3353 chkall = args.get('chkall', False)
3354 if chkall:
3355 for f in fields:
3356 request.vars['chk%s'%f] = 'on'
3357 ops = args.get('queries', [])
3358 zero = args.get('zero', '')
3359 if not ops:
3360 ops = ['equals', 'not equal', 'greater than',
3361 'less than', 'starts with',
3362 'ends with', 'contains']
3363 ops.insert(0,zero)
3364 query_labels = args.get('query_labels', {})
3365 query = args.get('query',table.id > 0)
3366 field_labels = args.get('field_labels',{})
3367 for field in fields:
3368 field = table[field]
3369 if not field.readable: continue
3370 fieldname = field.name
3371 chkval = request.vars.get('chk' + fieldname, None)
3372 txtval = request.vars.get('txt' + fieldname, None)
3373 opval = request.vars.get('op' + fieldname, None)
3374 row = TR(TD(INPUT(_type = "checkbox", _name = "chk" + fieldname,
3375 _disabled = (field.type == 'id'),
3376 value = (field.type == 'id' or chkval == 'on'))),
3377 TD(field_labels.get(fieldname,field.label)),
3378 TD(SELECT([OPTION(query_labels.get(op,op),
3379 _value=op) for op in ops],
3380 _name = "op" + fieldname,
3381 value = opval)),
3382 TD(INPUT(_type = "text", _name = "txt" + fieldname,
3383 _value = txtval, _id='txt' + fieldname,
3384 _class = str(field.type))))
3385 tbl.append(row)
3386 if request.post_vars and (chkval or field.type=='id'):
3387 if txtval and opval != '':
3388 if field.type[0:10] == 'reference ':
3389 refsearch.append(self.get_query(field,
3390 opval, txtval, refsearch=True))
3391 else:
3392 value, error = field.validate(txtval)
3393 if not error:
3394
3395 query &= self.get_query(field, opval, value)
3396 else:
3397 row[3].append(DIV(error,_class='error'))
3398 selected.append(field)
3399 form = FORM(tbl,INPUT(_type="submit"))
3400 if selected:
3401 try:
3402 results = db(query).select(*selected,**attributes)
3403 for r in refsearch:
3404 results = results.find(r)
3405 except:
3406 results = None
3407 return form, results
3408
3409
3410 urllib2.install_opener(urllib2.build_opener(urllib2.HTTPCookieProcessor()))
3411
3412 -def fetch(url, data=None, headers=None,
3413 cookie=Cookie.SimpleCookie(),
3414 user_agent='Mozilla/5.0'):
3415 headers = headers or {}
3416 if not data is None:
3417 data = urllib.urlencode(data)
3418 if user_agent: headers['User-agent'] = user_agent
3419 headers['Cookie'] = ' '.join(['%s=%s;'%(c.key,c.value) for c in cookie.values()])
3420 try:
3421 from google.appengine.api import urlfetch
3422 except ImportError:
3423 req = urllib2.Request(url, data, headers)
3424 html = urllib2.urlopen(req).read()
3425 else:
3426 method = ((data is None) and urlfetch.GET) or urlfetch.POST
3427 while url is not None:
3428 response = urlfetch.fetch(url=url, payload=data,
3429 method=method, headers=headers,
3430 allow_truncated=False,follow_redirects=False,
3431 deadline=10)
3432
3433 data = None
3434 method = urlfetch.GET
3435
3436 cookie.load(response.headers.get('set-cookie', ''))
3437 url = response.headers.get('location')
3438 html = response.content
3439 return html
3440
3441 regex_geocode = \
3442 re.compile('\<coordinates\>(?P<la>[^,]*),(?P<lo>[^,]*).*?\</coordinates\>')
3443
3444
3446 try:
3447 a = urllib.quote(address)
3448 txt = fetch('http://maps.google.com/maps/geo?q=%s&output=xml'
3449 % a)
3450 item = regex_geocode.search(txt)
3451 (la, lo) = (float(item.group('la')), float(item.group('lo')))
3452 return (la, lo)
3453 except:
3454 return (0.0, 0.0)
3455
3456
3458 c = f.func_code.co_argcount
3459 n = f.func_code.co_varnames[:c]
3460
3461 defaults = f.func_defaults or []
3462 pos_args = n[0:-len(defaults)]
3463 named_args = n[-len(defaults):]
3464
3465 arg_dict = {}
3466
3467
3468 for pos_index, pos_val in enumerate(a[:c]):
3469 arg_dict[n[pos_index]] = pos_val
3470
3471
3472
3473 for arg_name in pos_args[len(arg_dict):]:
3474 if b.has_key(arg_name):
3475 arg_dict[arg_name] = b[arg_name]
3476
3477 if len(arg_dict) >= len(pos_args):
3478
3479
3480 for arg_name in named_args:
3481 if b.has_key(arg_name):
3482 arg_dict[arg_name] = b[arg_name]
3483
3484 return f(**arg_dict)
3485
3486
3487 raise HTTP(404, "Object does not exist")
3488
3489
3491
3493 self.run_procedures = {}
3494 self.csv_procedures = {}
3495 self.xml_procedures = {}
3496 self.rss_procedures = {}
3497 self.json_procedures = {}
3498 self.jsonrpc_procedures = {}
3499 self.xmlrpc_procedures = {}
3500 self.amfrpc_procedures = {}
3501 self.amfrpc3_procedures = {}
3502 self.soap_procedures = {}
3503
3505 """
3506 example:
3507
3508 service = Service()
3509 @service.run
3510 def myfunction(a, b):
3511 return a + b
3512 def call():
3513 return service()
3514
3515 Then call it with:
3516
3517 wget http://..../app/default/call/run/myfunction?a=3&b=4
3518
3519 """
3520 self.run_procedures[f.__name__] = f
3521 return f
3522
3524 """
3525 example:
3526
3527 service = Service()
3528 @service.csv
3529 def myfunction(a, b):
3530 return a + b
3531 def call():
3532 return service()
3533
3534 Then call it with:
3535
3536 wget http://..../app/default/call/csv/myfunction?a=3&b=4
3537
3538 """
3539 self.run_procedures[f.__name__] = f
3540 return f
3541
3543 """
3544 example:
3545
3546 service = Service()
3547 @service.xml
3548 def myfunction(a, b):
3549 return a + b
3550 def call():
3551 return service()
3552
3553 Then call it with:
3554
3555 wget http://..../app/default/call/xml/myfunction?a=3&b=4
3556
3557 """
3558 self.run_procedures[f.__name__] = f
3559 return f
3560
3562 """
3563 example:
3564
3565 service = Service()
3566 @service.rss
3567 def myfunction():
3568 return dict(title=..., link=..., description=...,
3569 created_on=..., entries=[dict(title=..., link=...,
3570 description=..., created_on=...])
3571 def call():
3572 return service()
3573
3574 Then call it with:
3575
3576 wget http://..../app/default/call/rss/myfunction
3577
3578 """
3579 self.rss_procedures[f.__name__] = f
3580 return f
3581
3582 - def json(self, f):
3583 """
3584 example:
3585
3586 service = Service()
3587 @service.json
3588 def myfunction(a, b):
3589 return [{a: b}]
3590 def call():
3591 return service()
3592
3593 Then call it with:
3594
3595 wget http://..../app/default/call/json/myfunction?a=hello&b=world
3596
3597 """
3598 self.json_procedures[f.__name__] = f
3599 return f
3600
3602 """
3603 example:
3604
3605 service = Service()
3606 @service.jsonrpc
3607 def myfunction(a, b):
3608 return a + b
3609 def call():
3610 return service()
3611
3612 Then call it with:
3613
3614 wget http://..../app/default/call/jsonrpc/myfunction?a=hello&b=world
3615
3616 """
3617 self.jsonrpc_procedures[f.__name__] = f
3618 return f
3619
3621 """
3622 example:
3623
3624 service = Service()
3625 @service.xmlrpc
3626 def myfunction(a, b):
3627 return a + b
3628 def call():
3629 return service()
3630
3631 The call it with:
3632
3633 wget http://..../app/default/call/xmlrpc/myfunction?a=hello&b=world
3634
3635 """
3636 self.xmlrpc_procedures[f.__name__] = f
3637 return f
3638
3640 """
3641 example:
3642
3643 service = Service()
3644 @service.amfrpc
3645 def myfunction(a, b):
3646 return a + b
3647 def call():
3648 return service()
3649
3650 The call it with:
3651
3652 wget http://..../app/default/call/amfrpc/myfunction?a=hello&b=world
3653
3654 """
3655 self.amfrpc_procedures[f.__name__] = f
3656 return f
3657
3658 - def amfrpc3(self, domain='default'):
3659 """
3660 example:
3661
3662 service = Service()
3663 @service.amfrpc3('domain')
3664 def myfunction(a, b):
3665 return a + b
3666 def call():
3667 return service()
3668
3669 The call it with:
3670
3671 wget http://..../app/default/call/amfrpc3/myfunction?a=hello&b=world
3672
3673 """
3674 if not isinstance(domain, str):
3675 raise SyntaxError, "AMF3 requires a domain for function"
3676
3677 def _amfrpc3(f):
3678 if domain:
3679 self.amfrpc3_procedures[domain+'.'+f.__name__] = f
3680 else:
3681 self.amfrpc3_procedures[f.__name__] = f
3682 return f
3683 return _amfrpc3
3684
3685 - def soap(self, name=None, returns=None, args=None,doc=None):
3686 """
3687 example:
3688
3689 service = Service()
3690 @service.soap('MyFunction',returns={'result':int},args={'a':int,'b':int,})
3691 def myfunction(a, b):
3692 return a + b
3693 def call():
3694 return service()
3695
3696 The call it with:
3697
3698 from gluon.contrib.pysimplesoap.client import SoapClient
3699 client = SoapClient(wsdl="http://..../app/default/call/soap?WSDL")
3700 response = client.MyFunction(a=1,b=2)
3701 return response['result']
3702
3703 Exposes online generated documentation and xml example messages at:
3704 - http://..../app/default/call/soap
3705 """
3706
3707 def _soap(f):
3708 self.soap_procedures[name or f.__name__] = f, returns, args, doc
3709 return f
3710 return _soap
3711
3720
3722 request = current.request
3723 response = current.response
3724 response.headers['Content-Type'] = 'text/x-csv'
3725 if not args:
3726 args = request.args
3727
3728 def none_exception(value):
3729 if isinstance(value, unicode):
3730 return value.encode('utf8')
3731 if hasattr(value, 'isoformat'):
3732 return value.isoformat()[:19].replace('T', ' ')
3733 if value is None:
3734 return '<NULL>'
3735 return value
3736 if args and args[0] in self.run_procedures:
3737 r = universal_caller(self.run_procedures[args[0]],
3738 *args[1:], **dict(request.vars))
3739 s = cStringIO.StringIO()
3740 if hasattr(r, 'export_to_csv_file'):
3741 r.export_to_csv_file(s)
3742 elif r and isinstance(r[0], (dict, Storage)):
3743 import csv
3744 writer = csv.writer(s)
3745 writer.writerow(r[0].keys())
3746 for line in r:
3747 writer.writerow([none_exception(v) \
3748 for v in line.values()])
3749 else:
3750 import csv
3751 writer = csv.writer(s)
3752 for line in r:
3753 writer.writerow(line)
3754 return s.getvalue()
3755 self.error()
3756
3770
3783
3797
3800 self.code,self.info = code,info
3801
3803 def return_response(id, result):
3804 return serializers.json({'version': '1.1',
3805 'id': id, 'result': result, 'error': None})
3806 def return_error(id, code, message):
3807 return serializers.json({'id': id,
3808 'version': '1.1',
3809 'error': {'name': 'JSONRPCError',
3810 'code': code, 'message': message}
3811 })
3812
3813 request = current.request
3814 response = current.response
3815 response.headers['Content-Type'] = 'application/json; charset=utf-8'
3816 methods = self.jsonrpc_procedures
3817 data = json_parser.loads(request.body.read())
3818 id, method, params = data['id'], data['method'], data.get('params','')
3819 if not method in methods:
3820 return return_error(id, 100, 'method "%s" does not exist' % method)
3821 try:
3822 s = methods[method](*params)
3823 if hasattr(s, 'as_list'):
3824 s = s.as_list()
3825 return return_response(id, s)
3826 except Service.JsonRpcException, e:
3827 return return_error(id, e.code, e.info)
3828 except BaseException:
3829 etype, eval, etb = sys.exc_info()
3830 return return_error(id, 100, '%s: %s' % (etype.__name__, eval))
3831 except:
3832 etype, eval, etb = sys.exc_info()
3833 return return_error(id, 100, 'Exception %s: %s' % (etype, eval))
3834
3840
3842 try:
3843 import pyamf
3844 import pyamf.remoting.gateway
3845 except:
3846 return "pyamf not installed or not in Python sys.path"
3847 request = current.request
3848 response = current.response
3849 if version == 3:
3850 services = self.amfrpc3_procedures
3851 base_gateway = pyamf.remoting.gateway.BaseGateway(services)
3852 pyamf_request = pyamf.remoting.decode(request.body)
3853 else:
3854 services = self.amfrpc_procedures
3855 base_gateway = pyamf.remoting.gateway.BaseGateway(services)
3856 context = pyamf.get_context(pyamf.AMF0)
3857 pyamf_request = pyamf.remoting.decode(request.body, context)
3858 pyamf_response = pyamf.remoting.Envelope(pyamf_request.amfVersion)
3859 for name, message in pyamf_request:
3860 pyamf_response[name] = base_gateway.getProcessor(message)(message)
3861 response.headers['Content-Type'] = pyamf.remoting.CONTENT_TYPE
3862 if version==3:
3863 return pyamf.remoting.encode(pyamf_response).getvalue()
3864 else:
3865 return pyamf.remoting.encode(pyamf_response, context).getvalue()
3866
3868 try:
3869 from contrib.pysimplesoap.server import SoapDispatcher
3870 except:
3871 return "pysimplesoap not installed in contrib"
3872 request = current.request
3873 response = current.response
3874 procedures = self.soap_procedures
3875
3876 location = "%s://%s%s" % (
3877 request.env.wsgi_url_scheme,
3878 request.env.http_host,
3879 URL(r=request,f="call/soap",vars={}))
3880 namespace = 'namespace' in response and response.namespace or location
3881 documentation = response.description or ''
3882 dispatcher = SoapDispatcher(
3883 name = response.title,
3884 location = location,
3885 action = location,
3886 namespace = namespace,
3887 prefix='pys',
3888 documentation = documentation,
3889 ns = True)
3890 for method, (function, returns, args, doc) in procedures.items():
3891 dispatcher.register_function(method, function, returns, args, doc)
3892 if request.env.request_method == 'POST':
3893
3894 response.headers['Content-Type'] = 'text/xml'
3895 return dispatcher.dispatch(request.body.read())
3896 elif 'WSDL' in request.vars:
3897
3898 response.headers['Content-Type'] = 'text/xml'
3899 return dispatcher.wsdl()
3900 elif 'op' in request.vars:
3901
3902 response.headers['Content-Type'] = 'text/html'
3903 method = request.vars['op']
3904 sample_req_xml, sample_res_xml, doc = dispatcher.help(method)
3905 body = [H1("Welcome to Web2Py SOAP webservice gateway"),
3906 A("See all webservice operations",
3907 _href=URL(r=request,f="call/soap",vars={})),
3908 H2(method),
3909 P(doc),
3910 UL(LI("Location: %s" % dispatcher.location),
3911 LI("Namespace: %s" % dispatcher.namespace),
3912 LI("SoapAction: %s" % dispatcher.action),
3913 ),
3914 H3("Sample SOAP XML Request Message:"),
3915 CODE(sample_req_xml,language="xml"),
3916 H3("Sample SOAP XML Response Message:"),
3917 CODE(sample_res_xml,language="xml"),
3918 ]
3919 return {'body': body}
3920 else:
3921
3922 response.headers['Content-Type'] = 'text/html'
3923 body = [H1("Welcome to Web2Py SOAP webservice gateway"),
3924 P(response.description),
3925 P("The following operations are available"),
3926 A("See WSDL for webservice description",
3927 _href=URL(r=request,f="call/soap",vars={"WSDL":None})),
3928 UL([LI(A("%s: %s" % (method, doc or ''),
3929 _href=URL(r=request,f="call/soap",vars={'op': method})))
3930 for method, doc in dispatcher.list_methods()]),
3931 ]
3932 return {'body': body}
3933
3935 """
3936 register services with:
3937 service = Service()
3938 @service.run
3939 @service.rss
3940 @service.json
3941 @service.jsonrpc
3942 @service.xmlrpc
3943 @service.amfrpc
3944 @service.amfrpc3('domain')
3945 @service.soap('Method', returns={'Result':int}, args={'a':int,'b':int,})
3946
3947 expose services with
3948
3949 def call(): return service()
3950
3951 call services with
3952 http://..../app/default/call/run?[parameters]
3953 http://..../app/default/call/rss?[parameters]
3954 http://..../app/default/call/json?[parameters]
3955 http://..../app/default/call/jsonrpc
3956 http://..../app/default/call/xmlrpc
3957 http://..../app/default/call/amfrpc
3958 http://..../app/default/call/amfrpc3
3959 http://..../app/default/call/soap
3960 """
3961
3962 request = current.request
3963 if len(request.args) < 1:
3964 raise HTTP(404, "Not Found")
3965 arg0 = request.args(0)
3966 if arg0 == 'run':
3967 return self.serve_run(request.args[1:])
3968 elif arg0 == 'rss':
3969 return self.serve_rss(request.args[1:])
3970 elif arg0 == 'csv':
3971 return self.serve_csv(request.args[1:])
3972 elif arg0 == 'xml':
3973 return self.serve_xml(request.args[1:])
3974 elif arg0 == 'json':
3975 return self.serve_json(request.args[1:])
3976 elif arg0 == 'jsonrpc':
3977 return self.serve_jsonrpc()
3978 elif arg0 == 'xmlrpc':
3979 return self.serve_xmlrpc()
3980 elif arg0 == 'amfrpc':
3981 return self.serve_amfrpc()
3982 elif arg0 == 'amfrpc3':
3983 return self.serve_amfrpc(3)
3984 elif arg0 == 'soap':
3985 return self.serve_soap()
3986 else:
3987 self.error()
3988
3990 raise HTTP(404, "Object does not exist")
3991
3992
3994 """
3995 Executes a task on completion of the called action. For example:
3996
3997 from gluon.tools import completion
3998 @completion(lambda d: logging.info(repr(d)))
3999 def index():
4000 return dict(message='hello')
4001
4002 It logs the output of the function every time input is called.
4003 The argument of completion is executed in a new thread.
4004 """
4005 def _completion(f):
4006 def __completion(*a,**b):
4007 d = None
4008 try:
4009 d = f(*a,**b)
4010 return d
4011 finally:
4012 thread.start_new_thread(callback,(d,))
4013 return __completion
4014 return _completion
4015
4017 try:
4018 dt = datetime.datetime.now() - d
4019 except:
4020 return ''
4021 if dt.days >= 2*365:
4022 return T('%d years ago') % int(dt.days / 365)
4023 elif dt.days >= 365:
4024 return T('1 year ago')
4025 elif dt.days >= 60:
4026 return T('%d months ago') % int(dt.days / 30)
4027 elif dt.days > 21:
4028 return T('1 month ago')
4029 elif dt.days >= 14:
4030 return T('%d weeks ago') % int(dt.days / 7)
4031 elif dt.days >= 7:
4032 return T('1 week ago')
4033 elif dt.days > 1:
4034 return T('%d days ago') % dt.days
4035 elif dt.days == 1:
4036 return T('1 day ago')
4037 elif dt.seconds >= 2*60*60:
4038 return T('%d hours ago') % int(dt.seconds / 3600)
4039 elif dt.seconds >= 60*60:
4040 return T('1 hour ago')
4041 elif dt.seconds >= 2*60:
4042 return T('%d minutes ago') % int(dt.seconds / 60)
4043 elif dt.seconds >= 60:
4044 return T('1 minute ago')
4045 elif dt.seconds > 1:
4046 return T('%d seconds ago') % dt.seconds
4047 elif dt.seconds == 1:
4048 return T('1 second ago')
4049 else:
4050 return T('now')
4051
4060 lock1=thread.allocate_lock()
4061 lock2=thread.allocate_lock()
4062 lock1.acquire()
4063 thread.start_new_thread(f,())
4064 a=PluginManager()
4065 a.x=5
4066 lock1.release()
4067 lock2.acquire()
4068 return a.x
4069
4071 """
4072
4073 Plugin Manager is similar to a storage object but it is a single level singleton
4074 this means that multiple instances within the same thread share the same attributes
4075 Its constructor is also special. The first argument is the name of the plugin you are defining.
4076 The named arguments are parameters needed by the plugin with default values.
4077 If the parameters were previous defined, the old values are used.
4078
4079 For example:
4080
4081 ### in some general configuration file:
4082 >>> plugins = PluginManager()
4083 >>> plugins.me.param1=3
4084
4085 ### within the plugin model
4086 >>> _ = PluginManager('me',param1=5,param2=6,param3=7)
4087
4088 ### where the plugin is used
4089 >>> print plugins.me.param1
4090 3
4091 >>> print plugins.me.param2
4092 6
4093 >>> plugins.me.param3 = 8
4094 >>> print plugins.me.param3
4095 8
4096
4097 Here are some tests:
4098
4099 >>> a=PluginManager()
4100 >>> a.x=6
4101 >>> b=PluginManager('check')
4102 >>> print b.x
4103 6
4104 >>> b=PluginManager() # reset settings
4105 >>> print b.x
4106 <Storage {}>
4107 >>> b.x=7
4108 >>> print a.x
4109 7
4110 >>> a.y.z=8
4111 >>> print b.y.z
4112 8
4113 >>> test_thread_separation()
4114 5
4115 >>> plugins=PluginManager('me',db='mydb')
4116 >>> print plugins.me.db
4117 mydb
4118 >>> print 'me' in plugins
4119 True
4120 >>> print plugins.me.installed
4121 True
4122 """
4123 instances = {}
4137 - def __init__(self,plugin=None,**defaults):
4145 if not key in self.__dict__:
4146 self.__dict__[key] = Storage()
4147 return self.__dict__[key]
4149 return self.__dict__.keys()
4151 return key in self.__dict__
4152
4155 current.session.forget()
4156 base = base or os.path.join(current.request.folder,'static')
4157 args = self.args = current.request.raw_args and \
4158 current.request.raw_args.split('/') or []
4159 filename = os.path.join(base,*args)
4160 if not os.path.normpath(filename).startswith(base):
4161 raise HTTP(401,"NOT AUTHORIZED")
4162 if not os.path.isdir(filename):
4163 current.response.headers['Content-Type'] = contenttype(filename)
4164 raise HTTP(200,open(filename,'rb'),**current.response.headers)
4165 self.path = path = os.path.join(filename,'*')
4166 self.folders = [f[len(path)-1:] for f in sorted(glob.glob(path)) \
4167 if os.path.isdir(f) and not self.isprivate(f)]
4168 self.filenames = [f[len(path)-1:] for f in sorted(glob.glob(path)) \
4169 if not os.path.isdir(f) and not self.isprivate(f)]
4183 return TABLE(*[TR(TD(A(folder,_href=URL(args=self.args+[folder])))) \
4184 for folder in self.folders])
4185 @staticmethod
4188 @staticmethod
4190 return f.rsplit('.')[-1].lower() in ('png','jpg','jpeg','gif','tiff')
4192 return TABLE(*[TR(TD(A(f,_href=URL(args=self.args+[f]))),
4193 TD(IMG(_src=URL(args=self.args+[f]),
4194 _style='max-width:%spx' % width) \
4195 if width and self.isimage(f) else '')) \
4196 for f in self.filenames])
4204
4205 if __name__ == '__main__':
4206 import doctest
4207 doctest.testmod()
4208