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_MATCH('[\w\.\-]+'),
1176 IS_NOT_IN_DB(db, table.username))
1177 else:
1178 table = db.define_table(
1179 settings.table_user_name,
1180 Field('first_name', length=128, default='',
1181 label=self.messages.label_first_name),
1182 Field('last_name', length=128, default='',
1183 label=self.messages.label_last_name),
1184 Field('email', length=512, default='',
1185 label=self.messages.label_email),
1186 Field(passfield, 'password', length=512,
1187 readable=False, label=self.messages.label_password),
1188 Field('registration_key', length=512,
1189 writable=False, readable=False, default='',
1190 label=self.messages.label_registration_key),
1191 Field('reset_password_key', length=512,
1192 writable=False, readable=False, default='',
1193 label=self.messages.label_reset_password_key),
1194 *settings.extra_fields.get(settings.table_user_name,[]),
1195 **dict(
1196 migrate=self.__get_migrate(settings.table_user_name,
1197 migrate),
1198 fake_migrate=fake_migrate,
1199 format='%(first_name)s %(last_name)s (%(id)s)'))
1200 table.first_name.requires = \
1201 IS_NOT_EMPTY(error_message=self.messages.is_empty)
1202 table.last_name.requires = \
1203 IS_NOT_EMPTY(error_message=self.messages.is_empty)
1204 table[passfield].requires = [CRYPT(key=settings.hmac_key)]
1205 table.email.requires = \
1206 [IS_EMAIL(error_message=self.messages.invalid_email),
1207 IS_NOT_IN_DB(db, table.email)]
1208 table.registration_key.default = ''
1209 settings.table_user = db[settings.table_user_name]
1210 if not settings.table_group_name in db.tables:
1211 table = db.define_table(
1212 settings.table_group_name,
1213 Field('role', length=512, default='',
1214 label=self.messages.label_role),
1215 Field('description', 'text',
1216 label=self.messages.label_description),
1217 *settings.extra_fields.get(settings.table_group_name,[]),
1218 **dict(
1219 migrate=self.__get_migrate(
1220 settings.table_group_name, migrate),
1221 fake_migrate=fake_migrate,
1222 format = '%(role)s (%(id)s)'))
1223 table.role.requires = IS_NOT_IN_DB(db, '%s.role'
1224 % settings.table_group_name)
1225 settings.table_group = db[settings.table_group_name]
1226 if not settings.table_membership_name in db.tables:
1227 table = db.define_table(
1228 settings.table_membership_name,
1229 Field('user_id', settings.table_user,
1230 label=self.messages.label_user_id),
1231 Field('group_id', settings.table_group,
1232 label=self.messages.label_group_id),
1233 *settings.extra_fields.get(settings.table_membership_name,[]),
1234 **dict(
1235 migrate=self.__get_migrate(
1236 settings.table_membership_name, migrate),
1237 fake_migrate=fake_migrate))
1238 table.user_id.requires = IS_IN_DB(db, '%s.id' %
1239 settings.table_user_name,
1240 '%(first_name)s %(last_name)s (%(id)s)')
1241 table.group_id.requires = IS_IN_DB(db, '%s.id' %
1242 settings.table_group_name,
1243 '%(role)s (%(id)s)')
1244 settings.table_membership = db[settings.table_membership_name]
1245 if not settings.table_permission_name in db.tables:
1246 table = db.define_table(
1247 settings.table_permission_name,
1248 Field('group_id', settings.table_group,
1249 label=self.messages.label_group_id),
1250 Field('name', default='default', length=512,
1251 label=self.messages.label_name),
1252 Field('table_name', length=512,
1253 label=self.messages.label_table_name),
1254 Field('record_id', 'integer',default=0,
1255 label=self.messages.label_record_id),
1256 *settings.extra_fields.get(settings.table_permission_name,[]),
1257 **dict(
1258 migrate=self.__get_migrate(
1259 settings.table_permission_name, migrate),
1260 fake_migrate=fake_migrate))
1261 table.group_id.requires = IS_IN_DB(db, '%s.id' %
1262 settings.table_group_name,
1263 '%(role)s (%(id)s)')
1264 table.name.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1265 table.table_name.requires = IS_EMPTY_OR(IS_IN_SET(self.db.tables))
1266 table.record_id.requires = IS_INT_IN_RANGE(0, 10 ** 9)
1267 settings.table_permission = db[settings.table_permission_name]
1268 if not settings.table_event_name in db.tables:
1269 table = db.define_table(
1270 settings.table_event_name,
1271 Field('time_stamp', 'datetime',
1272 default=current.request.now,
1273 label=self.messages.label_time_stamp),
1274 Field('client_ip',
1275 default=current.request.client,
1276 label=self.messages.label_client_ip),
1277 Field('user_id', settings.table_user, default=None,
1278 label=self.messages.label_user_id),
1279 Field('origin', default='auth', length=512,
1280 label=self.messages.label_origin),
1281 Field('description', 'text', default='',
1282 label=self.messages.label_description),
1283 *settings.extra_fields.get(settings.table_event_name,[]),
1284 **dict(
1285 migrate=self.__get_migrate(
1286 settings.table_event_name, migrate),
1287 fake_migrate=fake_migrate))
1288 table.user_id.requires = IS_IN_DB(db, '%s.id' %
1289 settings.table_user_name,
1290 '%(first_name)s %(last_name)s (%(id)s)')
1291 table.origin.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1292 table.description.requires = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1293 settings.table_event = db[settings.table_event_name]
1294 now = current.request.now
1295 if settings.cas_domains:
1296 if not settings.table_cas_name in db.tables:
1297 table = db.define_table(
1298 settings.table_cas_name,
1299 Field('user_id', settings.table_user, default=None,
1300 label=self.messages.label_user_id),
1301 Field('created_on','datetime',default=now),
1302 Field('url',requires=IS_URL()),
1303 Field('uuid'),
1304 *settings.extra_fields.get(settings.table_cas_name,[]),
1305 **dict(
1306 migrate=self.__get_migrate(
1307 settings.table_event_name, migrate),
1308 fake_migrate=fake_migrate))
1309 table.user_id.requires = IS_IN_DB(db, '%s.id' % \
1310 settings.table_user_name,
1311 '%(first_name)s %(last_name)s (%(id)s)')
1312 settings.table_cas = db[settings.table_cas_name]
1313 if settings.cas_provider:
1314 settings.actions_disabled = \
1315 ['profile','register','change_password','request_reset_password']
1316 from gluon.contrib.login_methods.cas_auth import CasAuth
1317 maps = dict((name,lambda v,n=name:v.get(n,None)) for name in \
1318 settings.table_user.fields if name!='id' \
1319 and settings.table_user[name].readable)
1320 maps['registration_id'] = \
1321 lambda v,p=settings.cas_provider:'%s/%s' % (p,v['user'])
1322 settings.login_form = CasAuth(
1323 casversion = 2,
1324 urlbase = settings.cas_provider,
1325 actions=['login','validate','logout'],
1326 maps=maps)
1327
1328
1329 - def log_event(self, description, origin='auth'):
1330 """
1331 usage::
1332
1333 auth.log_event(description='this happened', origin='auth')
1334 """
1335
1336 if self.is_logged_in():
1337 user_id = self.user.id
1338 else:
1339 user_id = None
1340 self.settings.table_event.insert(description=description,
1341 origin=origin, user_id=user_id)
1342
1344 """
1345 Used for alternate login methods:
1346 If the user exists already then password is updated.
1347 If the user doesn't yet exist, then they are created.
1348 """
1349 table_user = self.settings.table_user
1350 if 'registration_id' in table_user.fields() and \
1351 'registration_id' in keys:
1352 username = 'registration_id'
1353 elif 'username' in table_user.fields():
1354 username = 'username'
1355 elif 'email' in table_user.fields():
1356 username = 'email'
1357 else:
1358 raise SyntaxError, "user must have username or email"
1359 passfield = self.settings.password_field
1360 user = self.db(table_user[username] == keys[username]).select().first()
1361 keys['registration_key']=''
1362 if user:
1363 user.update_record(**keys)
1364 else:
1365 if not 'first_name' in keys and 'first_name' in table_user.fields:
1366 keys['first_name']=keys[username]
1367 user_id = table_user.insert(**keys)
1368 user = self.user = table_user[user_id]
1369 if self.settings.create_user_groups:
1370 group_id = self.add_group("user_%s" % user_id)
1371 self.add_membership(group_id, user_id)
1372 return user
1373
1375 if not self.settings.allow_basic_login:
1376 return False
1377 basic = current.request.env.http_authorization
1378 if not basic or not basic[:6].lower() == 'basic ':
1379 return False
1380 (username, password) = base64.b64decode(basic[6:]).split(':')
1381 return self.login_bare(username, password)
1382
1384 """
1385 logins user
1386 """
1387
1388 request = current.request
1389 session = current.session
1390 table_user = self.settings.table_user
1391 if self.settings.login_userfield:
1392 userfield = self.settings.login_userfield
1393 elif 'username' in table_user.fields:
1394 userfield = 'username'
1395 else:
1396 userfield = 'email'
1397 passfield = self.settings.password_field
1398 user = self.db(table_user[userfield] == username).select().first()
1399 password = table_user[passfield].validate(password)[0]
1400 if user:
1401 if not user.registration_key and user[passfield] == password:
1402 user = Storage(table_user._filter_fields(user, id=True))
1403 session.auth = Storage(user=user, last_visit=request.now,
1404 expiration=self.settings.expiration,
1405 hmac_key = web2py_uuid())
1406 self.user = user
1407 return user
1408 return False
1409
1418 request, session = current.request, current.session
1419 db, table = self.db, self.settings.table_cas
1420 session._cas_service = request.vars.service or session._cas_service
1421 if not request.env.http_host in self.settings.cas_domains or \
1422 not session._cas_service:
1423 raise HTTP(403,'not authorized')
1424 def allow_access():
1425 row = table(url=session._cas_service,user_id=self.user.id)
1426 if row:
1427 row.update_record(created_on=request.now)
1428 uuid = row.uuid
1429 else:
1430 uuid = web2py_uuid()
1431 table.insert(url=session._cas_service, user_id=self.user.id,
1432 uuid=uuid, created_on=request.now)
1433 url = session._cas_service
1434 del session._cas_service
1435 redirect(url+"?ticket="+uuid)
1436 if self.is_logged_in():
1437 allow_access()
1438 def cas_onaccept(form, onaccept=onaccept):
1439 if onaccept!=DEFAULT: onaccept(form)
1440 allow_access()
1441 return self.login(next,onvalidation,cas_onaccept,log)
1442
1443
1445 request = current.request
1446 db, table = self.db, self.settings.table_cas
1447 current.response.headers['Content-Type']='text'
1448 ticket = table(uuid=request.vars.ticket)
1449 url = request.env.path_info.rsplit('/',1)[0]
1450 if ticket:
1451 user = self.settings.table_user(ticket.user_id)
1452 fullname = user.first_name+' '+user.last_name
1453 if version==1:
1454 raise HTTP(200,'yes\n%s:%s:%s'%(user.id,user.email,fullname))
1455
1456 username = user.get('username',user.email)
1457 raise HTTP(200,'<?xml version="1.0" encoding="UTF-8"?>\n'+\
1458 TAG['cas:serviceResponse'](
1459 TAG['cas:authenticationSuccess'](
1460 TAG['cas:user'](username),
1461 *[TAG['cas:'+field.name](user[field.name]) \
1462 for field in self.settings.table_user \
1463 if field.readable]),
1464 **{'_xmlns:cas':'http://www.yale.edu/tp/cas'}).xml())
1465 if version==1:
1466 raise HTTP(200,'no\n')
1467
1468 raise HTTP(200,'<?xml version="1.0" encoding="UTF-8"?>\n'+\
1469 TAG['cas:serviceResponse'](
1470 TAG['cas:authenticationFailure'](
1471 'Ticket %s not recognized' % ticket,
1472 _code='INVALID TICKET'),
1473 **{'_xmlns:cas':'http://www.yale.edu/tp/cas'}).xml())
1474
1475
1483 """
1484 returns a login form
1485
1486 .. method:: Auth.login([next=DEFAULT [, onvalidation=DEFAULT
1487 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1488
1489 """
1490
1491 table_user = self.settings.table_user
1492 if self.settings.login_userfield:
1493 username = self.settings.login_userfield
1494 elif 'username' in table_user.fields:
1495 username = 'username'
1496 else:
1497 username = 'email'
1498 if 'username' in table_user.fields or not self.settings.login_email_validate:
1499 tmpvalidator = IS_NOT_EMPTY(error_message=self.messages.is_empty)
1500 else:
1501 tmpvalidator = IS_EMAIL(error_message=self.messages.invalid_email)
1502 old_requires = table_user[username].requires
1503 table_user[username].requires = tmpvalidator
1504
1505 request = current.request
1506 response = current.response
1507 session = current.session
1508
1509 passfield = self.settings.password_field
1510 if next == DEFAULT:
1511 next = request.get_vars._next \
1512 or request.post_vars._next \
1513 or self.settings.login_next
1514 if onvalidation == DEFAULT:
1515 onvalidation = self.settings.login_onvalidation
1516 if onaccept == DEFAULT:
1517 onaccept = self.settings.login_onaccept
1518 if log == DEFAULT:
1519 log = self.messages.login_log
1520
1521 user = None
1522
1523
1524 if self.settings.login_form == self:
1525 form = SQLFORM(
1526 table_user,
1527 fields=[username, passfield],
1528 hidden=dict(_next=next),
1529 showid=self.settings.showid,
1530 submit_button=self.messages.submit_button,
1531 delete_label=self.messages.delete_label,
1532 formstyle=self.settings.formstyle
1533 )
1534
1535 if self.settings.remember_me_form:
1536
1537 addrow(form,XML(" "),
1538 DIV(XML(" "),
1539 INPUT(_type='checkbox',
1540 _class='checkbox',
1541 _id="auth_user_remember",
1542 _name="remember",
1543 ),
1544 XML(" "),
1545 LABEL(
1546 self.messages.label_remember_me,
1547 _for="auth_user_remember",
1548 )),"",
1549 self.settings.formstyle,
1550 'auth_user_remember__row')
1551
1552 captcha = self.settings.login_captcha or \
1553 (self.settings.login_captcha!=False and self.settings.captcha)
1554 if captcha:
1555 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row')
1556 accepted_form = False
1557
1558 if form.accepts(request, session,
1559 formname='login', dbio=False,
1560 onvalidation=onvalidation,
1561 hideerror=self.settings.hideerror):
1562
1563 accepted_form = True
1564
1565 user = self.db(table_user[username] == form.vars[username]).select().first()
1566 if user:
1567
1568 temp_user = user
1569 if temp_user.registration_key == 'pending':
1570 response.flash = self.messages.registration_pending
1571 return form
1572 elif temp_user.registration_key in ('disabled','blocked'):
1573 response.flash = self.messages.login_disabled
1574 return form
1575 elif temp_user.registration_key!=None and \
1576 temp_user.registration_key.strip():
1577 response.flash = \
1578 self.messages.registration_verifying
1579 return form
1580
1581
1582 user = None
1583 for login_method in self.settings.login_methods:
1584 if login_method != self and \
1585 login_method(request.vars[username],
1586 request.vars[passfield]):
1587 if not self in self.settings.login_methods:
1588
1589 form.vars[passfield] = None
1590 user = self.get_or_create_user(form.vars)
1591 break
1592 if not user:
1593
1594 if self.settings.login_methods[0] == self:
1595
1596 if temp_user[passfield] == form.vars.get(passfield, ''):
1597
1598 user = temp_user
1599 else:
1600
1601 if not self.settings.alternate_requires_registration:
1602
1603 for login_method in self.settings.login_methods:
1604 if login_method != self and \
1605 login_method(request.vars[username],
1606 request.vars[passfield]):
1607 if not self in self.settings.login_methods:
1608
1609 form.vars[passfield] = None
1610 user = self.get_or_create_user(form.vars)
1611 break
1612 if not user:
1613 if self.settings.login_failed_log:
1614 self.log_event(self.settings.login_failed_log % request.post_vars)
1615
1616 session.flash = self.messages.invalid_login
1617 redirect(self.url(args=request.args,vars=request.get_vars))
1618
1619 else:
1620
1621 cas = self.settings.login_form
1622 cas_user = cas.get_user()
1623
1624 if cas_user:
1625 cas_user[passfield] = None
1626 user = self.get_or_create_user(table_user._filter_fields(cas_user))
1627 elif hasattr(cas,'login_form'):
1628 return cas.login_form()
1629 else:
1630
1631 next = self.url('user',args='login',vars=dict(_next=next))
1632 redirect(cas.login_url(next))
1633
1634
1635
1636 if user:
1637 user = Storage(table_user._filter_fields(user, id=True))
1638
1639 if request.vars.has_key("remember"):
1640
1641 session.auth = Storage(
1642 user = user,
1643 last_visit = request.now,
1644 expiration = self.settings.long_expiration,
1645 remember = True,
1646 hmac_key = web2py_uuid()
1647 )
1648 else:
1649
1650 session.auth = Storage(
1651 user = user,
1652 last_visit = request.now,
1653 expiration = self.settings.expiration,
1654 remember = False,
1655 hmac_key = web2py_uuid()
1656 )
1657
1658 self.user = user
1659 callback(onaccept,None)
1660 session.flash = self.messages.logged_in
1661 if log and self.user:
1662 self.log_event(log % self.user)
1663
1664
1665 if self.settings.login_form == self:
1666 if accepted_form:
1667 callback(onaccept,form)
1668 if isinstance(next, (list, tuple)):
1669
1670 next = next[0]
1671 if next and not next[0] == '/' and next[:4] != 'http':
1672 next = self.url(next.replace('[id]', str(form.vars.id)))
1673 redirect(next)
1674 table_user[username].requires = old_requires
1675 return form
1676 else:
1677 redirect(next)
1678
1680 """
1681 logout and redirects to login
1682
1683 .. method:: Auth.logout ([next=DEFAULT[, onlogout=DEFAULT[,
1684 log=DEFAULT]]])
1685
1686 """
1687
1688 if next == DEFAULT:
1689 next = self.settings.logout_next
1690 if onlogout == DEFAULT:
1691 onlogout = self.settings.logout_onlogout
1692 if onlogout:
1693 onlogout(self.user)
1694 if log == DEFAULT:
1695 log = self.messages.logout_log
1696 if log and self.user:
1697 self.log_event(log % self.user)
1698
1699 if self.settings.login_form != self:
1700 cas = self.settings.login_form
1701 cas_user = cas.get_user()
1702 if cas_user:
1703 next = cas.logout_url(next)
1704
1705 current.session.auth = None
1706 current.session.flash = self.messages.logged_out
1707 if next:
1708 redirect(next)
1709
1717 """
1718 returns a registration form
1719
1720 .. method:: Auth.register([next=DEFAULT [, onvalidation=DEFAULT
1721 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1722
1723 """
1724
1725 table_user = self.settings.table_user
1726 request = current.request
1727 response = current.response
1728 session = current.session
1729 if self.is_logged_in():
1730 redirect(self.settings.logged_url)
1731 if next == DEFAULT:
1732 next = request.get_vars._next \
1733 or request.post_vars._next \
1734 or self.settings.register_next
1735 if onvalidation == DEFAULT:
1736 onvalidation = self.settings.register_onvalidation
1737 if onaccept == DEFAULT:
1738 onaccept = self.settings.register_onaccept
1739 if log == DEFAULT:
1740 log = self.messages.register_log
1741
1742 passfield = self.settings.password_field
1743 formstyle = self.settings.formstyle
1744 form = SQLFORM(table_user,
1745 fields = self.settings.register_fields,
1746 hidden=dict(_next=next),
1747 showid=self.settings.showid,
1748 submit_button=self.messages.submit_button,
1749 delete_label=self.messages.delete_label,
1750 formstyle=formstyle
1751 )
1752 for i, row in enumerate(form[0].components):
1753 item = row.element('input',_name=passfield)
1754 if item:
1755 form.custom.widget.password_two = \
1756 INPUT(_name="password_two", _type="password",
1757 requires=IS_EXPR('value==%s' % \
1758 repr(request.vars.get(passfield, None)),
1759 error_message=self.messages.mismatched_password))
1760
1761 addrow(form, self.messages.verify_password + ':',
1762 form.custom.widget.password_two,
1763 self.messages.verify_password_comment,
1764 formstyle,
1765 '%s_%s__row' % (table_user, 'password_two'),
1766 position=i+1)
1767 break
1768 captcha = self.settings.register_captcha or self.settings.captcha
1769 if captcha:
1770 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row')
1771
1772 table_user.registration_key.default = key = web2py_uuid()
1773 if form.accepts(request, session, formname='register',
1774 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1775 description = self.messages.group_description % form.vars
1776 if self.settings.create_user_groups:
1777 group_id = self.add_group("user_%s" % form.vars.id, description)
1778 self.add_membership(group_id, form.vars.id)
1779 if self.settings.registration_requires_verification:
1780 if not self.settings.mailer or \
1781 not self.settings.mailer.send(to=form.vars.email,
1782 subject=self.messages.verify_email_subject,
1783 message=self.messages.verify_email
1784 % dict(key=key)):
1785 self.db.rollback()
1786 response.flash = self.messages.unable_send_email
1787 return form
1788 session.flash = self.messages.email_sent
1789 elif self.settings.registration_requires_approval:
1790 table_user[form.vars.id] = dict(registration_key='pending')
1791 session.flash = self.messages.registration_pending
1792 else:
1793 table_user[form.vars.id] = dict(registration_key='')
1794 session.flash = self.messages.registration_successful
1795 table_user = self.settings.table_user
1796 if 'username' in table_user.fields:
1797 username = 'username'
1798 else:
1799 username = 'email'
1800 user = self.db(table_user[username] == form.vars[username]).select().first()
1801 user = Storage(table_user._filter_fields(user, id=True))
1802 session.auth = Storage(user=user, last_visit=request.now,
1803 expiration=self.settings.expiration,
1804 hmac_key = web2py_uuid())
1805 self.user = user
1806 session.flash = self.messages.logged_in
1807 if log:
1808 self.log_event(log % form.vars)
1809 callback(onaccept,form)
1810 if not next:
1811 next = self.url(args = request.args)
1812 elif isinstance(next, (list, tuple)):
1813 next = next[0]
1814 elif next and not next[0] == '/' and next[:4] != 'http':
1815 next = self.url(next.replace('[id]', str(form.vars.id)))
1816 redirect(next)
1817 return form
1818
1820 """
1821 checks if the user is logged in and returns True/False.
1822 if so user is in auth.user as well as in session.auth.user
1823 """
1824
1825 if self.user:
1826 return True
1827 return False
1828
1835 """
1836 action user to verify the registration email, XXXXXXXXXXXXXXXX
1837
1838 .. method:: Auth.verify_email([next=DEFAULT [, onvalidation=DEFAULT
1839 [, onaccept=DEFAULT [, log=DEFAULT]]]])
1840
1841 """
1842
1843 key = current.request.args[-1]
1844 table_user = self.settings.table_user
1845 user = self.db(table_user.registration_key == key).select().first()
1846 if not user:
1847 raise HTTP(404)
1848 if self.settings.registration_requires_approval:
1849 user.update_record(registration_key = 'pending')
1850 current.session.flash = self.messages.registration_pending
1851 else:
1852 user.update_record(registration_key = '')
1853 current.session.flash = self.messages.email_verified
1854 if log == DEFAULT:
1855 log = self.messages.verify_email_log
1856 if next == DEFAULT:
1857 next = self.settings.verify_email_next
1858 if onaccept == DEFAULT:
1859 onaccept = self.settings.verify_email_onaccept
1860 if log:
1861 self.log_event(log % user)
1862 callback(onaccept,user)
1863 redirect(next)
1864
1872 """
1873 returns a form to retrieve the user username
1874 (only if there is a username field)
1875
1876 .. method:: Auth.retrieve_username([next=DEFAULT
1877 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1878
1879 """
1880
1881 table_user = self.settings.table_user
1882 if not 'username' in table_user.fields:
1883 raise HTTP(404)
1884 request = current.request
1885 response = current.response
1886 session = current.session
1887 captcha = self.settings.retrieve_username_captcha or \
1888 (self.settings.retrieve_username_captcha!=False and self.settings.captcha)
1889 if not self.settings.mailer:
1890 response.flash = self.messages.function_disabled
1891 return ''
1892 if next == DEFAULT:
1893 next = request.get_vars._next \
1894 or request.post_vars._next \
1895 or self.settings.retrieve_username_next
1896 if onvalidation == DEFAULT:
1897 onvalidation = self.settings.retrieve_username_onvalidation
1898 if onaccept == DEFAULT:
1899 onaccept = self.settings.retrieve_username_onaccept
1900 if log == DEFAULT:
1901 log = self.messages.retrieve_username_log
1902 old_requires = table_user.email.requires
1903 table_user.email.requires = [IS_IN_DB(self.db, table_user.email,
1904 error_message=self.messages.invalid_email)]
1905 form = SQLFORM(table_user,
1906 fields=['email'],
1907 hidden=dict(_next=next),
1908 showid=self.settings.showid,
1909 submit_button=self.messages.submit_button,
1910 delete_label=self.messages.delete_label,
1911 formstyle=self.settings.formstyle
1912 )
1913 if captcha:
1914 addrow(form, captcha.label, captcha, captcha.comment,self.settings.formstyle, 'captcha__row')
1915
1916 if form.accepts(request, session,
1917 formname='retrieve_username', dbio=False,
1918 onvalidation=onvalidation,hideerror=self.settings.hideerror):
1919 user = self.db(table_user.email == form.vars.email).select().first()
1920 if not user:
1921 current.session.flash = \
1922 self.messages.invalid_email
1923 redirect(self.url(args=request.args))
1924 username = user.username
1925 self.settings.mailer.send(to=form.vars.email,
1926 subject=self.messages.retrieve_username_subject,
1927 message=self.messages.retrieve_username
1928 % dict(username=username))
1929 session.flash = self.messages.email_sent
1930 if log:
1931 self.log_event(log % user)
1932 callback(onaccept,form)
1933 if not next:
1934 next = self.url(args = request.args)
1935 elif isinstance(next, (list, tuple)):
1936 next = next[0]
1937 elif next and not next[0] == '/' and next[:4] != 'http':
1938 next = self.url(next.replace('[id]', str(form.vars.id)))
1939 redirect(next)
1940 table_user.email.requires = old_requires
1941 return form
1942
1944 import string
1945 import random
1946 password = ''
1947 specials=r'!#$*'
1948 for i in range(0,3):
1949 password += random.choice(string.lowercase)
1950 password += random.choice(string.uppercase)
1951 password += random.choice(string.digits)
1952 password += random.choice(specials)
1953 return ''.join(random.sample(password,len(password)))
1954
1962 """
1963 returns a form to reset the user password (deprecated)
1964
1965 .. method:: Auth.reset_password_deprecated([next=DEFAULT
1966 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
1967
1968 """
1969
1970 table_user = self.settings.table_user
1971 request = current.request
1972 response = current.response
1973 session = current.session
1974 if not self.settings.mailer:
1975 response.flash = self.messages.function_disabled
1976 return ''
1977 if next == DEFAULT:
1978 next = request.get_vars._next \
1979 or request.post_vars._next \
1980 or self.settings.retrieve_password_next
1981 if onvalidation == DEFAULT:
1982 onvalidation = self.settings.retrieve_password_onvalidation
1983 if onaccept == DEFAULT:
1984 onaccept = self.settings.retrieve_password_onaccept
1985 if log == DEFAULT:
1986 log = self.messages.retrieve_password_log
1987 old_requires = table_user.email.requires
1988 table_user.email.requires = [IS_IN_DB(self.db, table_user.email,
1989 error_message=self.messages.invalid_email)]
1990 form = SQLFORM(table_user,
1991 fields=['email'],
1992 hidden=dict(_next=next),
1993 showid=self.settings.showid,
1994 submit_button=self.messages.submit_button,
1995 delete_label=self.messages.delete_label,
1996 formstyle=self.settings.formstyle
1997 )
1998 if form.accepts(request, session,
1999 formname='retrieve_password', dbio=False,
2000 onvalidation=onvalidation,hideerror=self.settings.hideerror):
2001 user = self.db(table_user.email == form.vars.email).select().first()
2002 if not user:
2003 current.session.flash = \
2004 self.messages.invalid_email
2005 redirect(self.url(args=request.args))
2006 elif user.registration_key in ('pending','disabled','blocked'):
2007 current.session.flash = \
2008 self.messages.registration_pending
2009 redirect(self.url(args=request.args))
2010 password = self.random_password()
2011 passfield = self.settings.password_field
2012 d = {passfield: table_user[passfield].validate(password)[0],
2013 'registration_key': ''}
2014 user.update_record(**d)
2015 if self.settings.mailer and \
2016 self.settings.mailer.send(to=form.vars.email,
2017 subject=self.messages.retrieve_password_subject,
2018 message=self.messages.retrieve_password \
2019 % dict(password=password)):
2020 session.flash = self.messages.email_sent
2021 else:
2022 session.flash = self.messages.unable_to_send_email
2023 if log:
2024 self.log_event(log % user)
2025 callback(onaccept,form)
2026 if not next:
2027 next = self.url(args = request.args)
2028 elif isinstance(next, (list, tuple)):
2029 next = next[0]
2030 elif next and not next[0] == '/' and next[:4] != 'http':
2031 next = self.url(next.replace('[id]', str(form.vars.id)))
2032 redirect(next)
2033 table_user.email.requires = old_requires
2034 return form
2035
2043 """
2044 returns a form to reset the user password
2045
2046 .. method:: Auth.reset_password([next=DEFAULT
2047 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
2048
2049 """
2050
2051 table_user = self.settings.table_user
2052 request = current.request
2053
2054 session = current.session
2055
2056 if next == DEFAULT:
2057 next = request.get_vars._next \
2058 or request.post_vars._next \
2059 or self.settings.reset_password_next
2060
2061 try:
2062 key = request.vars.key or request.args[-1]
2063 t0 = int(key.split('-')[0])
2064 if time.time()-t0 > 60*60*24: raise Exception
2065 user = self.db(table_user.reset_password_key == key).select().first()
2066 if not user: raise Exception
2067 except Exception:
2068 session.flash = self.messages.invalid_reset_password
2069 redirect(next)
2070 passfield = self.settings.password_field
2071 form = SQLFORM.factory(
2072 Field('new_password', 'password',
2073 label=self.messages.new_password,
2074 requires=self.settings.table_user[passfield].requires),
2075 Field('new_password2', 'password',
2076 label=self.messages.verify_password,
2077 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
2078 self.messages.mismatched_password)]),
2079 submit_button=self.messages.submit_button,
2080 formstyle=self.settings.formstyle,
2081 )
2082 if form.accepts(request,session,hideerror=self.settings.hideerror):
2083 user.update_record(**{passfield:form.vars.new_password,
2084 'registration_key':'',
2085 'reset_password_key':''})
2086 session.flash = self.messages.password_changed
2087 redirect(next)
2088 return form
2089
2097 """
2098 returns a form to reset the user password
2099
2100 .. method:: Auth.reset_password([next=DEFAULT
2101 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT]]]])
2102
2103 """
2104
2105 table_user = self.settings.table_user
2106 request = current.request
2107 response = current.response
2108 session = current.session
2109 captcha = self.settings.retrieve_password_captcha or \
2110 (self.settings.retrieve_password_captcha!=False and self.settings.captcha)
2111
2112 if next == DEFAULT:
2113 next = request.get_vars._next \
2114 or request.post_vars._next \
2115 or self.settings.request_reset_password_next
2116
2117 if not self.settings.mailer:
2118 response.flash = self.messages.function_disabled
2119 return ''
2120 if onvalidation == DEFAULT:
2121 onvalidation = self.settings.reset_password_onvalidation
2122 if onaccept == DEFAULT:
2123 onaccept = self.settings.reset_password_onaccept
2124 if log == DEFAULT:
2125 log = self.messages.reset_password_log
2126
2127 table_user.email.requires = [
2128 IS_EMAIL(error_message=self.messages.invalid_email),
2129 IS_IN_DB(self.db, table_user.email,
2130 error_message=self.messages.invalid_email)]
2131 form = SQLFORM(table_user,
2132 fields=['email'],
2133 hidden=dict(_next=next),
2134 showid=self.settings.showid,
2135 submit_button=self.messages.submit_button,
2136 delete_label=self.messages.delete_label,
2137 formstyle=self.settings.formstyle
2138 )
2139 if captcha:
2140 addrow(form, captcha.label, captcha, captcha.comment, self.settings.formstyle,'captcha__row')
2141 if form.accepts(request, session,
2142 formname='reset_password', dbio=False,
2143 onvalidation=onvalidation,
2144 hideerror=self.settings.hideerror):
2145 user = self.db(table_user.email == form.vars.email).select().first()
2146 if not user:
2147 session.flash = self.messages.invalid_email
2148 redirect(self.url(args=request.args))
2149 elif user.registration_key in ('pending','disabled','blocked'):
2150 session.flash = self.messages.registration_pending
2151 redirect(self.url(args=request.args))
2152 reset_password_key = str(int(time.time()))+'-' + web2py_uuid()
2153
2154 if self.settings.mailer.send(to=form.vars.email,
2155 subject=self.messages.reset_password_subject,
2156 message=self.messages.reset_password % \
2157 dict(key=reset_password_key)):
2158 session.flash = self.messages.email_sent
2159 user.update_record(reset_password_key=reset_password_key)
2160 else:
2161 session.flash = self.messages.unable_to_send_email
2162 if log:
2163 self.log_event(log % user)
2164 callback(onaccept,form)
2165 if not next:
2166 next = self.url(args = request.args)
2167 elif isinstance(next, (list, tuple)):
2168 next = next[0]
2169 elif next and not next[0] == '/' and next[:4] != 'http':
2170 next = self.url(next.replace('[id]', str(form.vars.id)))
2171 redirect(next)
2172
2173 return form
2174
2186
2194 """
2195 returns a form that lets the user change password
2196
2197 .. method:: Auth.change_password([next=DEFAULT[, onvalidation=DEFAULT[,
2198 onaccept=DEFAULT[, log=DEFAULT]]]])
2199 """
2200
2201 if not self.is_logged_in():
2202 redirect(self.settings.login_url)
2203 db = self.db
2204 table_user = self.settings.table_user
2205 usern = self.settings.table_user_name
2206 s = db(table_user.id == self.user.id)
2207
2208 request = current.request
2209 session = current.session
2210 if next == DEFAULT:
2211 next = request.get_vars._next \
2212 or request.post_vars._next \
2213 or self.settings.change_password_next
2214 if onvalidation == DEFAULT:
2215 onvalidation = self.settings.change_password_onvalidation
2216 if onaccept == DEFAULT:
2217 onaccept = self.settings.change_password_onaccept
2218 if log == DEFAULT:
2219 log = self.messages.change_password_log
2220 passfield = self.settings.password_field
2221 form = SQLFORM.factory(
2222 Field('old_password', 'password',
2223 label=self.messages.old_password,
2224 requires=validators(
2225 table_user[passfield].requires,
2226 IS_IN_DB(s, '%s.%s' % (usern, passfield),
2227 error_message=self.messages.invalid_password))),
2228 Field('new_password', 'password',
2229 label=self.messages.new_password,
2230 requires=table_user[passfield].requires),
2231 Field('new_password2', 'password',
2232 label=self.messages.verify_password,
2233 requires=[IS_EXPR('value==%s' % repr(request.vars.new_password),
2234 self.messages.mismatched_password)]),
2235 submit_button=self.messages.submit_button,
2236 formstyle = self.settings.formstyle
2237 )
2238 if form.accepts(request, session,
2239 formname='change_password',
2240 onvalidation=onvalidation,
2241 hideerror=self.settings.hideerror):
2242 d = {passfield: form.vars.new_password}
2243 s.update(**d)
2244 session.flash = self.messages.password_changed
2245 if log:
2246 self.log_event(log % self.user)
2247 callback(onaccept,form)
2248 if not next:
2249 next = self.url(args=request.args)
2250 elif isinstance(next, (list, tuple)):
2251 next = next[0]
2252 elif next and not next[0] == '/' and next[:4] != 'http':
2253 next = self.url(next.replace('[id]', str(form.vars.id)))
2254 redirect(next)
2255 return form
2256
2264 """
2265 returns a form that lets the user change his/her profile
2266
2267 .. method:: Auth.profile([next=DEFAULT [, onvalidation=DEFAULT
2268 [, onaccept=DEFAULT [, log=DEFAULT]]]])
2269
2270 """
2271
2272 table_user = self.settings.table_user
2273 if not self.is_logged_in():
2274 redirect(self.settings.login_url)
2275 passfield = self.settings.password_field
2276 self.settings.table_user[passfield].writable = False
2277 request = current.request
2278 session = current.session
2279 if next == DEFAULT:
2280 next = request.get_vars._next \
2281 or request.post_vars._next \
2282 or self.settings.profile_next
2283 if onvalidation == DEFAULT:
2284 onvalidation = self.settings.profile_onvalidation
2285 if onaccept == DEFAULT:
2286 onaccept = self.settings.profile_onaccept
2287 if log == DEFAULT:
2288 log = self.messages.profile_log
2289 form = SQLFORM(
2290 table_user,
2291 self.user.id,
2292 fields = self.settings.profile_fields,
2293 hidden = dict(_next=next),
2294 showid = self.settings.showid,
2295 submit_button = self.messages.submit_button,
2296 delete_label = self.messages.delete_label,
2297 upload = self.settings.download_url,
2298 formstyle = self.settings.formstyle
2299 )
2300 if form.accepts(request, session,
2301 formname='profile',
2302 onvalidation=onvalidation,hideerror=self.settings.hideerror):
2303 self.user.update(table_user._filter_fields(form.vars))
2304 session.flash = self.messages.profile_updated
2305 if log:
2306 self.log_event(log % self.user)
2307 callback(onaccept,form)
2308 if not next:
2309 next = self.url(args=request.args)
2310 elif isinstance(next, (list, tuple)):
2311 next = next[0]
2312 elif next and not next[0] == '/' and next[:4] != 'http':
2313 next = self.url(next.replace('[id]', str(form.vars.id)))
2314 redirect(next)
2315 return form
2316
2318 return current.session.auth.impersonator
2319
2321 """
2322 usage: POST TO http://..../impersonate request.post_vars.user_id=<id>
2323 set request.post_vars.user_id to 0 to restore original user.
2324
2325 requires impersonator is logged in and
2326 has_permission('impersonate', 'auth_user', user_id)
2327 """
2328 request = current.request
2329 session = current.session
2330 auth = session.auth
2331 if not self.is_logged_in():
2332 raise HTTP(401, "Not Authorized")
2333 current_id = auth.user.id
2334 requested_id = user_id
2335 if user_id == DEFAULT:
2336 user_id = current.request.post_vars.user_id
2337 if user_id and user_id != self.user.id and user_id != '0':
2338 if not self.has_permission('impersonate',
2339 self.settings.table_user_name,
2340 user_id):
2341 raise HTTP(403, "Forbidden")
2342 user = self.settings.table_user(user_id)
2343 if not user:
2344 raise HTTP(401, "Not Authorized")
2345 auth.impersonator = cPickle.dumps(session)
2346 auth.user.update(
2347 self.settings.table_user._filter_fields(user, True))
2348 self.user = auth.user
2349 if self.settings.login_onaccept:
2350 form = Storage(dict(vars=self.user))
2351 self.settings.login_onaccept(form)
2352 log = self.messages.impersonate_log
2353 if log:
2354 self.log_event(log % dict(id=current_id,other_id=auth.user.id))
2355 elif user_id in (0, '0') and self.is_impersonating():
2356 session.clear()
2357 session.update(cPickle.loads(auth.impersonator))
2358 self.user = session.auth.user
2359 if requested_id == DEFAULT and not request.post_vars:
2360 return SQLFORM.factory(Field('user_id','integer'))
2361 return self.user
2362
2364 """
2365 displays the groups and their roles for the logged in user
2366 """
2367
2368 if not self.is_logged_in():
2369 redirect(self.settings.login_url)
2370 memberships = self.db(self.settings.table_membership.user_id
2371 == self.user.id).select()
2372 table = TABLE()
2373 for membership in memberships:
2374 groups = self.db(self.settings.table_group.id
2375 == membership.group_id).select()
2376 if groups:
2377 group = groups[0]
2378 table.append(TR(H3(group.role, '(%s)' % group.id)))
2379 table.append(TR(P(group.description)))
2380 if not memberships:
2381 return None
2382 return table
2383
2385 """
2386 you can change the view for this page to make it look as you like
2387 """
2388
2389 return 'ACCESS DENIED'
2390
2392 """
2393 decorator that prevents access to action if not logged in
2394 """
2395
2396 def decorator(action):
2397
2398 def f(*a, **b):
2399
2400 if self.settings.allow_basic_login_only and not self.basic():
2401 if current.request.is_restful:
2402 raise HTTP(403,"Not authorized")
2403 return call_or_redirect(self.settings.on_failed_authorization)
2404
2405 if not condition:
2406 if current.request.is_restful:
2407 raise HTTP(403,"Not authorized")
2408 if not self.basic() and not self.is_logged_in():
2409 request = current.request
2410 next = URL(r=request,args=request.args,
2411 vars=request.get_vars)
2412 current.session.flash = current.response.flash
2413 return call_or_redirect(
2414 self.settings.on_failed_authentication,
2415 self.settings.login_url + '?_next='+urllib.quote(next))
2416 else:
2417 current.session.flash = self.messages.access_denied
2418 return call_or_redirect(self.settings.on_failed_authorization)
2419 return action(*a, **b)
2420 f.__doc__ = action.__doc__
2421 f.__name__ = action.__name__
2422 f.__dict__.update(action.__dict__)
2423 return f
2424
2425 return decorator
2426
2428 """
2429 decorator that prevents access to action if not logged in
2430 """
2431
2432 def decorator(action):
2433
2434 def f(*a, **b):
2435
2436 if self.settings.allow_basic_login_only and not self.basic():
2437 if current.request.is_restful:
2438 raise HTTP(403,"Not authorized")
2439 return call_or_redirect(self.settings.on_failed_authorization)
2440
2441 if not self.basic() and not self.is_logged_in():
2442 if current.request.is_restful:
2443 raise HTTP(403,"Not authorized")
2444 request = current.request
2445 next = URL(r=request,args=request.args,
2446 vars=request.get_vars)
2447 current.session.flash = current.response.flash
2448 return call_or_redirect(
2449 self.settings.on_failed_authentication,
2450 self.settings.login_url + '?_next='+urllib.quote(next)
2451 )
2452 return action(*a, **b)
2453 f.__doc__ = action.__doc__
2454 f.__name__ = action.__name__
2455 f.__dict__.update(action.__dict__)
2456 return f
2457
2458 return decorator
2459
2461 """
2462 decorator that prevents access to action if not logged in or
2463 if user logged in is not a member of group_id.
2464 If role is provided instead of group_id then the
2465 group_id is calculated.
2466 """
2467
2468 def decorator(action):
2469 def f(*a, **b):
2470 if self.settings.allow_basic_login_only and not self.basic():
2471 if current.request.is_restful:
2472 raise HTTP(403,"Not authorized")
2473 return call_or_redirect(self.settings.on_failed_authorization)
2474
2475 if not self.basic() and not self.is_logged_in():
2476 if current.request.is_restful:
2477 raise HTTP(403,"Not authorized")
2478 request = current.request
2479 next = URL(r=request,args=request.args,
2480 vars=request.get_vars)
2481 current.session.flash = current.response.flash
2482 return call_or_redirect(
2483 self.settings.on_failed_authentication,
2484 self.settings.login_url + '?_next='+urllib.quote(next)
2485 )
2486 if not self.has_membership(group_id=group_id, role=role):
2487 current.session.flash = self.messages.access_denied
2488 return call_or_redirect(self.settings.on_failed_authorization)
2489 return action(*a, **b)
2490 f.__doc__ = action.__doc__
2491 f.__name__ = action.__name__
2492 f.__dict__.update(action.__dict__)
2493 return f
2494
2495 return decorator
2496
2497
2504 """
2505 decorator that prevents access to action if not logged in or
2506 if user logged in is not a member of any group (role) that
2507 has 'name' access to 'table_name', 'record_id'.
2508 """
2509
2510 def decorator(action):
2511
2512 def f(*a, **b):
2513 if self.settings.allow_basic_login_only and not self.basic():
2514 if current.request.is_restful:
2515 raise HTTP(403,"Not authorized")
2516 return call_or_redirect(self.settings.on_failed_authorization)
2517
2518 if not self.basic() and not self.is_logged_in():
2519 if current.request.is_restful:
2520 raise HTTP(403,"Not authorized")
2521 request = current.request
2522 next = URL(r=request,args=request.args,
2523 vars=request.get_vars)
2524 current.session.flash = current.response.flash
2525 return call_or_redirect(
2526 self.settings.on_failed_authentication,
2527 self.settings.login_url + '?_next='+urllib.quote(next)
2528 )
2529 if not self.has_permission(name, table_name, record_id):
2530 current.session.flash = self.messages.access_denied
2531 return call_or_redirect(self.settings.on_failed_authorization)
2532 return action(*a, **b)
2533 f.__doc__ = action.__doc__
2534 f.__name__ = action.__name__
2535 f.__dict__.update(action.__dict__)
2536 return f
2537
2538 return decorator
2539
2541 """
2542 decorator that prevents access to action if not logged in or
2543 if user logged in is not a member of group_id.
2544 If role is provided instead of group_id then the
2545 group_id is calculated.
2546 """
2547
2548 def decorator(action):
2549 def f(*a, **b):
2550 if self.settings.allow_basic_login_only and not self.basic():
2551 if current.request.is_restful:
2552 raise HTTP(403,"Not authorized")
2553 return call_or_redirect(self.settings.on_failed_authorization)
2554
2555 if not self.basic() and not self.is_logged_in():
2556 if current.request.is_restful:
2557 raise HTTP(403,"Not authorized")
2558 request = current.request
2559 next = URL(r=request,args=request.args,
2560 vars=request.get_vars)
2561 current.session.flash = current.response.flash
2562 return call_or_redirect(
2563 self.settings.on_failed_authentication,
2564 self.settings.login_url + '?_next='+urllib.quote(next)
2565 )
2566 if not URL.verify(current.request,user_signature=True):
2567 current.session.flash = self.messages.access_denied
2568 return call_or_redirect(self.settings.on_failed_authorization)
2569 return action(*a, **b)
2570 f.__doc__ = action.__doc__
2571 f.__name__ = action.__name__
2572 f.__dict__.update(action.__dict__)
2573 return f
2574
2575 return decorator
2576
2578 """
2579 creates a group associated to a role
2580 """
2581
2582 group_id = self.settings.table_group.insert(role=role,
2583 description=description)
2584 log = self.messages.add_group_log
2585 if log:
2586 self.log_event(log % dict(group_id=group_id, role=role))
2587 return group_id
2588
2590 """
2591 deletes a group
2592 """
2593
2594 self.db(self.settings.table_group.id == group_id).delete()
2595 self.db(self.settings.table_membership.group_id
2596 == group_id).delete()
2597 self.db(self.settings.table_permission.group_id
2598 == group_id).delete()
2599 log = self.messages.del_group_log
2600 if log:
2601 self.log_event(log % dict(group_id=group_id))
2602
2604 """
2605 returns the group_id of the group specified by the role
2606 """
2607 rows = self.db(self.settings.table_group.role == role).select()
2608 if not rows:
2609 return None
2610 return rows[0].id
2611
2613 """
2614 returns the group_id of the group uniquely associated to this user
2615 i.e. role=user:[user_id]
2616 """
2617 if not user_id and self.user:
2618 user_id = self.user.id
2619 role = 'user_%s' % user_id
2620 return self.id_group(role)
2621
2623 """
2624 checks if user is member of group_id or role
2625 """
2626
2627 group_id = group_id or self.id_group(role)
2628 try:
2629 group_id = int(group_id)
2630 except:
2631 group_id = self.id_group(group_id)
2632 if not user_id and self.user:
2633 user_id = self.user.id
2634 membership = self.settings.table_membership
2635 if self.db((membership.user_id == user_id)
2636 & (membership.group_id == group_id)).select():
2637 r = True
2638 else:
2639 r = False
2640 log = self.messages.has_membership_log
2641 if log:
2642 self.log_event(log % dict(user_id=user_id,
2643 group_id=group_id, check=r))
2644 return r
2645
2647 """
2648 gives user_id membership of group_id or role
2649 if user_id==None than user_id is that of current logged in user
2650 """
2651
2652 group_id = group_id or self.id_group(role)
2653 try:
2654 group_id = int(group_id)
2655 except:
2656 group_id = self.id_group(group_id)
2657 if not user_id and self.user:
2658 user_id = self.user.id
2659 membership = self.settings.table_membership
2660 record = membership(user_id = user_id,group_id = group_id)
2661 if record:
2662 return record.id
2663 else:
2664 id = membership.insert(group_id=group_id, user_id=user_id)
2665 log = self.messages.add_membership_log
2666 if log:
2667 self.log_event(log % dict(user_id=user_id,
2668 group_id=group_id))
2669 return id
2670
2672 """
2673 revokes membership from group_id to user_id
2674 if user_id==None than user_id is that of current logged in user
2675 """
2676
2677 group_id = group_id or self.id_group(role)
2678 if not user_id and self.user:
2679 user_id = self.user.id
2680 membership = self.settings.table_membership
2681 log = self.messages.del_membership_log
2682 if log:
2683 self.log_event(log % dict(user_id=user_id,
2684 group_id=group_id))
2685 return self.db(membership.user_id
2686 == user_id)(membership.group_id
2687 == group_id).delete()
2688
2689 - def has_permission(
2690 self,
2691 name='any',
2692 table_name='',
2693 record_id=0,
2694 user_id=None,
2695 group_id=None,
2696 ):
2697 """
2698 checks if user_id or current logged in user is member of a group
2699 that has 'name' permission on 'table_name' and 'record_id'
2700 if group_id is passed, it checks whether the group has the permission
2701 """
2702
2703 if not user_id and not group_id and self.user:
2704 user_id = self.user.id
2705 if user_id:
2706 membership = self.settings.table_membership
2707 rows = self.db(membership.user_id
2708 == user_id).select(membership.group_id)
2709 groups = set([row.group_id for row in rows])
2710 if group_id and not group_id in groups:
2711 return False
2712 else:
2713 groups = set([group_id])
2714 permission = self.settings.table_permission
2715 rows = self.db(permission.name == name)(permission.table_name
2716 == str(table_name))(permission.record_id
2717 == record_id).select(permission.group_id)
2718 groups_required = set([row.group_id for row in rows])
2719 if record_id:
2720 rows = self.db(permission.name
2721 == name)(permission.table_name
2722 == str(table_name))(permission.record_id
2723 == 0).select(permission.group_id)
2724 groups_required = groups_required.union(set([row.group_id
2725 for row in rows]))
2726 if groups.intersection(groups_required):
2727 r = True
2728 else:
2729 r = False
2730 log = self.messages.has_permission_log
2731 if log and user_id:
2732 self.log_event(log % dict(user_id=user_id, name=name,
2733 table_name=table_name, record_id=record_id))
2734 return r
2735
2736 - def add_permission(
2737 self,
2738 group_id,
2739 name='any',
2740 table_name='',
2741 record_id=0,
2742 ):
2743 """
2744 gives group_id 'name' access to 'table_name' and 'record_id'
2745 """
2746
2747 permission = self.settings.table_permission
2748 if group_id == 0:
2749 group_id = self.user_group()
2750 id = permission.insert(group_id=group_id, name=name,
2751 table_name=str(table_name),
2752 record_id=long(record_id))
2753 log = self.messages.add_permission_log
2754 if log:
2755 self.log_event(log % dict(permission_id=id, group_id=group_id,
2756 name=name, table_name=table_name,
2757 record_id=record_id))
2758 return id
2759
2760 - def del_permission(
2761 self,
2762 group_id,
2763 name='any',
2764 table_name='',
2765 record_id=0,
2766 ):
2767 """
2768 revokes group_id 'name' access to 'table_name' and 'record_id'
2769 """
2770
2771 permission = self.settings.table_permission
2772 log = self.messages.del_permission_log
2773 if log:
2774 self.log_event(log % dict(group_id=group_id, name=name,
2775 table_name=table_name, record_id=record_id))
2776 return self.db(permission.group_id == group_id)(permission.name
2777 == name)(permission.table_name
2778 == str(table_name))(permission.record_id
2779 == long(record_id)).delete()
2780
2782 """
2783 returns a query with all accessible records for user_id or
2784 the current logged in user
2785 this method does not work on GAE because uses JOIN and IN
2786
2787 example::
2788
2789 db(auth.accessible_query('read', db.mytable)).select(db.mytable.ALL)
2790
2791 """
2792 if not user_id:
2793 user_id = self.user.id
2794 if self.has_permission(name, table, 0, user_id):
2795 return table.id > 0
2796 db = self.db
2797 membership = self.settings.table_membership
2798 permission = self.settings.table_permission
2799 return table.id.belongs(db(membership.user_id == user_id)\
2800 (membership.group_id == permission.group_id)\
2801 (permission.name == name)\
2802 (permission.table_name == table)\
2803 ._select(permission.record_id))
2804
2805
2806 -class Crud(object):
2807
2808 - def url(self, f=None, args=[], vars={}):
2809 """
2810 this should point to the controller that exposes
2811 download and crud
2812 """
2813 return URL(c=self.settings.controller,f=f,args=args,vars=vars)
2814
2815 - def __init__(self, environment, db=None, controller='default'):
2816 self.db = db
2817 if not db and environment and isinstance(environment,DAL):
2818 self.db = environment
2819 elif not db:
2820 raise SyntaxError, "must pass db as first or second argument"
2821 self.environment = current
2822 settings = self.settings = Settings()
2823 settings.auth = None
2824 settings.logger = None
2825
2826 settings.create_next = None
2827 settings.update_next = None
2828 settings.controller = controller
2829 settings.delete_next = self.url()
2830 settings.download_url = self.url('download')
2831 settings.create_onvalidation = StorageList()
2832 settings.update_onvalidation = StorageList()
2833 settings.delete_onvalidation = StorageList()
2834 settings.create_onaccept = StorageList()
2835 settings.update_onaccept = StorageList()
2836 settings.update_ondelete = StorageList()
2837 settings.delete_onaccept = StorageList()
2838 settings.update_deletable = True
2839 settings.showid = False
2840 settings.keepvalues = False
2841 settings.create_captcha = None
2842 settings.update_captcha = None
2843 settings.captcha = None
2844 settings.formstyle = 'table3cols'
2845 settings.hideerror = False
2846 settings.detect_record_change = True
2847 settings.hmac_key = None
2848 settings.lock_keys = True
2849
2850 messages = self.messages = Messages(current.T)
2851 messages.submit_button = 'Submit'
2852 messages.delete_label = 'Check to delete:'
2853 messages.record_created = 'Record Created'
2854 messages.record_updated = 'Record Updated'
2855 messages.record_deleted = 'Record Deleted'
2856
2857 messages.update_log = 'Record %(id)s updated'
2858 messages.create_log = 'Record %(id)s created'
2859 messages.read_log = 'Record %(id)s read'
2860 messages.delete_log = 'Record %(id)s deleted'
2861
2862 messages.lock_keys = True
2863
2865 args = current.request.args
2866 if len(args) < 1:
2867 raise HTTP(404)
2868 elif args[0] == 'tables':
2869 return self.tables()
2870 elif len(args) > 1 and not args(1) in self.db.tables:
2871 raise HTTP(404)
2872 table = self.db[args(1)]
2873 if args[0] == 'create':
2874 return self.create(table)
2875 elif args[0] == 'select':
2876 return self.select(table,linkto=self.url(args='read'))
2877 elif args[0] == 'search':
2878 form, rows = self.search(table,linkto=self.url(args='read'))
2879 return DIV(form,SQLTABLE(rows))
2880 elif args[0] == 'read':
2881 return self.read(table, args(2))
2882 elif args[0] == 'update':
2883 return self.update(table, args(2))
2884 elif args[0] == 'delete':
2885 return self.delete(table, args(2))
2886 else:
2887 raise HTTP(404)
2888
2892
2894 if not self.settings.auth:
2895 return True
2896 try:
2897 record_id = record.id
2898 except:
2899 record_id = record
2900 return self.settings.auth.has_permission(name, str(table), record_id)
2901
2906
2907
2908 @staticmethod
2909 - def archive(form,archive_table=None,current_record='current_record'):
2910 """
2911 If you have a table (db.mytable) that needs full revision history you can just do::
2912
2913 form=crud.update(db.mytable,myrecord,onaccept=crud.archive)
2914
2915 crud.archive will define a new table "mytable_archive" and store the
2916 previous record in the newly created table including a reference
2917 to the current record.
2918
2919 If you want to access such table you need to define it yourself in a model::
2920
2921 db.define_table('mytable_archive',
2922 Field('current_record',db.mytable),
2923 db.mytable)
2924
2925 Notice such table includes all fields of db.mytable plus one: current_record.
2926 crud.archive does not timestamp the stored record unless your original table
2927 has a fields like::
2928
2929 db.define_table(...,
2930 Field('saved_on','datetime',
2931 default=request.now,update=request.now,writable=False),
2932 Field('saved_by',auth.user,
2933 default=auth.user_id,update=auth.user_id,writable=False),
2934
2935 there is nothing special about these fields since they are filled before
2936 the record is archived.
2937
2938 If you want to change the archive table name and the name of the reference field
2939 you can do, for example::
2940
2941 db.define_table('myhistory',
2942 Field('parent_record',db.mytable),
2943 db.mytable)
2944
2945 and use it as::
2946
2947 form=crud.update(db.mytable,myrecord,
2948 onaccept=lambda form:crud.archive(form,
2949 archive_table=db.myhistory,
2950 current_record='parent_record'))
2951
2952 """
2953 old_record = form.record
2954 if not old_record:
2955 return None
2956 table = form.table
2957 if not archive_table:
2958 archive_table_name = '%s_archive' % table
2959 if archive_table_name in table._db:
2960 archive_table = table._db[archive_table_name]
2961 else:
2962 archive_table = table._db.define_table(archive_table_name,
2963 Field(current_record,table),
2964 table)
2965 new_record = {current_record:old_record.id}
2966 for fieldname in archive_table.fields:
2967 if not fieldname in ['id',current_record] and fieldname in old_record:
2968 new_record[fieldname]=old_record[fieldname]
2969 id = archive_table.insert(**new_record)
2970 return id
2971
2972 - def update(
2973 self,
2974 table,
2975 record,
2976 next=DEFAULT,
2977 onvalidation=DEFAULT,
2978 onaccept=DEFAULT,
2979 ondelete=DEFAULT,
2980 log=DEFAULT,
2981 message=DEFAULT,
2982 deletable=DEFAULT,
2983 formname=DEFAULT,
2984 ):
2985 """
2986 .. method:: Crud.update(table, record, [next=DEFAULT
2987 [, onvalidation=DEFAULT [, onaccept=DEFAULT [, log=DEFAULT
2988 [, message=DEFAULT[, deletable=DEFAULT]]]]]])
2989
2990 """
2991 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
2992 or (isinstance(record, str) and not str(record).isdigit()):
2993 raise HTTP(404)
2994 if not isinstance(table, self.db.Table):
2995 table = self.db[table]
2996 try:
2997 record_id = record.id
2998 except:
2999 record_id = record or 0
3000 if record_id and not self.has_permission('update', table, record_id):
3001 redirect(self.settings.auth.settings.on_failed_authorization)
3002 if not record_id \
3003 and not self.has_permission('create', table, record_id):
3004 redirect(self.settings.auth.settings.on_failed_authorization)
3005
3006 request = current.request
3007 response = current.response
3008 session = current.session
3009 if request.extension == 'json' and request.vars.json:
3010 request.vars.update(simplejson.loads(request.vars.json))
3011 if next == DEFAULT:
3012 next = request.get_vars._next \
3013 or request.post_vars._next \
3014 or self.settings.update_next
3015 if onvalidation == DEFAULT:
3016 onvalidation = self.settings.update_onvalidation
3017 if onaccept == DEFAULT:
3018 onaccept = self.settings.update_onaccept
3019 if ondelete == DEFAULT:
3020 ondelete = self.settings.update_ondelete
3021 if log == DEFAULT:
3022 log = self.messages.update_log
3023 if deletable == DEFAULT:
3024 deletable = self.settings.update_deletable
3025 if message == DEFAULT:
3026 message = self.messages.record_updated
3027 form = SQLFORM(
3028 table,
3029 record,
3030 hidden=dict(_next=next),
3031 showid=self.settings.showid,
3032 submit_button=self.messages.submit_button,
3033 delete_label=self.messages.delete_label,
3034 deletable=deletable,
3035 upload=self.settings.download_url,
3036 formstyle=self.settings.formstyle
3037 )
3038 self.accepted = False
3039 self.deleted = False
3040 captcha = self.settings.update_captcha or \
3041 self.settings.captcha
3042 if record and captcha:
3043 addrow(form, captcha.label, captcha, captcha.comment,
3044 self.settings.formstyle,'captcha__row')
3045 captcha = self.settings.create_captcha or \
3046 self.settings.captcha
3047 if not record and captcha:
3048 addrow(form, captcha.label, captcha, captcha.comment,
3049 self.settings.formstyle,'captcha__row')
3050 if not request.extension in ('html','load'):
3051 (_session, _formname) = (None, None)
3052 else:
3053 (_session, _formname) = \
3054 (session, '%s/%s' % (table._tablename, form.record_id))
3055 if formname!=DEFAULT:
3056 _formname = formname
3057 keepvalues = self.settings.keepvalues
3058 if request.vars.delete_this_record:
3059 keepvalues = False
3060 if isinstance(onvalidation,StorageList):
3061 onvalidation=onvalidation.get(table._tablename, [])
3062 if form.accepts(request, _session, formname=_formname,
3063 onvalidation=onvalidation, keepvalues=keepvalues,
3064 hideerror=self.settings.hideerror,
3065 detect_record_change = self.settings.detect_record_change):
3066 self.accepted = True
3067 response.flash = message
3068 if log:
3069 self.log_event(log % form.vars)
3070 if request.vars.delete_this_record:
3071 self.deleted = True
3072 message = self.messages.record_deleted
3073 callback(ondelete,form,table._tablename)
3074 response.flash = message
3075 callback(onaccept,form,table._tablename)
3076 if not request.extension in ('html','load'):
3077 raise HTTP(200, 'RECORD CREATED/UPDATED')
3078 if isinstance(next, (list, tuple)):
3079 next = next[0]
3080 if next:
3081 if next[0] != '/' and next[:4] != 'http':
3082 next = URL(r=request,
3083 f=next.replace('[id]', str(form.vars.id)))
3084 session.flash = response.flash
3085 redirect(next)
3086 elif not request.extension in ('html','load'):
3087 raise HTTP(401)
3088 return form
3089
3100 """
3101 .. method:: Crud.create(table, [next=DEFAULT [, onvalidation=DEFAULT
3102 [, onaccept=DEFAULT [, log=DEFAULT[, message=DEFAULT]]]]])
3103 """
3104
3105 if next == DEFAULT:
3106 next = self.settings.create_next
3107 if onvalidation == DEFAULT:
3108 onvalidation = self.settings.create_onvalidation
3109 if onaccept == DEFAULT:
3110 onaccept = self.settings.create_onaccept
3111 if log == DEFAULT:
3112 log = self.messages.create_log
3113 if message == DEFAULT:
3114 message = self.messages.record_created
3115 return self.update(
3116 table,
3117 None,
3118 next=next,
3119 onvalidation=onvalidation,
3120 onaccept=onaccept,
3121 log=log,
3122 message=message,
3123 deletable=False,
3124 formname=formname,
3125 )
3126
3127 - def read(self, table, record):
3128 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
3129 or (isinstance(record, str) and not str(record).isdigit()):
3130 raise HTTP(404)
3131 if not isinstance(table, self.db.Table):
3132 table = self.db[table]
3133 if not self.has_permission('read', table, record):
3134 redirect(self.settings.auth.settings.on_failed_authorization)
3135 form = SQLFORM(
3136 table,
3137 record,
3138 readonly=True,
3139 comments=False,
3140 upload=self.settings.download_url,
3141 showid=self.settings.showid,
3142 formstyle=self.settings.formstyle
3143 )
3144 if not current.request.extension in ('html','load'):
3145 return table._filter_fields(form.record, id=True)
3146 return form
3147
3148 - def delete(
3149 self,
3150 table,
3151 record_id,
3152 next=DEFAULT,
3153 message=DEFAULT,
3154 ):
3155 """
3156 .. method:: Crud.delete(table, record_id, [next=DEFAULT
3157 [, message=DEFAULT]])
3158 """
3159 if not (isinstance(table, self.db.Table) or table in self.db.tables) \
3160 or not str(record_id).isdigit():
3161 raise HTTP(404)
3162 if not isinstance(table, self.db.Table):
3163 table = self.db[table]
3164 if not self.has_permission('delete', table, record_id):
3165 redirect(self.settings.auth.settings.on_failed_authorization)
3166 request = current.request
3167 session = current.session
3168 if next == DEFAULT:
3169 next = request.get_vars._next \
3170 or request.post_vars._next \
3171 or self.settings.delete_next
3172 if message == DEFAULT:
3173 message = self.messages.record_deleted
3174 record = table[record_id]
3175 if record:
3176 callback(self.settings.delete_onvalidation,record)
3177 del table[record_id]
3178 callback(self.settings.delete_onaccept,record,table._tablename)
3179 session.flash = message
3180 if next:
3181 redirect(next)
3182
3183 - def rows(
3184 self,
3185 table,
3186 query=None,
3187 fields=None,
3188 orderby=None,
3189 limitby=None,
3190 ):
3191 request = current.request
3192 if not (isinstance(table, self.db.Table) or table in self.db.tables):
3193 raise HTTP(404)
3194 if not self.has_permission('select', table):
3195 redirect(self.settings.auth.settings.on_failed_authorization)
3196
3197
3198 if not isinstance(table, self.db.Table):
3199 table = self.db[table]
3200 if not query:
3201 query = table.id > 0
3202 if not fields:
3203 fields = [field for field in table if field.readable]
3204 rows = self.db(query).select(*fields,**dict(orderby=orderby,
3205 limitby=limitby))
3206 return rows
3207
3208 - def select(
3209 self,
3210 table,
3211 query=None,
3212 fields=None,
3213 orderby=None,
3214 limitby=None,
3215 headers={},
3216 **attr
3217 ):
3218 rows = self.rows(table,query,fields,orderby,limitby)
3219 if not rows:
3220 return None
3221 if not 'upload' in attr:
3222 attr['upload'] = self.url('download')
3223 if not current.request.extension in ('html','load'):
3224 return rows.as_list()
3225 if not headers:
3226 headers = dict((str(k),k.label) for k in table)
3227 return SQLTABLE(rows,headers=headers,**attr)
3228
3235
3236 - def get_query(self, field, op, value, refsearch=False):
3237 try:
3238 if refsearch: format = self.get_format(field)
3239 if op == 'equals':
3240 if not refsearch:
3241 return field == value
3242 else:
3243 return lambda row: row[field.name][format] == value
3244 elif op == 'not equal':
3245 if not refsearch:
3246 return field != value
3247 else:
3248 return lambda row: row[field.name][format] != value
3249 elif op == 'greater than':
3250 if not refsearch:
3251 return field > value
3252 else:
3253 return lambda row: row[field.name][format] > value
3254 elif op == 'less than':
3255 if not refsearch:
3256 return field < value
3257 else:
3258 return lambda row: row[field.name][format] < value
3259 elif op == 'starts with':
3260 if not refsearch:
3261 return field.like(value+'%')
3262 else:
3263 return lambda row: str(row[field.name][format]).startswith(value)
3264 elif op == 'ends with':
3265 if not refsearch:
3266 return field.like('%'+value)
3267 else:
3268 return lambda row: str(row[field.name][format]).endswith(value)
3269 elif op == 'contains':
3270 if not refsearch:
3271 return field.like('%'+value+'%')
3272 else:
3273 return lambda row: value in row[field.name][format]
3274 except:
3275 return None
3276
3277
3278 - def search(self, *tables, **args):
3279 """
3280 Creates a search form and its results for a table
3281 Example usage:
3282 form, results = crud.search(db.test,
3283 queries = ['equals', 'not equal', 'contains'],
3284 query_labels={'equals':'Equals',
3285 'not equal':'Not equal'},
3286 fields = [db.test.id, db.test.children],
3287 field_labels = {'id':'ID','children':'Children'},
3288 zero='Please choose',
3289 query = (db.test.id > 0)&(db.test.id != 3) )
3290 """
3291 table = tables[0]
3292 fields = args.get('fields', table.fields)
3293 request = current.request
3294 db = self.db
3295 if not (isinstance(table, db.Table) or table in db.tables):
3296 raise HTTP(404)
3297 attributes = {}
3298 for key in ('orderby','groupby','left','distinct','limitby','cache'):
3299 if key in args: attributes[key]=args[key]
3300 tbl = TABLE()
3301 selected = []; refsearch = []; results = []
3302 ops = args.get('queries', [])
3303 zero = args.get('zero', '')
3304 if not ops:
3305 ops = ['equals', 'not equal', 'greater than',
3306 'less than', 'starts with',
3307 'ends with', 'contains']
3308 ops.insert(0,zero)
3309 query_labels = args.get('query_labels', {})
3310 query = args.get('query',table.id > 0)
3311 field_labels = args.get('field_labels',{})
3312 for field in fields:
3313 field = table[field]
3314 if not field.readable: continue
3315 fieldname = field.name
3316 chkval = request.vars.get('chk' + fieldname, None)
3317 txtval = request.vars.get('txt' + fieldname, None)
3318 opval = request.vars.get('op' + fieldname, None)
3319 row = TR(TD(INPUT(_type = "checkbox", _name = "chk" + fieldname,
3320 _disabled = (field.type == 'id'),
3321 value = (field.type == 'id' or chkval == 'on'))),
3322 TD(field_labels.get(fieldname,field.label)),
3323 TD(SELECT([OPTION(query_labels.get(op,op),
3324 _value=op) for op in ops],
3325 _name = "op" + fieldname,
3326 value = opval)),
3327 TD(INPUT(_type = "text", _name = "txt" + fieldname,
3328 _value = txtval, _id='txt' + fieldname,
3329 _class = str(field.type))))
3330 tbl.append(row)
3331 if request.post_vars and (chkval or field.type=='id'):
3332 if txtval and opval != '':
3333 if field.type[0:10] == 'reference ':
3334 refsearch.append(self.get_query(field,
3335 opval, txtval, refsearch=True))
3336 else:
3337 value, error = field.validate(txtval)
3338 if not error:
3339
3340 query &= self.get_query(field, opval, value)
3341 else:
3342 row[3].append(DIV(error,_class='error'))
3343 selected.append(field)
3344 form = FORM(tbl,INPUT(_type="submit"))
3345 if selected:
3346 try:
3347 results = db(query).select(*selected,**attributes)
3348 for r in refsearch:
3349 results = results.find(r)
3350 except:
3351 results = None
3352 return form, results
3353
3354
3355 urllib2.install_opener(urllib2.build_opener(urllib2.HTTPCookieProcessor()))
3356
3357 -def fetch(url, data=None, headers={},
3358 cookie=Cookie.SimpleCookie(),
3359 user_agent='Mozilla/5.0'):
3360 if data != None:
3361 data = urllib.urlencode(data)
3362 if user_agent: headers['User-agent'] = user_agent
3363 headers['Cookie'] = ' '.join(['%s=%s;'%(c.key,c.value) for c in cookie.values()])
3364 try:
3365 from google.appengine.api import urlfetch
3366 except ImportError:
3367 req = urllib2.Request(url, data, headers)
3368 html = urllib2.urlopen(req).read()
3369 else:
3370 method = ((data==None) and urlfetch.GET) or urlfetch.POST
3371 while url is not None:
3372 response = urlfetch.fetch(url=url, payload=data,
3373 method=method, headers=headers,
3374 allow_truncated=False,follow_redirects=False,
3375 deadline=10)
3376
3377 data = None
3378 method = urlfetch.GET
3379
3380 cookie.load(response.headers.get('set-cookie', ''))
3381 url = response.headers.get('location')
3382 html = response.content
3383 return html
3384
3385 regex_geocode = \
3386 re.compile('\<coordinates\>(?P<la>[^,]*),(?P<lo>[^,]*).*?\</coordinates\>')
3387
3388
3390 try:
3391 a = urllib.quote(address)
3392 txt = fetch('http://maps.google.com/maps/geo?q=%s&output=xml'
3393 % a)
3394 item = regex_geocode.search(txt)
3395 (la, lo) = (float(item.group('la')), float(item.group('lo')))
3396 return (la, lo)
3397 except:
3398 return (0.0, 0.0)
3399
3400
3402 c = f.func_code.co_argcount
3403 n = f.func_code.co_varnames[:c]
3404 b = dict([(k, v) for k, v in b.items() if k in n])
3405 if len(b) == c:
3406 return f(**b)
3407 elif len(a) >= c:
3408 return f(*a[:c])
3409 raise HTTP(404, "Object does not exist")
3410
3411
3413
3415 self.run_procedures = {}
3416 self.csv_procedures = {}
3417 self.xml_procedures = {}
3418 self.rss_procedures = {}
3419 self.json_procedures = {}
3420 self.jsonrpc_procedures = {}
3421 self.xmlrpc_procedures = {}
3422 self.amfrpc_procedures = {}
3423 self.amfrpc3_procedures = {}
3424 self.soap_procedures = {}
3425
3427 """
3428 example::
3429
3430 service = Service(globals())
3431 @service.run
3432 def myfunction(a, b):
3433 return a + b
3434 def call():
3435 return service()
3436
3437 Then call it with::
3438
3439 wget http://..../app/default/call/run/myfunction?a=3&b=4
3440
3441 """
3442 self.run_procedures[f.__name__] = f
3443 return f
3444
3446 """
3447 example::
3448
3449 service = Service(globals())
3450 @service.csv
3451 def myfunction(a, b):
3452 return a + b
3453 def call():
3454 return service()
3455
3456 Then call it with::
3457
3458 wget http://..../app/default/call/csv/myfunction?a=3&b=4
3459
3460 """
3461 self.run_procedures[f.__name__] = f
3462 return f
3463
3465 """
3466 example::
3467
3468 service = Service(globals())
3469 @service.xml
3470 def myfunction(a, b):
3471 return a + b
3472 def call():
3473 return service()
3474
3475 Then call it with::
3476
3477 wget http://..../app/default/call/xml/myfunction?a=3&b=4
3478
3479 """
3480 self.run_procedures[f.__name__] = f
3481 return f
3482
3484 """
3485 example::
3486
3487 service = Service(globals())
3488 @service.rss
3489 def myfunction():
3490 return dict(title=..., link=..., description=...,
3491 created_on=..., entries=[dict(title=..., link=...,
3492 description=..., created_on=...])
3493 def call():
3494 return service()
3495
3496 Then call it with::
3497
3498 wget http://..../app/default/call/rss/myfunction
3499
3500 """
3501 self.rss_procedures[f.__name__] = f
3502 return f
3503
3504 - def json(self, f):
3505 """
3506 example::
3507
3508 service = Service(globals())
3509 @service.json
3510 def myfunction(a, b):
3511 return [{a: b}]
3512 def call():
3513 return service()
3514
3515 Then call it with::
3516
3517 wget http://..../app/default/call/json/myfunction?a=hello&b=world
3518
3519 """
3520 self.json_procedures[f.__name__] = f
3521 return f
3522
3524 """
3525 example::
3526
3527 service = Service(globals())
3528 @service.jsonrpc
3529 def myfunction(a, b):
3530 return a + b
3531 def call():
3532 return service()
3533
3534 Then call it with::
3535
3536 wget http://..../app/default/call/jsonrpc/myfunction?a=hello&b=world
3537
3538 """
3539 self.jsonrpc_procedures[f.__name__] = f
3540 return f
3541
3543 """
3544 example::
3545
3546 service = Service(globals())
3547 @service.xmlrpc
3548 def myfunction(a, b):
3549 return a + b
3550 def call():
3551 return service()
3552
3553 The call it with::
3554
3555 wget http://..../app/default/call/xmlrpc/myfunction?a=hello&b=world
3556
3557 """
3558 self.xmlrpc_procedures[f.__name__] = f
3559 return f
3560
3562 """
3563 example::
3564
3565 service = Service(globals())
3566 @service.amfrpc
3567 def myfunction(a, b):
3568 return a + b
3569 def call():
3570 return service()
3571
3572 The call it with::
3573
3574 wget http://..../app/default/call/amfrpc/myfunction?a=hello&b=world
3575
3576 """
3577 self.amfrpc_procedures[f.__name__] = f
3578 return f
3579
3580 - def amfrpc3(self, domain='default'):
3581 """
3582 example::
3583
3584 service = Service(globals())
3585 @service.amfrpc3('domain')
3586 def myfunction(a, b):
3587 return a + b
3588 def call():
3589 return service()
3590
3591 The call it with::
3592
3593 wget http://..../app/default/call/amfrpc3/myfunction?a=hello&b=world
3594
3595 """
3596 if not isinstance(domain, str):
3597 raise SyntaxError, "AMF3 requires a domain for function"
3598
3599 def _amfrpc3(f):
3600 if domain:
3601 self.amfrpc3_procedures[domain+'.'+f.__name__] = f
3602 else:
3603 self.amfrpc3_procedures[f.__name__] = f
3604 return f
3605 return _amfrpc3
3606
3607 - def soap(self, name=None, returns=None, args=None,doc=None):
3608 """
3609 example::
3610
3611 service = Service(globals())
3612 @service.soap('MyFunction',returns={'result':int},args={'a':int,'b':int,})
3613 def myfunction(a, b):
3614 return a + b
3615 def call():
3616 return service()
3617
3618 The call it with::
3619
3620 from gluon.contrib.pysimplesoap.client import SoapClient
3621 client = SoapClient(wsdl="http://..../app/default/call/soap?WSDL")
3622 response = client.MyFunction(a=1,b=2)
3623 return response['result']
3624
3625 Exposes online generated documentation and xml example messages at:
3626 - http://..../app/default/call/soap
3627 """
3628
3629 def _soap(f):
3630 self.soap_procedures[name or f.__name__] = f, returns, args, doc
3631 return f
3632 return _soap
3633
3635 request = current.request
3636 if not args:
3637 args = request.args
3638 if args and args[0] in self.run_procedures:
3639 return str(universal_caller(self.run_procedures[args[0]],
3640 *args[1:], **dict(request.vars)))
3641 self.error()
3642
3644 request = current.request
3645 response = current.response
3646 response.headers['Content-Type'] = 'text/x-csv'
3647 if not args:
3648 args = request.args
3649
3650 def none_exception(value):
3651 if isinstance(value, unicode):
3652 return value.encode('utf8')
3653 if hasattr(value, 'isoformat'):
3654 return value.isoformat()[:19].replace('T', ' ')
3655 if value == None:
3656 return '<NULL>'
3657 return value
3658 if args and args[0] in self.run_procedures:
3659 r = universal_caller(self.run_procedures[args[0]],
3660 *args[1:], **dict(request.vars))
3661 s = cStringIO.StringIO()
3662 if hasattr(r, 'export_to_csv_file'):
3663 r.export_to_csv_file(s)
3664 elif r and isinstance(r[0], (dict, Storage)):
3665 import csv
3666 writer = csv.writer(s)
3667 writer.writerow(r[0].keys())
3668 for line in r:
3669 writer.writerow([none_exception(v) \
3670 for v in line.values()])
3671 else:
3672 import csv
3673 writer = csv.writer(s)
3674 for line in r:
3675 writer.writerow(line)
3676 return s.getvalue()
3677 self.error()
3678
3680 request = current.request
3681 response = current.response
3682 response.headers['Content-Type'] = 'text/xml'
3683 if not args:
3684 args = request.args
3685 if args and args[0] in self.run_procedures:
3686 s = universal_caller(self.run_procedures[args[0]],
3687 *args[1:], **dict(request.vars))
3688 if hasattr(s, 'as_list'):
3689 s = s.as_list()
3690 return serializers.xml(s)
3691 self.error()
3692
3694 request = current.request
3695 response = current.response
3696 if not args:
3697 args = request.args
3698 if args and args[0] in self.rss_procedures:
3699 feed = universal_caller(self.rss_procedures[args[0]],
3700 *args[1:], **dict(request.vars))
3701 else:
3702 self.error()
3703 response.headers['Content-Type'] = 'application/rss+xml'
3704 return serializers.rss(feed)
3705
3707 request = current.request
3708 response = current.response
3709 response.headers['Content-Type'] = 'text/x-json'
3710 if not args:
3711 args = request.args
3712 d = dict(request.vars)
3713 if args and args[0] in self.json_procedures:
3714 s = universal_caller(self.json_procedures[args[0]],*args[1:],**d)
3715 if hasattr(s, 'as_list'):
3716 s = s.as_list()
3717 return response.json(s)
3718 self.error()
3719
3722 self.code,self.info = code,info
3723
3725 import contrib.simplejson as simplejson
3726 def return_response(id, result):
3727 return serializers.json({'version': '1.1',
3728 'id': id, 'result': result, 'error': None})
3729
3730 def return_error(id, code, message):
3731 return serializers.json({'id': id,
3732 'version': '1.1',
3733 'error': {'name': 'JSONRPCError',
3734 'code': code, 'message': message}
3735 })
3736
3737 request = current.request
3738 methods = self.jsonrpc_procedures
3739 data = simplejson.loads(request.body.read())
3740 id, method, params = data['id'], data['method'], data.get('params','')
3741 if not method in methods:
3742 return return_error(id, 100, 'method "%s" does not exist' % method)
3743 try:
3744 s = methods[method](*params)
3745 if hasattr(s, 'as_list'):
3746 s = s.as_list()
3747 return return_response(id, s)
3748 except Service.JsonRpcException, e:
3749 return return_error(id, e.code, e.info)
3750 except BaseException:
3751 etype, eval, etb = sys.exc_info()
3752 return return_error(id, 100, '%s: %s' % (etype.__name__, eval))
3753 except:
3754 etype, eval, etb = sys.exc_info()
3755 return return_error(id, 100, 'Exception %s: %s' % (etype, eval))
3756
3758 request = current.request
3759 response = current.response
3760 services = self.xmlrpc_procedures.values()
3761 return response.xmlrpc(request, services)
3762
3764 try:
3765 import pyamf
3766 import pyamf.remoting.gateway
3767 except:
3768 return "pyamf not installed or not in Python sys.path"
3769 request = current.request
3770 response = current.response
3771 if version == 3:
3772 services = self.amfrpc3_procedures
3773 base_gateway = pyamf.remoting.gateway.BaseGateway(services)
3774 pyamf_request = pyamf.remoting.decode(request.body)
3775 else:
3776 services = self.amfrpc_procedures
3777 base_gateway = pyamf.remoting.gateway.BaseGateway(services)
3778 context = pyamf.get_context(pyamf.AMF0)
3779 pyamf_request = pyamf.remoting.decode(request.body, context)
3780 pyamf_response = pyamf.remoting.Envelope(pyamf_request.amfVersion,
3781 pyamf_request.clientType)
3782 for name, message in pyamf_request:
3783 pyamf_response[name] = base_gateway.getProcessor(message)(message)
3784 response.headers['Content-Type'] = pyamf.remoting.CONTENT_TYPE
3785 if version==3:
3786 return pyamf.remoting.encode(pyamf_response).getvalue()
3787 else:
3788 return pyamf.remoting.encode(pyamf_response, context).getvalue()
3789
3791 try:
3792 from contrib.pysimplesoap.server import SoapDispatcher
3793 except:
3794 return "pysimplesoap not installed in contrib"
3795 request = current.request
3796 response = current.response
3797 procedures = self.soap_procedures
3798
3799 location = "%s://%s%s" % (
3800 request.env.wsgi_url_scheme,
3801 request.env.http_host,
3802 URL(r=request,f="call/soap",vars={}))
3803 namespace = 'namespace' in response and response.namespace or location
3804 documentation = response.description or ''
3805 dispatcher = SoapDispatcher(
3806 name = response.title,
3807 location = location,
3808 action = location,
3809 namespace = namespace,
3810 prefix='pys',
3811 documentation = documentation,
3812 ns = True)
3813 for method, (function, returns, args, doc) in procedures.items():
3814 dispatcher.register_function(method, function, returns, args, doc)
3815 if request.env.request_method == 'POST':
3816
3817 response.headers['Content-Type'] = 'text/xml'
3818 return dispatcher.dispatch(request.body.read())
3819 elif 'WSDL' in request.vars:
3820
3821 response.headers['Content-Type'] = 'text/xml'
3822 return dispatcher.wsdl()
3823 elif 'op' in request.vars:
3824
3825 response.headers['Content-Type'] = 'text/html'
3826 method = request.vars['op']
3827 sample_req_xml, sample_res_xml, doc = dispatcher.help(method)
3828 body = [H1("Welcome to Web2Py SOAP webservice gateway"),
3829 A("See all webservice operations",
3830 _href=URL(r=request,f="call/soap",vars={})),
3831 H2(method),
3832 P(doc),
3833 UL(LI("Location: %s" % dispatcher.location),
3834 LI("Namespace: %s" % dispatcher.namespace),
3835 LI("SoapAction: %s" % dispatcher.action),
3836 ),
3837 H3("Sample SOAP XML Request Message:"),
3838 CODE(sample_req_xml,language="xml"),
3839 H3("Sample SOAP XML Response Message:"),
3840 CODE(sample_res_xml,language="xml"),
3841 ]
3842 return {'body': body}
3843 else:
3844
3845 response.headers['Content-Type'] = 'text/html'
3846 body = [H1("Welcome to Web2Py SOAP webservice gateway"),
3847 P(response.description),
3848 P("The following operations are available"),
3849 A("See WSDL for webservice description",
3850 _href=URL(r=request,f="call/soap",vars={"WSDL":None})),
3851 UL([LI(A("%s: %s" % (method, doc or ''),
3852 _href=URL(r=request,f="call/soap",vars={'op': method})))
3853 for method, doc in dispatcher.list_methods()]),
3854 ]
3855 return {'body': body}
3856
3858 """
3859 register services with:
3860 service = Service(globals())
3861 @service.run
3862 @service.rss
3863 @service.json
3864 @service.jsonrpc
3865 @service.xmlrpc
3866 @service.jsonrpc
3867 @service.amfrpc
3868 @service.amfrpc3('domain')
3869 @service.soap('Method', returns={'Result':int}, args={'a':int,'b':int,})
3870
3871 expose services with
3872
3873 def call(): return service()
3874
3875 call services with
3876 http://..../app/default/call/run?[parameters]
3877 http://..../app/default/call/rss?[parameters]
3878 http://..../app/default/call/json?[parameters]
3879 http://..../app/default/call/jsonrpc
3880 http://..../app/default/call/xmlrpc
3881 http://..../app/default/call/amfrpc
3882 http://..../app/default/call/amfrpc3
3883 http://..../app/default/call/soap
3884 """
3885
3886 request = current.request
3887 if len(request.args) < 1:
3888 raise HTTP(404, "Not Found")
3889 arg0 = request.args(0)
3890 if arg0 == 'run':
3891 return self.serve_run(request.args[1:])
3892 elif arg0 == 'rss':
3893 return self.serve_rss(request.args[1:])
3894 elif arg0 == 'csv':
3895 return self.serve_csv(request.args[1:])
3896 elif arg0 == 'xml':
3897 return self.serve_xml(request.args[1:])
3898 elif arg0 == 'json':
3899 return self.serve_json(request.args[1:])
3900 elif arg0 == 'jsonrpc':
3901 return self.serve_jsonrpc()
3902 elif arg0 == 'xmlrpc':
3903 return self.serve_xmlrpc()
3904 elif arg0 == 'amfrpc':
3905 return self.serve_amfrpc()
3906 elif arg0 == 'amfrpc3':
3907 return self.serve_amfrpc(3)
3908 elif arg0 == 'soap':
3909 return self.serve_soap()
3910 else:
3911 self.error()
3912
3914 raise HTTP(404, "Object does not exist")
3915
3916
3918 """
3919 Executes a task on completion of the called action. For example:
3920
3921 from gluon.tools import completion
3922 @completion(lambda d: logging.info(repr(d)))
3923 def index():
3924 return dict(message='hello')
3925
3926 It logs the output of the function every time input is called.
3927 The argument of completion is executed in a new thread.
3928 """
3929 def _completion(f):
3930 def __completion(*a,**b):
3931 d = None
3932 try:
3933 d = f(*a,**b)
3934 return d
3935 finally:
3936 thread.start_new_thread(callback,(d,))
3937 return __completion
3938 return _completion
3939
3941 try:
3942 dt = datetime.datetime.now() - d
3943 except:
3944 return ''
3945 if dt.days >= 2*365:
3946 return T('%d years ago') % int(dt.days / 365)
3947 elif dt.days >= 365:
3948 return T('1 year ago')
3949 elif dt.days >= 60:
3950 return T('%d months ago') % int(dt.days / 30)
3951 elif dt.days > 21:
3952 return T('1 month ago')
3953 elif dt.days >= 14:
3954 return T('%d weeks ago') % int(dt.days / 7)
3955 elif dt.days >= 7:
3956 return T('1 week ago')
3957 elif dt.days > 1:
3958 return T('%d days ago') % dt.days
3959 elif dt.days == 1:
3960 return T('1 day ago')
3961 elif dt.seconds >= 2*60*60:
3962 return T('%d hours ago') % int(dt.seconds / 3600)
3963 elif dt.seconds >= 60*60:
3964 return T('1 hour ago')
3965 elif dt.seconds >= 2*60:
3966 return T('%d minutes ago') % int(dt.seconds / 60)
3967 elif dt.seconds >= 60:
3968 return T('1 minute ago')
3969 elif dt.seconds > 1:
3970 return T('%d seconds ago') % dt.seconds
3971 elif dt.seconds == 1:
3972 return T('1 second ago')
3973 else:
3974 return T('now')
3975
3984 lock1=thread.allocate_lock()
3985 lock2=thread.allocate_lock()
3986 lock1.acquire()
3987 thread.start_new_thread(f,())
3988 a=PluginManager()
3989 a.x=5
3990 lock1.release()
3991 lock2.acquire()
3992 return a.x
3993
3995 """
3996
3997 Plugin Manager is similar to a storage object but it is a single level singleton
3998 this means that multiple instances within the same thread share the same attributes
3999 Its constructor is also special. The first argument is the name of the plugin you are defining.
4000 The named arguments are parameters needed by the plugin with default values.
4001 If the parameters were previous defined, the old values are used.
4002
4003 For example:
4004
4005 ### in some general configuration file:
4006 >>> plugins = PluginManager()
4007 >>> plugins.me.param1=3
4008
4009 ### within the plugin model
4010 >>> _ = PluginManager('me',param1=5,param2=6,param3=7)
4011
4012 ### where the plugin is used
4013 >>> print plugins.me.param1
4014 3
4015 >>> print plugins.me.param2
4016 6
4017 >>> plugins.me.param3 = 8
4018 >>> print plugins.me.param3
4019 8
4020
4021 Here are some tests:
4022
4023 >>> a=PluginManager()
4024 >>> a.x=6
4025 >>> b=PluginManager('check')
4026 >>> print b.x
4027 6
4028 >>> b=PluginManager() # reset settings
4029 >>> print b.x
4030 <Storage {}>
4031 >>> b.x=7
4032 >>> print a.x
4033 7
4034 >>> a.y.z=8
4035 >>> print b.y.z
4036 8
4037 >>> test_thread_separation()
4038 5
4039 >>> plugins=PluginManager('me',db='mydb')
4040 >>> print plugins.me.db
4041 mydb
4042 >>> print 'me' in plugins
4043 True
4044 >>> print plugins.me.installed
4045 True
4046 """
4047 instances = {}
4061 - def __init__(self,plugin=None,**defaults):
4068 if not key in self.__dict__:
4069 self.__dict__[key] = Storage()
4070 return self.__dict__[key]
4072 return self.__dict__.keys()
4074 return key in self.__dict__
4075
4076 if __name__ == '__main__':
4077 import doctest
4078 doctest.testmod()
4079