示例#1
0
class AccountModule(Component):
    """Exposes methods for users to do account management on their own.

    Allows users to change their password, reset their password, if they've
    forgotten it, even delete their account.  The settings for the
    AccountManager module must be set in trac.ini in order to use this.
    """

    implements(IPreferencePanelProvider, IRequestHandler, ITemplateProvider,
               INavigationContributor, IRequestFilter)

    _password_chars = string.ascii_letters + string.digits
    password_length = IntOption(
        'account-manager', 'generated_password_length', 8,
        """Length of the randomly-generated passwords created when resetting
        the password for an account.""")

    reset_password = BoolOption(
        'account-manager', 'reset_password', True,
        'Set to False, if there is no email system setup.')

    def __init__(self):
        self.acctmgr = AccountManager(self.env)
        self.store = ResetPwStore(self.env)
        self._write_check(log=True)

    def _write_check(self, log=False):
        writable = self.acctmgr.get_all_supporting_stores('set_password')
        if not writable and log:
            self.log.warn('AccountModule is disabled because the password '
                          'store does not support writing.')
        return writable

    # IPreferencePanelProvider methods

    def get_preference_panels(self, req):
        writable = self._write_check()
        if not writable:
            return
        if req.authname and req.authname != 'anonymous':
            user_store = self.acctmgr.find_user_store(req.authname)
            if user_store in writable:
                yield 'account', _("Account")

    def render_preference_panel(self, req, panel):
        data = {
            'account': self._do_account(req),
            '_dgettext': dgettext,
        }
        return 'prefs_account.html', data

    # IRequestHandler methods

    def match_request(self, req):
        return req.path_info == '/reset_password' and \
               self._reset_password_enabled(log=True)

    def process_request(self, req):
        data = {'_dgettext': dgettext, 'reset': self._do_reset_password(req)}
        return 'reset_password.html', data, None

    # IRequestFilter methods

    def pre_process_request(self, req, handler):
        return handler

    def post_process_request(self, req, template, data, content_type):
        if req.authname and req.authname != 'anonymous':
            if req.session.get('force_change_passwd', False):
                redirect_url = req.href.prefs('account')
                if req.href(req.path_info) != redirect_url:
                    req.redirect(redirect_url)
        return (template, data, content_type)

    # INavigationContributor methods

    def get_active_navigation_item(self, req):
        return 'reset_password'

    def get_navigation_items(self, req):
        if not self.reset_password_enabled or LoginModule(self.env).enabled:
            return
        if req.authname == 'anonymous':
            yield 'metanav', 'reset_password', tag.a(
                _("Forgot your password?"), href=req.href.reset_password())

    def _reset_password_enabled(self, log=False):
        return is_enabled(self.env, self.__class__) and \
               self.reset_password and (self._write_check(log) != [])

    reset_password_enabled = property(_reset_password_enabled)

    def _do_account(self, req):
        if not req.authname or req.authname == 'anonymous':
            # DEVEL: Shouldn't this be a more generic URL?
            req.redirect(req.href.wiki())
        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 _do_reset_password(self, req):
        if req.authname and req.authname != 'anonymous':
            return {'logged_in': True}
        if req.method != 'POST':
            return {}
        username = req.args.get('username')
        email = req.args.get('email')
        if not username:
            return {'error': _("Username is required")}
        if not email:
            return {'error': _("Email is required")}
        for username_, name, email_ in self.env.get_known_users():
            if username_ == username and email_ == email:
                self._reset_password(username, email)
                break
        else:
            return {
                'error':
                _("The email and username must match a known account.")
            }
        return {'sent_to_email': email}

    def _reset_password(self, username, email):
        acctmgr = self.acctmgr
        new_password = self._random_password()
        try:
            acctmgr._notify('password_reset', username, email, new_password)
        except Exception, e:
            return {'error': ','.join(map(to_unicode, e.args))}
        self.store.set_password(username, new_password)
        if acctmgr.force_passwd_change:
            set_user_attribute(self.env, username, 'force_change_passwd', 1)
示例#2
0
class AccountModule(CommonTemplateProvider):
    """Exposes methods for users to do account management on their own.

    Allows users to change their password, reset their password, if they've
    forgotten it, even delete their account.  The settings for the
    AccountManager module must be set in trac.ini in order to use this.
    Password reset procedure depends on both, ResetPwStore and an
    IPasswordHashMethod implementation being enabled as well.
    """

    implements(IPreferencePanelProvider, IRequestHandler,
               INavigationContributor, IRequestFilter)

    _password_chars = string.ascii_letters + string.digits
    password_length = IntOption(
        'account-manager', 'generated_password_length', 8,
        """Length of the randomly-generated passwords created when resetting
        the password for an account.""")

    reset_password = BoolOption(
        'account-manager', 'reset_password', True,
        'Set to False, if there is no email system setup.')

    def __init__(self):
        self.acctmgr = AccountManager(self.env)
        self.store = ResetPwStore(self.env)
        self._write_check(log=True)

    def _write_check(self, log=False):
        writable = self.acctmgr.get_all_supporting_stores('set_password')
        if not writable and log:
            self.log.warn("AccountModule is disabled because the password "
                          "store does not support writing.")
        return writable

    # IPreferencePanelProvider methods

    def get_preference_panels(self, req):
        writable = self._write_check()
        if not writable:
            return
        if req.authname and req.authname != 'anonymous':
            user_store = self.acctmgr.find_user_store(req.authname)
            if user_store in writable:
                yield 'account', _("Account")

    def render_preference_panel(self, req, panel):
        data = {'account': self._do_account(req),
                '_dgettext': dgettext,
               }
        return 'prefs_account.html', data

    # IRequestHandler methods

    def match_request(self, req):
        return req.path_info == '/reset_password' and \
               self._reset_password_enabled(log=True)

    def process_request(self, req):
        data = {'_dgettext': dgettext,
                'reset': self._do_reset_password(req)
               }
        return 'reset_password.html', data, None

    # IRequestFilter methods

    def pre_process_request(self, req, handler):
        if req.path_info == '/prefs/account' and \
                not (req.authname and req.authname != 'anonymous'):
            # An anonymous session has no account associated with it, and
            # no account properies too, but general session preferences should
            # always be available.
            req.redirect(req.href.prefs())
        return handler

    def post_process_request(self, req, template, data, content_type):
        if req.authname and req.authname != 'anonymous':
            if req.session.get('force_change_passwd', False):
                redirect_url = req.href.prefs('account')
                if req.href(req.path_info) != redirect_url:
                    req.redirect(redirect_url)
        return (template, data, content_type)

    # INavigationContributor methods

    def get_active_navigation_item(self, req):
        return 'reset_password'

    def get_navigation_items(self, req):
        if not self.reset_password_enabled or LoginModule(self.env).enabled:
            return
        if req.authname == 'anonymous':
            yield 'metanav', 'reset_password', tag.a(
                _("Forgot your password?"), href=req.href.reset_password())

    def _reset_password_enabled(self, log=False):
        return is_enabled(self.env, self.__class__) and \
               self.reset_password and (self._write_check(log) != []) and \
               is_enabled(self.env, self.store.__class__) and \
               self.store.hash_method

    reset_password_enabled = property(_reset_password_enabled)

    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 _do_reset_password(self, req):
        if req.authname and req.authname != 'anonymous':
            return {'logged_in': True}
        if req.method != 'POST':
            return {}
        username = req.args.get('username')
        email = req.args.get('email')
        if not username:
            return {'error': _("Username is required")}
        if not email:
            return {'error': _("Email is required")}
        for username_, name, email_ in self.env.get_known_users():
            if username_ == username and email_ == email:
                error = self._reset_password(username, email)
                return error and error or {'sent_to_email': email}
        return {'error': _(
            "The email and username must match a known account.")}

    def _reset_password(self, username, email):
        acctmgr = self.acctmgr
        new_password = self._random_password()
        try:
            self.store.set_password(username, new_password)
            acctmgr._notify('password_reset', username, email, new_password)
        except Exception, e:
            return {'error': ','.join(map(to_unicode, e.args))}
        if acctmgr.force_passwd_change:
            set_user_attribute(self.env, username, 'force_change_passwd', 1)
示例#3
0
class AccountModule(CommonTemplateProvider):
    """Exposes methods for users to do account management on their own.

    Allows users to change their password, reset their password, if they've
    forgotten it, even delete their account.  The settings for the
    AccountManager module must be set in trac.ini in order to use this.
    Password reset procedure depends on both, ResetPwStore and an
    IPasswordHashMethod implementation being enabled as well.
    """

    implements(IPreferencePanelProvider, IRequestHandler,
               INavigationContributor, IRequestFilter)

    _password_chars = string.ascii_letters + string.digits
    password_length = IntOption(
        'account-manager', 'generated_password_length', 8,
        """Length of the randomly-generated passwords created when resetting
        the password for an account.""")
    reset_password = BoolOption(
        'account-manager', 'reset_password', True,
        'Set to False, if there is no email system setup.')

    def __init__(self):
        self.acctmgr = AccountManager(self.env)
        self.store = ResetPwStore(self.env)
        self._write_check(log=True)

    def _write_check(self, log=False):
        """Returns all configured write-enabled password stores."""
        writable = self.acctmgr.get_all_supporting_stores('set_password')
        if writable:
            try:
                writable = writable.remove(self.store)
            except ValueError:
                # ResetPwStore is not enabled.
                if log:
                    self.log.warn("ResetPwStore is disabled, therefor "
                                  "password reset won't work.")
        # Require at least one more write-enabled password store.
        if not writable and log:
            self.log.warn("AccountModule is disabled because no configured "
                          "password store supports writing.")
        return writable

    # INavigationContributor methods

    def get_active_navigation_item(self, req):
        return 'reset_password'

    def get_navigation_items(self, req):
        if not self.reset_password_enabled or LoginModule(self.env).enabled:
            return
        if req.authname == 'anonymous':
            yield 'metanav', 'reset_password', tag.a(
                _("Forgot your password?"), href=req.href.reset_password())

    def _reset_password_enabled(self, log=False):
        try:
            self.store.hash_method
        except AttributeError:
            return False
        return is_enabled(self.env, self.__class__) and \
               self.reset_password and (self._write_check(log) != []) and \
               is_enabled(self.env, self.store.__class__) and \
               self.store.hash_method and True or False

    reset_password_enabled = property(_reset_password_enabled)

    # IPreferencePanelProvider methods

    def get_preference_panels(self, req):
        writable = self._write_check()
        if not writable:
            return
        if req.authname and req.authname != 'anonymous':
            user_store = self.acctmgr.find_user_store(req.authname)
            if user_store in writable:
                yield 'account', _("Account")

    def render_preference_panel(self, req, panel):
        data = dict(_dgettext=dgettext)
        data.update(self._do_account(req))
        return 'prefs_account.html', data

    # IRequestFilter methods

    def pre_process_request(self, req, handler):
        if req.path_info == '/prefs/account' and \
                not (req.authname and req.authname != 'anonymous'):
            # An anonymous session has no account associated with it, and
            # no account properies too, but general session preferences should
            # always be available.
            req.redirect(req.href.prefs())
        return handler

    def post_process_request(self, req, template, data, content_type):
        if req.authname and req.authname != 'anonymous':
            if req.session.get('force_change_passwd', False):
                # Prevent authenticated usage before another password change.
                redirect_url = req.href.prefs('account')
                if req.href(req.path_info) != redirect_url:
                    req.redirect(redirect_url)
        return (template, data, content_type)

    # IRequestHandler methods

    def match_request(self, req):
        return req.path_info == '/reset_password' and \
               self._reset_password_enabled(log=True)

    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 _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,
                Markup(
                    _(
                        "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 _do_change_password(self, req):
        username = req.authname

        old_password = req.args.get('old_password')
        if not self.acctmgr.check_password(username, old_password):
            if old_password:
                add_warning(req, _("Old password is incorrect."))
            else:
                add_warning(req, _("Old password cannot be empty."))
            return
        password = req.args.get('password')
        if not password:
            add_warning(req, _("Password cannot be empty."))
        elif password != req.args.get('password_confirm'):
            add_warning(req, _("The passwords must match."))
        elif password == old_password:
            add_warning(req, _("Password must not match old password."))
        else:
            self.acctmgr.set_password(username, password, old_password)
            if req.session.get('password') is not None:
                # Fetch all session_attributes in case new user password is in
                # SessionStore, preventing overwrite by session.save().
                req.session.get_session(req.authname, authenticated=True)
            add_notice(req, _("Password updated successfully."))
            return True

    def _do_delete(self, req):
        username = req.authname

        password = req.args.get('password')
        if not password:
            add_warning(req, _("Password cannot be empty."))
        elif not self.acctmgr.check_password(username, password):
            add_warning(req, _("Password is incorrect."))
        else:
            self.acctmgr.delete_user(username)
            # Delete the whole session, since records in session_attribute
            # would get restored on logout otherwise.
            req.session.clear()
            req.session.save()
            req.redirect(req.href.logout())

    def _do_reset_password(self, req):
        email = req.args.get('email')
        username = req.args.get('username')
        if not username:
            add_warning(req, _("Username is required."))
        elif not email:
            add_warning(req, _("Email is required."))
        else:
            for username_, name, email_ in self.env.get_known_users():
                if username_ == username and email_ == email:
                    self._reset_password(req, username, email)
                    return
            add_warning(req,
                        _("Email and username must match a known account."))

    @property
    def _random_password(self):
        return ''.join([
            random.choice(self._password_chars)
            for _ in xrange(self.password_length)
        ])

    def _reset_password(self, req, username, email):
        acctmgr = self.acctmgr
        new_password = self._random_password
        try:
            self.store.set_password(username, new_password)
            acctmgr._notify('password_reset', username, email, new_password)
            # No message, if method has been called from user admin panel.
            if not req.path_info.startswith('/admin'):
                add_notice(
                    req,
                    _("A new password has been sent to you at "
                      "<%(email)s>.",
                      email=email))
        except Exception, e:
            add_warning(
                req,
                _("Cannot reset password: %(error)s",
                  error=', '.join(map(to_unicode, e.args))))
            return
        if acctmgr.force_passwd_change:
            set_user_attribute(self.env, username, 'force_change_passwd', 1)
class AccountModule(CommonTemplateProvider):
    """Exposes methods for users to do account management on their own.

    Allows users to change their password, reset their password, if they've
    forgotten it, even delete their account.  The settings for the
    AccountManager module must be set in trac.ini in order to use this.
    Password reset procedure depends on both, ResetPwStore and an
    IPasswordHashMethod implementation being enabled as well.
    """

    implements(IPreferencePanelProvider, IRequestHandler,
               INavigationContributor, IRequestFilter)

    _password_chars = string.ascii_letters + string.digits
    password_length = IntOption(
        'account-manager', 'generated_password_length', 8,
        """Length of the randomly-generated passwords created when resetting
        the password for an account.""")
    reset_password = BoolOption(
        'account-manager', 'reset_password', True,
        'Set to False, if there is no email system setup.')

    def __init__(self):
        self.acctmgr = AccountManager(self.env)
        self.store = ResetPwStore(self.env)
        self._write_check(log=True)

    def _write_check(self, log=False):
        """Returns all configured write-enabled password stores."""
        writable = self.acctmgr.get_all_supporting_stores('set_password')
        if writable:
            try:
                writable = writable.remove(self.store)
            except ValueError:
                # ResetPwStore is not enabled.
                if log:
                    self.log.warn("ResetPwStore is disabled, therefor "
                                  "password reset won't work.")
        # Require at least one more write-enabled password store.
        if not writable and log:
            self.log.warn("AccountModule is disabled because no configured "
                          "password store supports writing.")
        return writable

    # INavigationContributor methods

    def get_active_navigation_item(self, req):
        return 'reset_password'

    def get_navigation_items(self, req):
        if not self.reset_password_enabled or LoginModule(self.env).enabled:
            return
        if req.authname == 'anonymous':
            yield 'metanav', 'reset_password', tag.a(
                _("Forgot your password?"), href=req.href.reset_password())

    def _reset_password_enabled(self, log=False):
        try:
            self.store.hash_method
        except (AttributeError, ConfigurationError):
            return False
        return is_enabled(self.env, self.__class__) and \
               self.reset_password and (self._write_check(log) != []) and \
               is_enabled(self.env, self.store.__class__) and \
               self.store.hash_method and True or False

    reset_password_enabled = property(_reset_password_enabled)

    # IPreferencePanelProvider methods

    def get_preference_panels(self, req):
        writable = self._write_check()
        if not writable:
            return
        if req.authname and req.authname != 'anonymous':
            user_store = self.acctmgr.find_user_store(req.authname)
            if user_store in writable:
                yield 'account', _("Account")

    def render_preference_panel(self, req, panel):
        data = dict(_dgettext=dgettext)
        data.update(self._do_account(req))
        return 'prefs_account.html', data

    # IRequestFilter methods

    def pre_process_request(self, req, handler):
        if req.path_info == '/prefs/account' and \
                not (req.authname and req.authname != 'anonymous'):
            # An anonymous session has no account associated with it, and
            # no account properies too, but general session preferences should
            # always be available.
            req.redirect(req.href.prefs())
        return handler

    def post_process_request(self, req, template, data, content_type):
        if req.authname and req.authname != 'anonymous':
            if req.session.get('force_change_passwd', False):
                # Prevent authenticated usage before another password change.
                redirect_url = req.href.prefs('account')
                if req.href(req.path_info) != redirect_url:
                    req.redirect(redirect_url)
        return (template, data, content_type)

    # IRequestHandler methods

    def match_request(self, req):
        return req.path_info == '/reset_password' and \
               self._reset_password_enabled(log=True)

    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 _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, Markup(_(
                "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 _do_change_password(self, req):
        username = req.authname

        old_password = req.args.get('old_password')
        if not self.acctmgr.check_password(username, old_password):
            if old_password:
                add_warning(req, _("Old password is incorrect."))
            else:
                add_warning(req, _("Old password cannot be empty."))
            return
        password = req.args.get('password')
        if not password:
            add_warning(req, _("Password cannot be empty."))
        elif password != req.args.get('password_confirm'):
            add_warning(req, _("The passwords must match."))
        elif password == old_password:
            add_warning(req, _("Password must not match old password."))
        else:
            _set_password(self.env, req, username, password, old_password)
            if req.session.get('password') is not None:
                # Fetch all session_attributes in case new user password is in
                # SessionStore, preventing overwrite by session.save().
                req.session.get_session(req.authname, authenticated=True)
            add_notice(req, _("Password updated successfully."))
            return True

    def _do_delete(self, req):
        username = req.authname

        password = req.args.get('password')
        if not password:
            add_warning(req, _("Password cannot be empty."))
        elif not self.acctmgr.check_password(username, password):
            add_warning(req, _("Password is incorrect."))
        else:
            try:
                self.acctmgr.delete_user(username)
            except NotificationError, e:
                # User wont care for notification, only care for logging here.
                self.log.error(
                       'Unable to send account deletion notification: %s',
                       exception_to_unicode(e, traceback=True))
            # Delete the whole session, since records in session_attribute
            # would get restored on logout otherwise.
            req.session.clear()
            req.session.save()
            req.redirect(req.href.logout())