def process_request(self, req): if not req.session.authenticated: chrome.add_warning(req, Markup(tag.span(tag_( "Please log in to finish email verification procedure."))) ) req.redirect(req.href.login()) if 'email_verification_token' not in req.session: chrome.add_notice(req, _("Your email is already verified.")) elif req.method == 'POST' and 'resend' in req.args: AccountManager(self.env)._notify( 'email_verification_requested', req.authname, req.session['email_verification_token'] ) chrome.add_notice(req, _("A notification email has been resent to <%s>."), req.session.get('email') ) elif 'verify' in req.args: # allow via POST or GET (the latter for email links) if req.args['token'] == req.session['email_verification_token']: del req.session['email_verification_token'] chrome.add_notice( req, _("Thank you for verifying your email address.") ) req.redirect(req.href.prefs()) else: chrome.add_warning(req, _("Invalid verification token")) data = {'_dgettext': dgettext} if 'token' in req.args: data['token'] = req.args['token'] if 'email_verification_token' not in req.session: data['button_state'] = { 'disabled': 'disabled' } return 'verify_email.html', data, None
def _do_account(self, req): assert (req.authname and req.authname != 'anonymous') action = req.args.get('action') delete_enabled = self.acctmgr.supports('delete_user') and \ self.acctmgr.allow_delete_account data = { 'delete_enabled': delete_enabled, 'delete_msg_confirm': _("Are you sure you want to delete your account?"), } force_change_password = req.session.get('force_change_passwd', False) if req.method == 'POST': if action == 'save': if self._do_change_password(req) and force_change_password: del req.session['force_change_passwd'] req.session.save() add_notice(req, _("Thank you for taking the time to " "update your password.")) force_change_password = False elif action == 'delete' and delete_enabled: self._do_delete(req) if force_change_password: add_warning(req, tag_( "You are required to change password because of a recent " "password change request. %(invitation)s", invitation=tag.b(_("Please change your password now.")))) return data
def process_request(self, req): if not req.session.authenticated: chrome.add_warning(req, tag_( "Please log in to finish email verification procedure.")) req.redirect(req.href.login()) if 'email_verification_token' not in req.session: chrome.add_notice(req, _("Your email is already verified.")) elif req.method == 'POST' and 'resend' in req.args: try: AccountManager(self.env)._notify( 'email_verification_requested', req.authname, req.session['email_verification_token'] ) except NotificationError, e: chrome.add_warning(req, _("Error raised while sending a " "change notification.") + _( "You should " "report that issue to a Trac admin.")) self.log.error('Unable to send verification notification: %s', exception_to_unicode(e, traceback=True)) else: chrome.add_notice(req, _("A notification email has been " "resent to <%s>."), req.session.get('email'))
def _do_account(self, req): assert(req.authname and req.authname != 'anonymous') action = req.args.get('action') delete_enabled = self.acctmgr.supports('delete_user') and \ self.acctmgr.allow_delete_account data = {'delete_enabled': delete_enabled, 'delete_msg_confirm': _( "Are you sure you want to delete your account?"), } force_change_password = req.session.get('force_change_passwd', False) if req.method == 'POST': if action == 'save': data.update(self._do_change_password(req)) if force_change_password: del(req.session['force_change_passwd']) req.session.save() chrome.add_notice(req, Markup(tag.span(tag_( "Thank you for taking the time to update your password." )))) force_change_password = False elif action == 'delete' and delete_enabled: data.update(self._do_delete(req)) else: data.update({'error': 'Invalid action'}) if force_change_password: chrome.add_warning(req, Markup(tag.span(_( "You are required to change password because of a recent " "password change request. "), tag.b(_("Please change your password now."))))) return data
def process_request(self, req): if not req.session.authenticated: chrome.add_warning( req, Markup( tag.span( tag_( "Please log in to finish email verification procedure." )))) req.redirect(req.href.login()) if 'email_verification_token' not in req.session: chrome.add_notice(req, _("Your email is already verified.")) elif req.method == 'POST' and 'resend' in req.args: AccountManager(self.env)._notify( 'email_verification_requested', req.authname, req.session['email_verification_token']) chrome.add_notice( req, _("A notification email has been resent to <%s>."), req.session.get('email')) elif 'verify' in req.args: # allow via POST or GET (the latter for email links) if req.args['token'] == req.session['email_verification_token']: del req.session['email_verification_token'] chrome.add_notice( req, _("Thank you for verifying your email address.")) req.redirect(req.href.prefs()) else: chrome.add_warning(req, _("Invalid verification token")) data = {'_dgettext': dgettext} if 'token' in req.args: data['token'] = req.args['token'] if 'email_verification_token' not in req.session: data['button_state'] = {'disabled': 'disabled'} return 'verify_email.html', data, None
def post_process_request(self, req, template, data, content_type): if template is None or not req.session.authenticated: # Don't start the email verification procedure on anonymous users. return template, data, content_type email = req.session.get('email') # Only send verification if the user entered an email address. if self.verify_email and self.email_enabled is True and email and \ email != req.session.get('email_verification_sent_to') and \ 'ACCTMGR_ADMIN' not in req.perm: req.session['email_verification_token'] = self._gen_token() req.session['email_verification_sent_to'] = email try: AccountManager(self.env)._notify( 'email_verification_requested', req.authname, req.session['email_verification_token'] ) except NotificationError, e: chrome.add_warning(req, _( "Error raised while sending a change notification." ) + _("You should report that issue to a Trac admin.")) self.log.error('Unable to send registration notification: %s', exception_to_unicode(e, traceback=True)) else: # TRANSLATOR: An email has been sent to <%(email)s> # with a token to ... (the link label for following message) link = tag.a(_("verify your new email address"), href=req.href.verify_email()) # TRANSLATOR: ... verify your new email address chrome.add_notice(req, tag_( "An email has been sent to <%(email)s> with a token to " "%(link)s.", email=tag(email), link=link))
def render_registration_fields(self, req, data): """Add a hidden text input field to the registration form, and a visible one with mandatory input as well, if token is configured. """ if self.reg_basic_token: # Preserve last input for editing on failure instead of typing # everything again. old_value = req.args.get('basic_token', '') if self.reg_basic_question: # TRANSLATOR: Question-style hint for visible bot trap # registration input field. hint = tag.p(_("Please answer above: %(question)s", question=self.reg_basic_question), class_='hint') else: # TRANSLATOR: Verbatim token hint for visible bot trap # registration input field. hint = tag.p(tag_( "Please type [%(token)s] as verification token, " "exactly replicating everything within the braces.", token=tag.b(self.reg_basic_token)), class_='hint') insert = tag( tag.label(_("Parole:"), tag.input(type='text', name='basic_token', size=20, class_='textwidget', value=old_value)), hint) else: insert = None # TRANSLATOR: Registration form hint for hidden bot trap input field. insert = tag(insert, tag.input(type='hidden', name='sentinel', title=_("Better do not fill this field."))) return insert, data
def validate_registration(self, req): if req.path_info == '/prefs': return acctmgr = AccountManager(self.env) username = acctmgr.handle_username_casing( req.args.get('username', '').strip()) if not username: raise RegistrationError(N_("Username cannot be empty.")) # Always exclude some special characters, i.e. # ':' can't be used in HtPasswdStore # '[' and ']' can't be used in SvnServePasswordStore blacklist = acctmgr.username_char_blacklist if contains_any(username, blacklist): pretty_blacklist = '' for c in blacklist: if pretty_blacklist == '': pretty_blacklist = tag(' \'', tag.b(c), '\'') else: pretty_blacklist = tag(pretty_blacklist, ', \'', tag.b(c), '\'') raise RegistrationError(N_( "The username must not contain any of these characters: %s"), tag.b(pretty_blacklist) ) # All upper-cased names are reserved for permission action names. if username.isupper(): raise RegistrationError(N_("A username with only upper-cased " "characters is not allowed.")) # Prohibit some user names, that are important for Trac and therefor # reserved, even if not in the permission store for some reason. if username.lower() in ['anonymous', 'authenticated']: raise RegistrationError(N_("Username %s is not allowed."), tag.b(username)) # NOTE: A user may exist in a password store but not in the permission # store. I.e. this happens, when the user (from the password store) # never logged in into Trac. So we have to perform this test here # and cannot just check for the user being in the permission store. # And better obfuscate whether an existing user or group name # was responsible for rejection of this user name. for store_user in acctmgr.get_users(): # Do it carefully by disregarding case. if store_user.lower() == username.lower(): raise RegistrationError(tag_( "Another account or group already exists, who's name " "differs from %(username)s only by case or is identical.", username=tag.b(username))) # Password consistency checks follow. password = req.args.get('password') if not password: raise RegistrationError(N_("Password cannot be empty.")) elif password != req.args.get('password_confirm'): raise RegistrationError(N_("The passwords must match."))
def process_request(self, req): data = dict(_dgettext=dgettext) if req.authname and req.authname != 'anonymous': add_notice(req, Markup(tag_( "You're already logged in. If you need to change your " "password please use the %(prefs_href)s page.", prefs_href=tag.a(_("Account Preferences"), href=req.href.prefs('account'))))) data['authenticated'] = True if req.method == 'POST': self._do_reset_password(req) return 'reset_password.html', data, None
def process_request(self, req): data = dict(_dgettext=dgettext) if req.authname and req.authname != 'anonymous': add_notice(req, tag_( "You're already logged in. If you need to change your " "password please use the %(prefs_href)s page.", prefs_href=tag.a(_("Account Preferences"), href=req.href.prefs('account')))) data['authenticated'] = True if req.method == 'POST': self._do_reset_password(req) return 'reset_password.html', data, None
def validate_registration(self, req): if req.authname and req.authname != 'anonymous': return username = AccountManager(self.env).handle_username_casing( req.args.get('username', '').strip()) # NOTE: We can't use 'get_user_permissions(username)' here # as this always returns a list - even if the user doesn't exist. # In this case the permissions of "anonymous" are returned. # # Also note that we can't simply compare the result of # 'get_user_permissions(username)' to some known set of permission, # i.e. "get_user_permissions('authenticated') as this is always # false when 'username' is the name of an existing permission group. # # And again obfuscate whether an existing user or group name # was responsible for rejection of this username. for (perm_user, perm_action) in \ perm.PermissionSystem(self.env).get_all_permissions(): if perm_user.lower() == username.lower(): raise RegistrationError(tag_( "Another account or group already exists, who's name " "differs from %(username)s only by case or is identical.", username=tag.b(username)))
def _do_acct_details(self, req): username = req.args.get('user') if not username: # Accessing user account details without username is not useful, # so we revert such request immediately. add_warning(req, Markup(tag.span(tag_( "Please choose account by username from list to proceed." )))) req.redirect(req.href.admin('accounts', 'users')) acctmgr = self.acctmgr guard = self.guard if req.args.get('update'): req.redirect(req.href.admin('accounts', 'users', user=username)) elif req.args.get('delete') or req.args.get('release'): # delete failed login attempts, evaluating attempts count if guard.failed_count(username, reset=True) > 0: add_notice(req, Markup(tag.span(Markup(_( "Failed login attempts for user %(user)s deleted", user=tag.b(username) ))))) elif req.args.get('list'): req.redirect(req.href.admin('accounts', 'users')) data = {'_': _, 'user': username, } stores = StoreOrder(stores=acctmgr.stores, list=acctmgr.password_store) user_store = acctmgr.find_user_store(username) if not user_store is None: data['user_store'] = user_store.__class__.__name__ data['store_order_num'] = stores[user_store] data['ignore_auth_case'] = \ self.config.getbool('trac', 'ignore_auth_case') for username_, name, email in self.env.get_known_users(): if username_ == username: data['name'] = name if email: data['email'] = email break ts_seen = acctmgr.last_seen(username) if ts_seen is not None: data['last_visit'] = format_datetime(ts_seen[0][1], tzinfo=req.tz) attempts = [] attempts_count = guard.failed_count(username, reset = None) if attempts_count > 0: for attempt in guard.get_failed_log(username): t = format_datetime(to_datetime( attempt['time']), tzinfo=req.tz) attempts.append({'ipnr': attempt['ipnr'], 'time': t}) data['attempts'] = attempts data['attempts_count'] = attempts_count data['pretty_lock_time'] = guard.pretty_lock_time(username, next=True) data['lock_count'] = guard.lock_count(username) if guard.user_locked(username) is True: data['user_locked'] = True data['release_time'] = guard.pretty_release_time(req, username) if is_enabled(self.env, EmailVerificationModule) and \ acctmgr.verify_email is True: data['verification'] = 'enabled' data['email_verified'] = acctmgr.email_verified(username, email) self.log.debug('AcctMgr:admin:_do_acct_details for user \"' + \ username + '\", email \"' + str(email) + '\": ' + \ str(data['email_verified'])) add_stylesheet(req, 'acct_mgr/acct_mgr.css') data['url'] = req.href.admin('accounts', 'users', user=username) return 'account_details.html', data
def expand_macro(self, formatter, name, content): env = formatter.env req = formatter.req if not content: args = [] kw = {} else: args, kw = parse_args(content) if name == 'ProjectStats': if 'wiki' in kw.keys(): prefix = 'prefix' in kw.keys() and kw['prefix'] or None wiki = WikiSystem(env) if kw['wiki'] == 'count' or 'count' in args: return tag(len(list(wiki.get_pages(prefix)))) elif name == 'UserQuery': msg_no_perm = tag.p(tag_("(required %(perm)s missing)", perm=tag.strong('USER_VIEW')), class_='hint') if 'perm' in kw.keys(): perm_sys = PermissionSystem(self.env) users = perm_sys.get_users_with_permission(kw['perm'].upper()) else: acct_mgr = AccountManager(env) users = list(set(acct_mgr.get_users())) if 'locked' in kw.keys() or 'locked' in args: guard = AccountGuard(env) locked = [] for user in users: if guard.user_locked(user): locked.append(user) if kw.get('locked', 'True').lower() in ('true', 'yes', '1'): users = locked else: users = list(set(users) - set(locked)) elif 'visit' in kw.keys() or 'visit' in args: if 'USER_VIEW' not in req.perm: return msg_no_perm cols = [] data = {'accounts': fetch_user_data(env, req), 'cls': 'wiki'} for col in ('email', 'name'): if col in args: cols.append(col) data['cols'] = cols return Chrome(env).render_template( req, 'user_table.html', data, 'text/html', True) if kw.get('format') == 'count' or 'count' in args: return tag(len(users)) if 'USER_VIEW' not in req.perm: return msg_no_perm if 'email' in args or 'name' in args: # Replace username with full name, add email if available. for username, name, email in self.env.get_known_users(): if username in users: if 'name' not in args or name is None: name = username if 'email' in args and email is not None: email = ''.join(['<', email, '>']) name = ' '.join([name, email]) if not username == name: users.pop(users.index(username)) users.append(name) if not users and 'nomatch' in kw.keys(): return format_to_oneliner(env, formatter.context, kw['nomatch']) users = sorted(users) if kw.get('format') == 'list': return tag.ul([tag.li(Chrome(env).format_author(req, user)) for user in users]) else: # Default output format: comma-separated list. return tag(', '.join([Chrome(env).format_author(req, user) for user in users]))
def _do_acct_details(self, req): username = req.args.get('user') if not username: # Accessing user account details without username is not useful, # so we revert such request immediately. add_warning( req, Markup( tag.span( tag_( "Please choose account by username from list to proceed." )))) req.redirect(req.href.admin('accounts', 'users')) acctmgr = self.acctmgr guard = self.guard if req.args.get('update'): req.redirect(req.href.admin('accounts', 'users', user=username)) elif req.args.get('delete') or req.args.get('release'): # delete failed login attempts, evaluating attempts count if guard.failed_count(username, reset=True) > 0: add_notice( req, Markup( tag.span( Markup( _("Failed login attempts for user %(user)s deleted", user=tag.b(username)))))) elif req.args.get('list'): req.redirect(req.href.admin('accounts', 'users')) data = { '_dgettext': dgettext, 'user': username, } stores = StoreOrder(stores=acctmgr.stores, list=acctmgr.password_store) user_store = acctmgr.find_user_store(username) if not user_store is None: data['user_store'] = user_store.__class__.__name__ data['store_order_num'] = stores[user_store] data['ignore_auth_case'] = \ self.config.getbool('trac', 'ignore_auth_case') for username_, name, email in self.env.get_known_users(): if username_ == username: data['name'] = name if email: data['email'] = email break ts_seen = last_seen(self.env, username) if ts_seen is not None: data['last_visit'] = format_datetime(ts_seen[0][1], tzinfo=req.tz) attempts = [] attempts_count = guard.failed_count(username, reset=None) if attempts_count > 0: for attempt in guard.get_failed_log(username): t = format_datetime(to_datetime(attempt['time']), tzinfo=req.tz) attempts.append({'ipnr': attempt['ipnr'], 'time': t}) data['attempts'] = attempts data['attempts_count'] = attempts_count data['pretty_lock_time'] = guard.pretty_lock_time(username, next=True) data['lock_count'] = guard.lock_count(username) if guard.user_locked(username) is True: data['user_locked'] = True data['release_time'] = guard.pretty_release_time(req, username) if is_enabled(self.env, EmailVerificationModule) and \ acctmgr.verify_email is True: data['verification'] = 'enabled' data['email_verified'] = email_verified(self.env, username, email) self.log.debug('AcctMgr:admin:_do_acct_details for user \"' + \ username + '\", email \"' + str(email) + '\": ' + \ str(data['email_verified'])) add_stylesheet(req, 'acct_mgr/acct_mgr.css') data['url'] = req.href.admin('accounts', 'users', user=username) return 'account_details.html', data
def expand_macro(self, formatter, name, content): env = formatter.env req = formatter.req if not content: args = [] kw = {} else: args, kw = parse_args(content) if name == 'ProjectStats': if 'wiki' in kw.keys(): prefix = 'prefix' in kw.keys() and kw['prefix'] or None wiki = WikiSystem(env) if kw['wiki'] == 'count' or 'count' in args: return tag(len(list(wiki.get_pages(prefix)))) elif name == 'UserQuery': msg_no_perm = tag.p(tag_("(required %(perm)s missing)", perm=tag.strong('USER_VIEW')), class_='hint') if 'perm' in kw.keys(): perm_sys = PermissionSystem(self.env) users = perm_sys.get_users_with_permission(kw['perm'].upper()) else: acct_mgr = AccountManager(env) users = list(set(acct_mgr.get_users())) if 'locked' in kw.keys() or 'locked' in args: guard = AccountGuard(env) locked = [] for user in users: if guard.user_locked(user): locked.append(user) if kw.get('locked', 'True').lower() in ('true', 'yes', '1'): users = locked else: users = list(set(users) - set(locked)) elif 'visit' in kw.keys() or 'visit' in args: if 'USER_VIEW' not in req.perm: return msg_no_perm cols = [] data = {'accounts': fetch_user_data(env, req), 'cls': 'wiki'} for col in ('email', 'name'): if col in args: cols.append(col) data['cols'] = cols return Chrome(env).render_template(req, 'user_table.html', data, 'text/html', True) if kw.get('format') == 'count' or 'count' in args: return tag(len(users)) if 'USER_VIEW' not in req.perm: return msg_no_perm if 'email' in args or 'name' in args: # Replace username with full name, add email if available. for username, name, email in self.env.get_known_users(): if username in users: if 'name' not in args or name is None: name = username if 'email' in args and email is not None: email = ''.join(['<', email, '>']) name = ' '.join([name, email]) if not username == name: users.pop(users.index(username)) users.append(name) if not users and 'nomatch' in kw.keys(): return format_to_oneliner(env, formatter.context, kw['nomatch']) users = sorted(users) if kw.get('format') == 'list': return tag.ul([ tag.li(Chrome(env).format_author(req, user)) for user in users ]) else: # Default output format: comma-separated list. return tag(', '.join( [Chrome(env).format_author(req, user) for user in users]))
class EmailVerificationModule(CommonTemplateProvider): """Performs email verification on every new or changed address. A working email sender for Trac (!TracNotification or !TracAnnouncer) is strictly required to enable this module's functionality. Anonymous users should register and perms should be tweaked, so that anonymous users can't edit wiki pages and change or create tickets. So this email verification code won't be used on them. """ implements(IRequestFilter, IRequestHandler) verify_email = BoolOption( 'account-manager', 'verify_email', True, doc="Verify the email address of Trac users.") def __init__(self, *args, **kwargs): self.email_enabled = True if self.config.getbool('announcer', 'email_enabled') and \ self.config.getbool('notification', 'smtp_enabled'): self.email_enabled = False if self.env.is_enabled(self.__class__): self.log.warning("%s can't work because of missing email " "setup.", self.__class__.__name__) # IRequestFilter methods def pre_process_request(self, req, handler): if not req.authname or req.authname == 'anonymous': # Permissions for anonymous users remain unchanged. return handler elif req.path_info == '/prefs' and \ req.method == 'POST' and \ 'restore' not in req.args and \ req.get_header( 'X-Requested-With') != 'XMLHttpRequest': try: AccountManager(self.env).validate_account(req) # Check passed without error: New email address seems good. except RegistrationError, e: # Always warn about issues. chrome.add_warning(req, e) # Look, if the issue existed before. attributes = get_user_attribute(self.env, req.authname, attribute='email') email = req.authname in attributes and \ attributes[req.authname][1].get('email') or None new_email = req.args.get('email', '').strip() if (email or new_email) and email != new_email: # Attempt to change email to an empty or invalid # address detected, resetting to previously stored value. req.redirect(req.href.prefs(None)) if self.verify_email and handler is not self and \ 'email_verification_token' in req.session and \ 'ACCTMGR_ADMIN' not in req.perm: # TRANSLATOR: Your permissions have been limited until you ... link = tag.a(_("verify your email address"), href=req.href.verify_email()) # TRANSLATOR: ... verify your email address chrome.add_warning(req, tag_("Your permissions have been limited " "until you %(link)s.", link=link)) req.perm = perm.PermissionCache(self.env, 'anonymous') return handler
def process_request(self, req): acctmgr = self.acctmgr if req.authname != 'anonymous': req.redirect(req.href.prefs('account')) action = req.args.get('action') name = req.args.get('name', '') if isinstance(name, list): raise HTTPBadRequest(_("Invalid request arguments.")) name = name.strip() username = req.args.get('username', '') if isinstance(username, list): raise HTTPBadRequest(_("Invalid request arguments.")) username = acctmgr.handle_username_casing(username.strip()) data = { '_dgettext': dgettext, 'acctmgr': {'name': name, 'username': username}, 'ignore_auth_case': self.config.getbool('trac', 'ignore_auth_case') } verify_enabled = self.env.is_enabled(EmailVerificationModule) and \ EmailVerificationModule(self.env).verify_email data['verify_account_enabled'] = verify_enabled if req.method == 'POST' and action == 'create': try: try: # Check request and prime account on success. acctmgr.validate_account(req, True) except NotificationError, e: chrome.add_warning(req, _( "Error raised while sending a change notification." ) + _("You should report that issue to a Trac admin.")) self.log.error( 'Unable to send registration notification: %s', exception_to_unicode(e, traceback=True)) except RegistrationError, e: chrome.add_warning(req, e) else: if self.require_approval: set_user_attribute(self.env, username, 'approval', N_('pending')) # Notify admin user about registration pending for review. try: acctmgr._notify('registration_approval_required', username) except NotificationError, e: chrome.add_warning(req, _( "Error raised while sending a change " "notification.") + _( "You should report that issue to a Trac admin.")) self.log.error( 'Unable to send admin notification: %s', exception_to_unicode(e, traceback=True)) else: chrome.add_notice(req, tag_( "Your username has been registered successfully, " "but your account requires administrative " "approval. Please proceed according to local " "policy.")) if verify_enabled: chrome.add_notice(req, tag_( "Your username has been successfully registered but " "your account still requires activation. Please " "login as user %(user)s, and follow the " "instructions.", user=tag.b(username))) req.redirect(req.href.login()) chrome.add_notice(req, tag_( "Registration has been finished successfully. " "You may log in as user %(user)s now.", user=tag.b(username))) req.redirect(req.href.login())