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