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