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