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