Ticket #442: verify_account_registrations.patch

File verify_account_registrations.patch, 30.3 kB (added by s0undt3ch, 7 months ago)
  • a/acct_mgr/admin.py

    old new  
    1616from trac.perm import PermissionSystem 
    1717from trac.util import sorted 
    1818from trac.util.datefmt import format_datetime 
    19 from trac.web.chrome import ITemplateProvider 
     19from trac.web.chrome import ITemplateProvider, add_stylesheet 
    2020from trac.admin import IAdminPanelProvider 
    2121 
    2222from acct_mgr.api import AccountManager 
    2323from acct_mgr.web_ui import _create_user 
     24from acct_mgr.verify import VerifyAccountModule 
    2425 
    2526def _getoptions(cls): 
    2627    if isinstance(cls, Component): 
     
    4243            yield ('accounts', 'Accounts', 'users', 'Users') 
    4344 
    4445    def render_admin_panel(self, req, cat, page, path_info): 
     46        add_stylesheet(req, 'acct_mgr/admin_acct_mgr.css') 
    4547        if page == 'config': 
    4648            return self._do_config(req) 
    4749        elif page == 'users': 
    4850            return self._do_users(req) 
    4951 
    5052    def _do_config(self, req): 
     53 
    5154        if req.method == 'POST': 
    5255            selected_class = req.args.get('selected') 
    5356            self.config.set('account-manager', 'password_store', selected_class) 
     
    5962                    self.config.save() 
    6063            self.config.set('account-manager', 'force_passwd_change', 
    6164                            req.args.get('force_passwd_change')) 
     65            if 'verify_accounts' in req.args: 
     66                self.config.set('account-manager', 'verify_accounts', 
     67                                req.args.get('verify_accounts')) 
    6268            self.config.save() 
    6369 
    6470 
     
    8086            } for store in self.account_manager.stores 
    8187        ] 
    8288        sections = sorted(sections, key=lambda i: i['name']) 
     89        verify_acct_enabled = self.env.is_component_enabled(VerifyAccountModule) 
    8390        data = {'sections': sections, 
    84                 'force_passwd_change': self.account_manager.force_passwd_change} 
     91                'force_passwd_change': self.account_manager.force_passwd_change, 
     92                'verify_accounts_enabled': verify_acct_enabled, 
     93                'verify_accounts': self.account_manager.verify_accounts} 
    8594        return 'admin_accountsconfig.html', data 
    8695 
    8796    def _do_users(self, req): 
     
    8998        listing_enabled = self.account_manager.supports('get_users') 
    9099        create_enabled = self.account_manager.supports('set_password') 
    91100        delete_enabled = self.account_manager.supports('delete_user') 
     101        verify_accounts = self.account_manager.verify_accounts 
     102        verify_acct_enabled = self.env.is_component_enabled(VerifyAccountModule) 
    92103 
    93104        data = { 
    94105            'listing_enabled': listing_enabled, 
    95106            'create_enabled': create_enabled, 
    96107            'delete_enabled': delete_enabled, 
     108            'verify_accounts': verify_accounts, 
     109            'verify_accounts_enabled': verify_acct_enabled 
    97110        } 
    98111 
    99112        if req.method == 'POST': 
     
    136149                if account and last_visit: 
    137150                    account['last_visit'] = format_datetime(last_visit) 
    138151 
     152            if verify_accounts: 
     153                cursor.execute("SELECT sid,value FROM session_attribute " 
     154                               "WHERE name=%s AND authenticated=1", 
     155                               ('verified',)) 
     156                for username, verified in cursor: 
     157                    account = accounts.get(username) 
     158                    if account: 
     159                        account['verified'] = bool(int(verified)) 
     160 
    139161            data['accounts'] = sorted(accounts.itervalues(), 
    140162                                      key=lambda acct: acct['username']) 
    141163 
     
    147169        """Return the absolute path of a directory containing additional 
    148170        static resources (such as images, style sheets, etc). 
    149171        """ 
    150         return [] 
     172        from pkg_resources import resource_filename 
     173        return [('acct_mgr', resource_filename(__name__, 'htdocs'))] 
    151174 
    152175    def get_templates_dirs(self): 
    153176        """Return the absolute path of the directory containing the provided 
  • a/acct_mgr/api.py

    old new  
    8888    force_passwd_change = BoolOption('account-manager', 'force_passwd_change', 
    8989                                     True, doc="Forge the user to change " 
    9090                                     "password when it's reset.") 
     91    verify_accounts = BoolOption('account-manager', 'verify_accounts', True, 
     92                                 "Force user to verify their email address") 
    9193 
    9294    # Public API 
    9395 
  • /dev/null

    old new  
     1from trac.db import Table, Column 
     2 
     3name = 'acct_mgr.verify_accounts' 
     4version = 1 
     5tables = [ 
     6    Table('acct_mgr_verify_accounts', key=('ts', 'sid', 'hash'))[ 
     7        Column('ts'), 
     8        Column('sid'), 
     9        Column('hash') 
     10    ] 
     11] 
     12 
     13def current_usernames_to_confirmed_state(data): 
     14    "Update current sid's to a verified state" 
     15    if 'session_attribute' in data: 
     16        colnames, coldata = data['session_attribute'] 
     17        ndata = [] 
     18        sids = [] 
     19        for sid, authenticated, name, value in coldata: 
     20            ndata.append((sid, authenticated, name, value)) 
     21            if sid not in sids and authenticated: 
     22                sids.append(sid) 
     23                ndata.append((sid, authenticated, 'verified', 1)) 
     24        data['session_attribute'] = (colnames, ndata) 
     25 
     26migrations = [ 
     27    (xrange(0, 1), current_usernames_to_confirmed_state) 
     28] 
  • /dev/null

    old new  
     1.verified { 
     2  background-color: #bcecaf; 
     3} 
     4 
     5.unverified { 
     6  background-color: #ffc1c1; 
     7} 
  • /dev/null

    old new  
     1You, or someone posing as you, has just registered into 
     2${project.name} Trac environment. 
     3 
     4To verify your email address please follow the following link: 
     5 
     6  ${hash.link} 
     7 
     8 
     9 
     10If the above link got broken because it's too long, go to: 
     11 
     12   ${hash.url} 
     13 
     14And the hash value to fill is: 
     15 
     16  ${hash.value} 
     17 
     18-- 
     19${project.name} <${project.url}> 
     20${project.descr} 
     21 
  • a/acct_mgr/templates/admin_accountsconfig.html

    old new  
    1010  </head> 
    1111 
    1212  <body> 
    13     <h2>Accounts: Configuration</h2> 
     13    <h2>Accounts: General Configuration</h2> 
    1414 
    1515    <form id="accountsconfig" class="mod" method="post"> 
     16 
     17      <fieldset py:if="verify_accounts_enabled"> 
     18        <legend>New Accounts</legend> 
     19        <label for="verify_accounts"> 
     20          Verify email addresses on new accounts? 
     21        </label> 
     22        <input type="radio" name="verify_accounts" value="true" 
     23          checked="${verify_accounts and 'checked' or None}">Yes</input> 
     24        <input type="radio" name="verify_accounts" value="false" 
     25          checked="${not verify_accounts and 'checked' or None}">No</input> 
     26      </fieldset> 
     27 
     28      <fieldset> 
     29        <legend>Password Reset</legend> 
     30        <label for="force_passwd_change"> 
     31          Force users to change passwords after a password reset? 
     32        </label> 
     33        <input type="radio" name="force_passwd_change" value="true" 
     34          checked="${force_passwd_change and 'checked' or None}">Yes</input> 
     35        <input type="radio" name="force_passwd_change" value="false" 
     36          checked="${not force_passwd_change and 'checked' or None}">No</input> 
     37      </fieldset> 
     38 
     39      <div class="buttons"> 
     40        <input type="submit" name="save" value="Save" /> 
     41      </div> 
     42 
     43    <hr/> 
     44    <h2>Accounts: Stores Configuration</h2> 
     45 
    1646      <fieldset py:for="section in sections"> 
    1747        <legend> 
    1848          <label> 
     
    2959          </label> 
    3060        </div> 
    3161      </fieldset> 
    32       <fieldset> 
    33         <legend>Password Reset</legend> 
    34         <label for="force_passwd_change"> 
    35           Force users to change passwords after a password reset? 
    36         </label> 
    37         <input type="radio" name="force_passwd_change" value="true" 
    38           checked="${force_passwd_change and 'checked' or None}">Yes</input> 
    39         <input type="radio" name="force_passwd_change" value="false" 
    40           checked="${not force_passwd_change and 'checked' or None}">No</input> 
    41       </fieldset> 
    4262      <div class="buttons"> 
    4363        <input type="submit" name="save" value="Save" /> 
    4464      </div> 
  • a/acct_mgr/templates/admin_users.html

    old new  
    5656          </tr> 
    5757        </thead> 
    5858        <tbody> 
    59           <tr py:for="account in accounts"> 
     59          <tr py:for="account in accounts" 
     60            class="${verify_accounts_enabled and ( 
     61                       verify_accounts and ( 
     62                         account.verified and 'verified' or 'unverified') 
     63                       or None) 
     64                     or None}" 
     65            title="${verify_accounts_enabled and ( 
     66                       verify_accounts and ( 
     67                         account.verified and 
     68                           'Account with confirmed email address' or 
     69                           'Account with un-confirmed email address') 
     70                       or None) 
     71                     or None}"> 
    6072            <td py:if="delete_enabled"> 
    6173              <input type="checkbox" name="sel" value="${account.username}" /> 
    6274            </td> 
  • a/acct_mgr/templates/register.html

    old new  
    1515  </head> 
    1616 
    1717  <body> 
     18    <py:def function="email_field_output"> 
     19      <label>Email: 
     20        <input type="text" name="email" class="textwidget" size="20" 
     21          value="${defined('email') and email or None}"/> 
     22      </label> 
     23      <p py:if="reset_password_enabled">Entering your email address will 
     24      enable you to reset your password if you ever forget it.</p> 
     25    </py:def> 
    1826    <div id="content" class="register"> 
    1927      <h1>Register an account</h1> 
    2028 
     
    2937          <div> 
    3038            <input type="hidden" name="action" value="create" /> 
    3139            <label>Username: 
    32               <input type="text" name="user" class="textwidget" size="20" /> 
     40              <input type="text" name="user" class="textwidget" size="20" 
     41                value="${defined('user') and user or None}"/> 
    3342            </label> 
    3443          </div> 
    3544          <div> 
    3645            <label>Password: 
    37               <input type="password" name="password" class="textwidget" size="20" /> 
     46              <input type="password" name="password" class="textwidget" 
     47                size="20" value="${defined('password') and password or None}"/> 
    3848            </label> 
    3949          </div> 
    4050          <div> 
    4151            <label>Confirm Password: 
    42               <input type="password" name="password_confirm" 
    43                      class="textwidget" size="20" /> 
     52              <input type="password" name="password_confirm" class="textwidget" 
     53                size="20" value="${defined('password_confirm') and password_confirm or None}"/> 
    4454            </label> 
     55          </div> 
     56          <div py:if="verify_accounts"> 
     57            ${ email_field_output() } 
    4558          </div> 
    4659        </fieldset> 
    4760        <fieldset> 
    4861          <legend>Optional</legend> 
    4962          <div> 
    5063            <label>Name: 
    51               <input type="text" name="name" class="textwidget" size="20" /> 
     64              <input type="text" name="name" class="textwidget" size="20" 
     65                value="${defined('name') and name or None}"/> 
    5266            </label> 
    5367          </div> 
    54           <div> 
    55             <label>Email: 
    56               <input type="text" name="email" class="textwidget" size="20" /> 
    57             </label> 
    58             <p py:if="reset_password_enabled">Entering your email address will 
    59             enable you to reset your password if you ever forget it.</p> 
     68          <div py:if="not verify_accounts"> 
     69            ${ email_field_output() } 
    6070          </div> 
    6171        </fieldset> 
    6272        <input type="submit" value="Create account" /> 
  • /dev/null

    old new  
     1<!DOCTYPE html 
     2    PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" 
     3    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> 
     4<html xmlns="http://www.w3.org/1999/xhtml" 
     5      xmlns:py="http://genshi.edgewall.org/" 
     6      xmlns:xi="http://www.w3.org/2001/XInclude"> 
     7  <xi:include href="layout.html" /> 
     8  <head> 
     9    <title>Verify Account</title> 
     10  </head> 
     11  <body> 
     12    <div class="system-message" py:if="message"> 
     13      <h2>Error</h2> 
     14      <p>$message</p> 
     15    </div> 
     16    <form action="" method="POST" py:if="not verified"> 
     17      <fieldset> 
     18        <legend>Verify Account</legend> 
     19        <div> 
     20          <p><b>Please fill in the <em>hash</em> that was sent to you by email</b></p> 
     21          <label for="hash">Hash:</label> 
     22          <input type="text" size="55" value="${defined('hash') and hash or None}" 
     23            name="hash" class="textwidgets"/> 
     24        </div> 
     25        <input type="submit" value="Verify account" /> 
     26      </fieldset> 
     27    </form> 
     28    <fieldset py:if="verified"> 
     29      <legend>Verified Account</legend> 
     30      <p>Your email address is verified.</p> 
     31      <p py:if="not req.session.authenticated">You can now 
     32      <a href="${req.href.login()}">login</a>.</p> 
     33    </fieldset> 
     34  </body> 
     35</html> 
  • /dev/null

    old new  
     1import sha 
     2import time 
     3import random 
     4 
     5 
     6from trac.core import * 
     7from trac.env import IEnvironmentSetupParticipant 
     8from trac.config import BoolOption 
     9from trac.web.api import ITemplateStreamFilter 
     10from trac.web.main import IRequestHandler, IRequestFilter 
     11from trac.notification import NotifyEmail 
     12from trac.util.translation import _ 
     13 
     14from genshi.filters.transform import Transformer 
     15from genshi.builder import tag 
     16 
     17from api import AccountManager 
     18import db_default 
     19 
     20def _build_hash(sid, env, req): 
     21    import sha, time 
     22    hash = sha.new(str(random.random())) 
     23    email = req.session.get('email', "") 
     24    now = int(time.time()) 
     25 
     26    hash.update(sid) 
     27    hash.update(str(random.random())) 
     28    hash.update(email) 
     29    hash.update(str(random.random())) 
     30    hash.update(str(now)) 
     31    hash.update(str(random.random())) 
     32 
     33    db = env.get_db_cnx() 
     34    cursor = db.cursor() 
     35 
     36    older_than = now - 86400 * 1 # Delete pending older than a day 
     37    cursor.execute("DELETE FROM acct_mgr_verify_accounts WHERE ts < %s", 
     38                   (older_than,)) 
     39    env.log.debug("Cleaning %s timed out email verifications", cursor.rowcount) 
     40    cursor.execute("INSERT INTO acct_mgr_verify_accounts (ts,sid,hash) " 
     41                   "VALUES (%s,%s,%s)", (now, sid, hash.hexdigest())) 
     42    cursor.execute("UPDATE session_attribute SET value=%s WHERE name=%s", 
     43                   (0, 'verified')) 
     44    if not cursor.rowcount: 
     45        cursor.execute("INSERT INTO session_attribute " 
     46                       "(sid,authenticated,name,value) VALUES (%s,%s,%s,%s)", 
     47                       (sid, 1, 'verified', 0)) 
     48    db.commit() 
     49    return hash.hexdigest() 
     50 
     51class AccountVerifyNotification(NotifyEmail): 
     52    template_name = 'account_verify_email.txt' 
     53    _username = None 
     54    _hash = None 
     55 
     56    def get_recipients(self, resid): 
     57        return ([resid],[]) 
     58 
     59    def get_smtp_address(self, addr): 
     60        """Overrides `get_smtp_address` in order to prevent CCing users 
     61        other than the user whose password is being reset. 
     62        """ 
     63        if addr == self._username: 
     64            return NotifyEmail.get_smtp_address(self, addr) 
     65        else: 
     66            return None 
     67 
     68    def notify(self, username, hash): 
     69        # save the username for use in `get_smtp_address` 
     70        self._username = username 
     71        self._hash = hash 
     72        self.data.update({ 
     73            'hash': { 
     74                'link': self.env.abs_href.verify_account(self._hash), 
     75                'url': self.env.abs_href.verify_account(), 
     76                'value': self._hash 
     77            } 
     78        }) 
     79 
     80        projname = self.config.get('project', 'name') 
     81        subject = _('[%(project_name)s] Trac Email Verification for user: ' 
     82                    '%(username)s', project_name=projname, username=username) 
     83 
     84        NotifyEmail.notify(self, username, subject) 
     85 
     86class VerifyAccountModule(Component): 
     87    implements(IRequestHandler, IRequestFilter, ITemplateStreamFilter, 
     88               IEnvironmentSetupParticipant) 
     89 
     90    def __init__(self): 
     91        self.verify_accounts = AccountManager(self.env).verify_accounts 
     92 
     93    # IEnvironmentSetupParticipant methods 
     94    def environment_created(self): 
     95        if self.verify_accounts: 
     96            self.found_db_version = 0 
     97            self.upgrade_environment(self.env.get_db_cnx()) 
     98 
     99    def environment_needs_upgrade(self, db): 
     100        if not self.verify_accounts: 
     101            return False 
     102        cursor = db.cursor() 
     103        cursor.execute("SELECT value FROM system WHERE name=%s", 
     104                       (db_default.name,)) 
     105        value = cursor.fetchone() 
     106        if not value: 
     107            self.found_db_version = 0 
     108            return True 
     109        else: 
     110            self.found_db_version = int(value[0]) 
     111            self.log.debug(_('AccountManager: Found db version ' 
     112                             '%(db_default_version)s, current is ' 
     113                             '%(system_version)s', 
     114                             db_default_version=self.found_db_version, 
     115                             system_version=db_default.version)) 
     116            return self.found_db_version < db_default.version 
     117 
     118    def upgrade_environment(self, db): 
     119        # 0.10 compatibility hack (thanks Alec) 
     120        try: 
     121            from trac.db import DatabaseManager 
     122            db_manager, _ = DatabaseManager(self.env)._get_connector() 
     123        except ImportError: 
     124            db_manager = db 
     125 
     126        from trac.db_default import schema as trac_schema 
     127 
     128        session_attribute = [ 
     129            s for s in trac_schema if s.name == 'session_attribute' 
     130        ] 
     131 
     132        # Insert the default table 
     133        old_data = {} # {table_name: (col_names, [row, ...]), ...} 
     134        cursor = db.cursor() 
     135        if not self.found_db_version: 
     136            cursor.execute("INSERT INTO system (name, value) VALUES (%s, %s)", 
     137                           (db_default.name, db_default.version)) 
     138            for tbl in session_attribute: 
     139                try: 
     140                    cursor.execute('SELECT * FROM %s'%tbl.name) 
     141                    old_data[tbl.name] = ([d[0] for d in cursor.description], 
     142                                          cursor.fetchall()) 
     143                    cursor.execute('DROP TABLE %s'%tbl.name) 
     144                except Exception, e: 
     145                    if 'OperationalError' not in e.__class__.__name__: 
     146                        # If it is an OperationalError, just move on to the 
     147                        # next table 
     148                        raise e 
     149        else: 
     150            cursor.execute("UPDATE system SET value=%s WHERE name=%s", 
     151                           (db_default.version, db_default.name)) 
     152            for tbl in db_default.tables + session_attribute: 
     153                try: 
     154                    cursor.execute('SELECT * FROM %s'%tbl.name) 
     155                    old_data[tbl.name] = ([d[0] for d in cursor.description], 
     156                                          cursor.fetchall()) 
     157                    cursor.execute('DROP TABLE %s'%tbl.name) 
     158                except Exception, e: 
     159                    if 'OperationalError' not in e.__class__.__name__: 
     160                        # If it is an OperationalError, just move on to the 
     161                        # next table 
     162                        raise e 
     163 
     164 
     165        for vers, migration in db_default.migrations: 
     166            if self.found_db_version in vers: 
     167                self.log.info(_('AccountManager: Running migration %(doc)s', 
     168                                doc=migration.__doc__)) 
     169                migration(old_data) 
     170 
     171        for tbl in db_default.tables + session_attribute: 
     172            for sql in db_manager.to_sql(tbl): 
     173                cursor.execute(sql) 
     174 
     175            # Try to reinsert any old data 
     176            if tbl.name in old_data: 
     177                data = old_data[tbl.name] 
     178                sql = 'INSERT INTO %s (%s) VALUES (%s)' % ( 
     179                    tbl.name, ','.join(data[0]),','.join(['%s'] * len(data[0])) 
     180                ) 
     181                for row in data[1]: 
     182                    try: 
     183                        cursor.execute(sql, row) 
     184                    except Exception, e: 
     185                        if 'OperationalError' not in e.__class__.__name__: 
     186                            raise 
     187                        else: 
     188                            self.log.debug(_('AccountManager: Masking exception' 
     189                                             ' %(exception)s', exception=e)) 
     190 
     191 
     192    # IRequestHandler methods 
     193    def match_request(self, req): 
     194        return (req.path_info.startswith('/verify_account') or 
     195                req.path_info.startswith('/verified_account')) 
     196 
     197    def process_request(self, req): 
     198        if req.path_info.startswith('/verify_account'): 
     199            return self._process_verify_account(req) 
     200        elif req.path_info.startswith('/verified_account'): 
     201            return self._process_verified_account(req) 
     202 
     203    # IRequestFilter methods 
     204    def pre_process_request(self, req, handler): 
     205        if not self.verify_accounts or not req.session.authenticated or \ 
     206            not self.env.is_component_enabled(VerifyAccountModule): 
     207            # Don't do anything is we're not verifying accounts; 
     208            # Don't trac anonymous users; 
     209            # Don't do anything if component is not enabled; 
     210            return handler 
     211        if req.path_info.startswith('/prefs'): 
     212            if not req.method == 'POST': 
     213                return handler 
     214            if not hasattr(self, '_old') and ('TRAC_ADMIN' not in req.perm): 
     215                # Don't track TRAC_ADMIN email changes 
     216                self._old = req.session._old.copy() 
     217        return handler 
     218 
     219    def post_process_request(self, req, template, data, content_type): 
     220        if not self.verify_accounts or not req.session.authenticated or \ 
     221            not self.env.is_component_enabled(VerifyAccountModule): 
     222            # Don't do anything is we're not verifying accounts; 
     223            # Don't trac anonymous users; 
     224            # Don't do anything if component is not enabled; 
     225            return (template, data, content_type) 
     226        if req.session.authenticated and ('TRAC_ADMIN' not in req.perm): 
     227            # Don't track TRAC_ADMIN email changes 
     228            if req.path_info.startswith('/verify_account'): 
     229                if req.session.get('verified') == '1': 
     230                    req.redirect(req.href.verified_account()) 
     231 
     232            elif req.path_info.startswith('/prefs') and self.verify_accounts: 
     233                if req.session.get('verified') == '0': 
     234                    req.redirect(req.href.verify_account()) 
     235 
     236                if not hasattr(self, '_old'): 
     237                    # No previous POST, prefs were not saved 
     238                    return (template, data, content_type) 
     239 
     240                req.session.save() # Just in case 
     241 
     242                if self._old['email'] != req.session.get('email'): 
     243                    # Email Changed. Need to re-verify address 
     244                    email = req.session.get('email') 
     245                    notifier = AccountVerifyNotification(self.env) 
     246                    hash = _build_hash(req.session.sid, self.env, req) 
     247                    if email == notifier.email_map.get(req.session.sid): 
     248                        notifier.notify(req.session.sid, hash) 
     249                    # Let user know that he has to verify his new email address 
     250                    del self._old 
     251                    return ('confirm_address_email_sent.html', data, 
     252                            content_type) 
     253            else: 
     254                if req.session.get('verified') == '0': 
     255                    req.redirect(req.href.verify_account()) 
     256        return (template, data, content_type) 
     257 
     258    # ITemplateStreamFilter 
     259    def filter_stream(self, req, method, filename, stream, data): 
     260        self.log.debug("On ITemplateStreamFilter: %s",req.session.authenticated) 
     261        if not self.verify_accounts or not req.session.authenticated or \ 
     262            not self.env.is_component_enabled(VerifyAccountModule): 
     263            # No point tweaking css if we're not using this module or 
     264            # if user is not authenticated. Are we actually going to verify 
     265            # anonymous users email addresses? 
     266            # Perhaps we should... But that would probably also mean finding a 
     267            # way to use that info, ie, only notify verified addresses. 
     268            return stream 
     269        if filename == 'prefs_general.html': 
     270            if req.session.get('verified') == '1': 
     271                return stream | Transformer("td/input[@id='email']").attr( 
     272                    'style', 'border-color: green;').after( 
     273                    tag(tag(" "), tag.span("verified address", 
     274                                           style="color: green;"))) 
     275            else: 
     276                return stream | Transformer("td/input[@id='email']").attr( 
     277                    'style', 'border-color: red;').after( 
     278                    tag(tag(" "), tag.span("unverified address", 
     279                                           style="color: red;"))) 
     280        return stream 
     281 
     282 
     283    # Internal Methods 
     284    def _process_verified_account(self, req): 
     285        data = {'verified': True} 
     286        return 'verify_account.html', data, None 
     287 
     288    def _process_verify_account(self, req): 
     289        verified = False 
     290        data = {} 
     291        def _activate_account(hash): 
     292            db = self.env.get_db_cnx() 
     293            cursor = db.cursor() 
     294            cursor.execute("SELECT sid from acct_mgr_verify_accounts " 
     295                           "WHERE hash=%s", (hash,)) 
     296            row = cursor.fetchone() 
     297            if row: 
     298                if req.session.authenticated: 
     299                    # In case user is authenticated, update session. 
     300                    req.session['verified'] = 1 
     301                    req.session.save() 
     302                else: 
     303                    # User ain't authenticated, update DB 
     304                    sid = row[0] 
     305                    cursor.execute("UPDATE session_attribute SET value=%s " 
     306                       "WHERE sid=%s AND name=%s",(1, sid, 'verified')) 
     307                cursor.execute("DELETE FROM acct_mgr_verify_accounts WHERE " 
     308                               "hash=%s", (hash,)) 
     309                data['verified'] = True 
     310                db.commit() 
     311                return True # Verified 
     312            else: 
     313                data['message'] = _("Address already verified? Hash not found.") 
     314            return False # Not verified 
     315 
     316        if req.method == 'POST': 
     317            hash = req.args.get('hash') 
     318        elif req.method == 'GET': 
     319            hash = req.path_info[len('/verify_account/'):] 
     320 
     321        if hash: 
     322            verified = _activate_account(hash) 
     323        data.update({'hash': hash, 
     324                     'verified': verified}) 
     325        if req.session.get('verified') == '0' and req.session.authenticated: 
     326            data['message'] = _("Please verify your email address. An email " 
     327                                "message was sent to you just for that") 
     328        return 'verify_account.html', data, None 
     329 
  • a/acct_mgr/web_ui.py

    old new  
    2323from trac.web.chrome import INavigationContributor, ITemplateProvider 
    2424from genshi.builder import tag 
    2525 
    26 from api import AccountManager 
     26from acct_mgr.api import AccountManager 
     27from acct_mgr.verify import AccountVerifyNotification, _build_hash 
     28 
     29 
    2730 
    2831def _create_user(req, env, check_permissions=True): 
    2932    mgr = AccountManager(env) 
     
    109112 
    110113        projname = self.config.get('project', 'name') 
    111114        subject = '[%s] Trac password reset for user: %s' % (projname, username) 
    112  
    113115        NotifyEmail.notify(self, username, subject) 
    114116 
    115117 
     
    345347        if req.authname != 'anonymous': 
    346348            req.redirect(req.href.prefs('account')) 
    347349        action = req.args.get('action') 
    348         data = {} 
     350        verify_accounts = AccountManager(self.env).verify_accounts 
     351        email_sent = False 
     352        data = {'verify_accounts': verify_accounts} 
    349353        if req.method == 'POST' and action == 'create': 
    350354            try: 
    351                 _create_user(req, self.env) 
     355                flabels = { 
     356                    'user': 'Username', 
     357                    'password': 'Password', 
     358                    'password_confirm': 'Confirm Password' 
     359                } 
     360                for argname in ('user', 'password', 'password_confirm'): 
     361                    arg = req.args.get(argname) 
     362                    if not arg: 
     363                        data.update(req.args) 
     364                        raise TracError("Missing the required '%s' field" % 
     365                                        flabels[argname]) 
     366                email = req.args.get('email') 
     367                if verify_accounts and email: 
     368                    _create_user(req, self.env) 
     369                elif verify_accounts and not email: 
     370                    data.update(req.args) 
     371                    raise TracError("Missing the required 'Email' field") 
     372                else: 
     373                    _create_user(req, self.env) 
     374                if verify_accounts: 
     375                    notifier = AccountVerifyNotification(self.env) 
     376                    username = req.args.get('user') 
     377                    hash = _build_hash(username, self.env, req) 
     378                    if email == notifier.email_map.get(username): 
     379                        notifier.notify(username, hash) 
     380                        return 'confirm_address_email_sent.html', data, None 
    352381            except TracError, e: 
    353382                data['registration_error'] = e.message 
    354383            else: 
     
    451480        """ 
    452481        from pkg_resources import resource_filename 
    453482        return [resource_filename(__name__, 'templates')] 
    454