Changeset 3799

Show
Ignore:
Timestamp:
06/07/08 22:48:51 (4 months ago)
Author:
mgood
Message:

add an email verification module re #442. Keeping the ticket open until this has been integrated with the admin modules too.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • accountmanagerplugin/trunk/acct_mgr/pwhash.py

    r2127 r3799  
    1717 
    1818from md5crypt import md5crypt 
     19from acct_mgr.util import urandom 
    1920 
    2021class IPasswordHashMethod(Interface): 
     
    6465    crypt = None 
    6566 
    66 # os.urandom was added in Python 2.4 
    67 # try to fall back on reading from /dev/urandom on older Python versions 
    68 try: 
    69     from os import urandom 
    70 except ImportError: 
    71     from random import randrange 
    72     def urandom(n): 
    73         return ''.join([chr(randrange(256)) for _ in xrange(n)]) 
    74  
    7567def salt(): 
    7668    s = '' 
  • accountmanagerplugin/trunk/acct_mgr/util.py

    r2068 r3799  
    2323            return path 
    2424        return os.path.normpath(os.path.join(instance.env.path, path)) 
     25 
     26 
     27# os.urandom was added in Python 2.4 
     28# try to fall back on pseudo-random values if it's not available 
     29try: 
     30    from os import urandom 
     31except ImportError: 
     32    from random import randrange 
     33    def urandom(n): 
     34        return ''.join([chr(randrange(256)) for _ in xrange(n)]) 
     35 
     36 
  • accountmanagerplugin/trunk/acct_mgr/web_ui.py

    r3731 r3799  
    1010# Author: Matthew Good <trac@matt-good.net> 
    1111 
     12import base64 
     13import os 
    1214import random 
    1315import string 
     
    2123from trac.web.api import IAuthenticator 
    2224from trac.web.main import IRequestHandler, IRequestFilter 
     25from trac.web import chrome 
    2326from trac.web.chrome import INavigationContributor, ITemplateProvider 
    2427from genshi.builder import tag 
    2528 
    2629from api import AccountManager 
     30from acct_mgr.util import urandom 
    2731 
    2832def _create_user(req, env, check_permissions=True): 
     
    7983 
    8084 
    81 class PasswordResetNotification(NotifyEmail): 
    82     template_name = 'reset_password_email.txt' 
     85class SingleUserNofification(NotifyEmail): 
     86    """Helper class used for account email notifications which should only be 
     87    sent to one persion, not including the rest of the normally CCed users 
     88    """ 
    8389    _username = None 
    8490 
     
    95101            return None 
    96102 
    97     def notify(self, username, password): 
     103    def notify(self, username, subject): 
    98104        # save the username for use in `get_smtp_address` 
    99105        self._username = username 
     106        old_public_cc = self.config.getbool('notification', 'use_public_cc') 
     107        # override public cc option so that the user's email is included in the To: field 
     108        self.config.set('notification', 'use_public_cc', 'true') 
     109        try: 
     110            NotifyEmail.notify(self, username, subject) 
     111        finally: 
     112            self.config.set('notification', 'use_public_cc', old_public_cc) 
     113 
     114 
     115class PasswordResetNotification(SingleUserNofification): 
     116    template_name = 'reset_password_email.txt' 
     117 
     118    def notify(self, username, password): 
    100119        self.data.update({ 
    101120            'account': { 
     
    111130        subject = '[%s] Trac password reset for user: %s' % (projname, username) 
    112131 
    113         NotifyEmail.notify(self, username, subject) 
     132        SingleUserNofification.notify(self, username, subject) 
    114133 
    115134 
     
    453472        return [resource_filename(__name__, 'templates')] 
    454473 
     474 
     475class MessageWrapper(object): 
     476    """Wrapper for add_warning and add_notice to work around the requirement 
     477    for a % operator.""" 
     478    def __init__(self, body): 
     479        self.body = body 
     480 
     481    def __mod__(self, rhs): 
     482        return self.body 
     483 
     484 
     485class EmailVerificationNotification(SingleUserNofification): 
     486    template_name = 'verify_email.txt' 
     487 
     488    def notify(self, username, token): 
     489        self.data.update({ 
     490            'account': { 
     491                'username': username, 
     492                'token': token, 
     493            }, 
     494            'verify': { 
     495                'link': self.env.abs_href.verify_email(token=token), 
     496            } 
     497        }) 
     498 
     499        projname = self.config.get('project', 'name') 
     500        subject = '[%s] Trac email verification for user: %s' % (projname, username) 
     501 
     502        SingleUserNofification.notify(self, username, subject) 
     503 
     504 
     505class EmailVerificationModule(Component): 
     506    implements(IRequestFilter, IRequestHandler) 
     507 
     508    # IRequestFilter methods 
     509 
     510    def pre_process_request(self, req, handler): 
     511        if handler is not self and 'email_verification_token' in req.session: 
     512            chrome.add_warning(req, MessageWrapper(tag.span( 
     513                    'Your permissions have been limited until you ', 
     514                    tag.a(href=req.href.verify_email())( 
     515                          'verify your email address')))) 
     516            req.perm = perm.PermissionCache(self.env, 'anonymous') 
     517        return handler 
     518 
     519    def post_process_request(self, req, template, data, content_type): 
     520        if req.session.get('email') != req.session.get('email_verification_sent_to'): 
     521            req.session['email_verification_token'] = self._gen_token() 
     522            req.session['email_verification_sent_to'] = req.session.get('email') 
     523            self._send_email(req) 
     524            chrome.add_notice(req, MessageWrapper(tag.span( 
     525                    'An email has been sent to ', req.session['email'], 
     526                    ' with a token to ', 
     527                    tag.a(href=req.href.verify_email())( 
     528                        'verify your new email address')))) 
     529        return template, data, content_type 
     530 
     531    # IRequestHandler methods 
     532 
     533    def match_request(self, req): 
     534        return req.path_info == '/verify_email' 
     535 
     536    def process_request(self, req): 
     537        if 'email_verification_token' not in req.session: 
     538            chrome.add_notice(req, 'Your email is already verified') 
     539        elif req.method != 'POST': 
     540            pass 
     541        elif 'resend' in req.args: 
     542            self._send_email(req) 
     543            chrome.add_notice(req, 
     544                    'A notification email has been resent to %s.', 
     545                    req.session.get('email')) 
     546        elif 'verify' in req.args: 
     547            if req.args['token'] == req.session['email_verification_token']: 
     548                del req.session['email_verification_token'] 
     549                chrome.add_notice(req, 'Thank you for verifying your email address') 
     550            else: 
     551                chrome.add_warning(req, 'Invalid verification token') 
     552        data = {} 
     553        if 'token' in req.args: 
     554            data['token'] = req.args['token'] 
     555        return 'verify_email.html', data, None 
     556 
     557    def _gen_token(self): 
     558        return base64.urlsafe_b64encode(urandom(6)) 
     559 
     560    def _send_email(self, req): 
     561        notifier = EmailVerificationNotification(self.env) 
     562        notifier.notify(req.authname, req.session['email_verification_token'])