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