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