Esempio n. 1
0
    def validate_registration(self, req):
        acctmgr = AccountManager(self.env)
        email = req.args.get('email', '').strip()

        if is_enabled(self.env, EmailVerificationModule) and \
                acctmgr.verify_email:
            if not email:
                raise RegistrationError(
                    N_("You must specify a valid email address."))
            elif email_associated(self.env, email):
                raise RegistrationError(
                    N_("The email address specified is already in use. "
                       "Please specify a different one."))
Esempio n. 2
0
class HtDigestStore(AbstractPasswordFileStore):
    """Manages user accounts stored in Apache's htdigest format.

    To use this implementation add the following configuration section to
    trac.ini:
    {{{
    [account-manager]
    password_store = HtDigestStore
    htdigest_file = /path/to/trac.htdigest
    htdigest_realm = TracDigestRealm
    }}}
    """

    implements(IPasswordStore)

    filename = EnvRelativePathOption(
        'account-manager',
        'htdigest_file',
        '',
        doc=N_("""Path relative to Trac environment or full host machine
                path to password file"""))
    realm = Option('account-manager',
                   'htdigest_realm',
                   '',
                   doc=N_("Realm to select relevant htdigest file entries"))

    def config_key(self):
        return 'htdigest'

    def prefix(self, user):
        return '%s:%s:' % (user, self.realm.encode('utf-8'))

    def userline(self, user, password):
        return self.prefix(user) + htdigest(user, self.realm.encode('utf-8'),
                                            password)

    def _check_userline(self, user, password, suffix):
        return suffix == htdigest(user, self.realm.encode('utf-8'), password)

    def _get_users(self, filename):
        _realm = self.realm.encode('utf-8')
        f = open(filename)
        for line in f:
            args = line.split(':')[:2]
            if len(args) == 2:
                user, realm = args
                if realm == _realm and user:
                    yield user.decode('utf-8')
Esempio n. 3
0
class HttpAuthStore(Component):
    implements(IPasswordStore)

    auth_url = Option('account-manager',
                      'authentication_url',
                      '',
                      doc=N_("URL of the HTTP authentication service"))

    def check_password(self, user, password):
        acctmgr = HTTPPasswordMgrWithDefaultRealm()
        acctmgr.add_password(None, self.auth_url, user, password)
        try:
            build_opener(HTTPBasicAuthHandler(acctmgr),
                         HTTPDigestAuthHandler(acctmgr)).open(self.auth_url)
        except IOError:
            return None
        except ValueError:
            return None
        else:
            return True

    def get_users(self):
        return []

    def has_user(self, user):
        return False
Esempio n. 4
0
    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(N_(
                    "Another account or group already exists, who's name "
                    "differs from %s only by case or is identical."),
                    tag.b(username)
                )
Esempio n. 5
0
class HtPasswdStore(AbstractPasswordFileStore):
    """Manages user accounts stored in Apache's htpasswd format.

    To use this implementation add the following configuration section to
    trac.ini:
    {{{
    [account-manager]
    password_store = HtPasswdStore
    htpasswd_file = /path/to/trac.htpasswd
    htpasswd_hash_type = crypt|md5|sha|sha256|sha512 <- None or one of these
    }}}

    Default behaviour is to detect presence of 'crypt' and use it or
    fallback to generation of passwords with md5 hash otherwise.
    """

    implements(IPasswordStore)

    filename = EnvRelativePathOption(
        'account-manager',
        'htpasswd_file',
        '',
        doc=N_("""Path relative to Trac environment or full host machine
                path to password file"""))
    hash_type = Option('account-manager',
                       'htpasswd_hash_type',
                       'crypt',
                       doc=N_("Default hash type of new/updated passwords"))

    def config_key(self):
        return 'htpasswd'

    def prefix(self, user):
        return user + ':'

    def userline(self, user, password):
        return self.prefix(user) + mkhtpasswd(password, self.hash_type)

    def _check_userline(self, user, password, suffix):
        return suffix == htpasswd(password, suffix)

    def _get_users(self, filename):
        f = open(filename, 'rU')
        for line in f:
            user = line.split(':', 1)[0]
            if user:
                yield user.decode('utf-8')
Esempio n. 6
0
 def validate_registration(self, req):
     acctmgr = AccountManager(self.env)
     email = req.args.get('email', '').strip()
     if is_enabled(self.env, EmailVerificationModule) and \
             EmailVerificationModule(self.env).verify_email:
         # Initial configuration case.
         if not email and not req.args.get('active'):
             raise RegistrationError(
                 N_("You must specify a valid email address."))
         # User preferences case.
         elif req.path_info == '/prefs' and email == req.session.get(
                 'email'):
             return
         elif email_associated(self.env, email):
             raise RegistrationError(
                 N_("The email address specified is already in use. "
                    "Please specify a different one."))
Esempio n. 7
0
 def validate_registration(self, req):
     # Input must be an exact replication of the required token.
     basic_token = req.args.get('basic_token', '')
     # Unlike the former, the hidden bot-trap input field must stay empty.
     keep_empty = req.args.get('sentinel', '')
     if keep_empty or self.reg_basic_token and \
             self.reg_basic_token != basic_token:
         raise RegistrationError(N_("Are you human? If so, try harder!"))
Esempio n. 8
0
    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 containsAny(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(
                    N_("Another account or group already exists, who's name "
                       "differs from %s only by case or is identical."),
                    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."))
Esempio n. 9
0
class SvnServePasswordStore(Component):
    """PasswordStore implementation for reading svnserve's password file format
    """

    implements(IPasswordStore)

    filename = EnvRelativePathOption(
        'account-manager',
        'password_file',
        doc=N_("""Path to the users file; leave blank to locate
                the users file by reading svnserve.conf"""))

    def __init__(self):
        repo_dir = RepositoryManager(self.env).repository_dir
        self._svnserve_conf = Configuration(
            os.path.join(os.path.join(repo_dir, 'conf'), 'svnserve.conf'))
        self._userconf = None

    def _config(self):
        filename = self.filename
        if not filename:
            self._svnserve_conf.parse_if_needed()
            filename = self._svnserve_conf['general'].getpath('password-db')
        if self._userconf is None or filename != self._userconf.filename:
            self._userconf = Configuration(filename)
            # Overwrite default with str class to preserve case.
            self._userconf.parser.optionxform = str
            self._userconf.parse_if_needed(force=True)
        else:
            self._userconf.parse_if_needed()
        return self._userconf

    _config = property(_config)

    # IPasswordStore methods

    def get_users(self):
        return [user for (user, password) in self._config.options('users')]

    def has_user(self, user):
        return user in self._config['users']

    def set_password(self, user, password, old_password=None):
        cfg = self._config
        cfg.set('users', user, password)
        cfg.save()

    def check_password(self, user, password):
        if self.has_user(user):
            return password == self._config.get('users', user)
        return None

    def delete_user(self, user):
        cfg = self._config
        cfg.remove('users', user)
        cfg.save()
Esempio n. 10
0
    def validate_registration(self, req):
        acctmgr = AccountManager(self.env)

        username = acctmgr.handle_username_casing(
            req.args.get('username', '').strip())
        if self.username_regexp != "" and \
                not re.match(self.username_regexp.strip(), username):
            raise RegistrationError(
                N_("Username %s doesn't match local naming policy."),
                tag.b(username))

        email = req.args.get('email', '').strip()
        if acctmgr.verify_email and is_enabled(self.env, EmailCheck) and \
                is_enabled(self.env, EmailVerificationModule):
            if self.email_regexp.strip() != "" and \
                    not re.match(self.email_regexp.strip(), email):
                raise RegistrationError(
                    N_("The email address specified appears to be invalid. "
                       "Please specify a valid email address."))
    def __init__(self, message, *args, **kwargs):
        """TracError sub-class with extended i18n support.

        It eases error initialization with messages optionally including
        arguments meant for string substitution after deferred translation.
        """
        title = N_("Registration Error")
        tb = 'show_traceback'
        # Care for the 2nd TracError standard keyword argument only.
        show_traceback = tb in kwargs and kwargs.pop(tb, False)
        TracError.__init__(self, message, title, show_traceback)
        self.msg_args = args
Esempio n. 12
0
class HtDigestHashMethod(Component):
    implements(IPasswordHashMethod)

    realm = Option('account-manager',
                   'db_htdigest_realm',
                   '',
                   doc=N_("Realm to select relevant htdigest db entries"))

    def generate_hash(self, user, password):
        user, password, realm = _encode(user, password, self.realm)
        return ':'.join([realm, htdigest(user, realm, password)])

    def check_hash(self, user, password, hash):
        return hash == self.generate_hash(user, password)
Esempio n. 13
0
class HtPasswdHashMethod(Component):
    implements(IPasswordHashMethod)

    hash_type = Option('account-manager',
                       'db_htpasswd_hash_type',
                       'crypt',
                       doc=N_("Default hash type of new/updated passwords"))

    def generate_hash(self, user, password):
        password = password.encode('utf-8')
        return mkhtpasswd(password, self.hash_type)

    def check_hash(self, user, password, hash):
        password = password.encode('utf-8')
        hash2 = htpasswd(password, hash)
        return hash == hash2
Esempio n. 14
0
class HttpAuthStore(Component):
    implements(IPasswordStore)

    auth_url = Option('account-manager',
                      'authentication_url',
                      '',
                      doc=N_("URL of the HTTP authentication service"))

    def check_password(self, username, password):
        self.log.debug("Trac.ini authentication_url = '%s'" % self.auth_url)
        # Nothing to do, if URL is absolute.
        if self.auth_url.startswith('http://') or \
                self.auth_url.startswith('https://'):
            authUrl = self.auth_url
        # Handle server-relative URLs.
        elif self.auth_url.startswith('/'):
            # Prepend the Trac server component.
            pr = urlparse(self.env.abs_href())
            href = Href(pr[0] + '://' + pr[1])
            authUrl = href(self.auth_url)
        elif '/' in self.auth_url:
            # URLs with path like 'common/authFile' or 'site/authFile'.
            authUrl = self.env.abs_href.chrome(self.auth_url)
        else:
            # Bare file name option value like 'authFile'.
            authUrl = self.env.abs_href.chrome('common', self.auth_url)
        self.log.debug("Final auth_url = '%s'" % authUrl)

        acctmgr = HTTPPasswordMgrWithDefaultRealm()
        acctmgr.add_password(None, authUrl, username, password)
        try:
            build_opener(HTTPBasicAuthHandler(acctmgr),
                         HTTPDigestAuthHandler(acctmgr)).open(authUrl)
        except IOError, e:
            if hasattr(e, 'code') and e.code == 404:
                self.log.debug("""HttpAuthStore page not found; we are
                               authenticated nonetheless""")
                return True
            if hasattr(e, 'code') and e.code == 401:
                self.log.debug("HttpAuthStore authentication failed")
            return None
        except ValueError, e:
            self.log.debug("""HttpAuthStore: 'authentication_url' specifies
                           an invalid URL""")
            return None
Esempio n. 15
0
class SessionStore(Component):
    implements(IPasswordStore)

    hash_method = ExtensionOption('account-manager', 'hash_method',
        IPasswordHashMethod, 'HtDigestHashMethod',
        doc = N_("IPasswordHashMethod used to create new/updated passwords"))

    def __init__(self):
        self.key = 'password'
        # Check for valid hash method configuration.
        self.hash_method_enabled

    def get_users(self):
        """Returns an iterable of the known usernames."""
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute("""
            SELECT DISTINCT sid
            FROM    session_attribute
            WHERE   authenticated=1
                AND name=%s
            """, (self.key,))
        for sid, in cursor:
            yield sid
 
    def has_user(self, user):
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute("""
            SELECT  *
            FROM    session_attribute
            WHERE   authenticated=1
                AND name=%s
                AND sid=%s
            """, (self.key, user))
        for row in cursor:
            return True
        return False

    def set_password(self, user, password, old_password=None):
        """Sets the password for the user.

        This should create the user account, if it doesn't already exist.
        Returns True, if a new account was created, and False,
        if an existing account was updated.
        """
        if not self.hash_method_enabled:
            return
        hash = self.hash_method.generate_hash(user, password)
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        sql = """
            WHERE   authenticated=1
                AND name=%s
                AND sid=%s
            """
        cursor.execute("""
            UPDATE  session_attribute
                SET value=%s
            """ + sql, (hash, self.key, user))
        cursor.execute("""
            SELECT  value
            FROM    session_attribute
            """ + sql, (self.key, user))
        not_exists = cursor.fetchone() is None
        if not_exists:
            cursor.execute("""
                INSERT INTO session_attribute
                        (sid,authenticated,name,value)
                VALUES  (%s,1,%s,%s)
                """, (user, self.key, hash))
        db.commit()
        return not_exists

    def check_password(self, user, password):
        """Checks if the password is valid for the user."""
        if not self.hash_method_enabled:
            return
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute("""
            SELECT  value
            FROM    session_attribute
            WHERE   authenticated=1
                AND name=%s
                AND sid=%s
            """, (self.key, user))
        for hash, in cursor:
            return self.hash_method.check_hash(user, password, hash)
        # Return value 'None' allows to proceed with another, chained store.
        return

    def delete_user(self, user):
        """Deletes the user account.

        Returns True, if the account existed and was deleted, False otherwise.
        """
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        sql = """
            WHERE   authenticated=1
                AND name=%s
                AND sid=%s
            """
        # Avoid has_user() to make this transaction atomic.
        cursor.execute("""
            SELECT  *
            FROM    session_attribute
            """ + sql, (self.key, user))
        exists = cursor.fetchone() is not None
        if exists:
            cursor.execute("""
                DELETE
                FROM    session_attribute
                """ + sql, (self.key, user))
            db.commit()
        return exists

    @property
    def hash_method_enabled(self):
        try:
            hash_method = self.hash_method
        except AttributeError:
            self.env.log.error("%s: no IPasswordHashMethod enabled "
                               "- fatal, can't work" % self.__class__)
            return
        return True
 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())
Esempio n. 17
0
 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', '').strip()
     username = acctmgr.handle_username_casing(
         req.args.get('username', '').strip())
     data = {
         '_dgettext': dgettext,
         'acctmgr': dict(name=name, username=username),
         'ignore_auth_case': self.config.getbool('trac', 'ignore_auth_case')
     }
     verify_enabled = is_enabled(self.env, EmailVerificationModule) and \
                      EmailVerificationModule(self.env).verify_email
     data['verify_account_enabled'] = verify_enabled
     if req.method == 'POST' and action == 'create':
         try:
             # Check request and prime account on success.
             acctmgr.validate_account(req, True)
         except RegistrationError, e:
             # Attempt deferred translation.
             message = gettext(e.message)
             # Check for (matching number of) message arguments before
             #   attempting string substitution.
             if e.msg_args and \
                     len(e.msg_args) == len(re.findall('%s', message)):
                 message = message % e.msg_args
             chrome.add_warning(req, Markup(message))
         else:
             if self.require_approval:
                 set_user_attribute(self.env, username, 'approval',
                                    N_('pending'))
                 # Notify admin user about registration pending for review.
                 acctmgr._notify('registration_approval_required', username)
                 chrome.add_notice(
                     req,
                     Markup(
                         tag.span(
                             Markup(
                                 _("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,
                     Markup(
                         tag.span(
                             Markup(
                                 _("""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,
                 Markup(
                     tag.span(
                         Markup(
                             _("""Registration has been finished successfully.
                  You may log in as user %(user)s now.""",
                               user=tag.b(username))))))
             req.redirect(req.href.login())
Esempio n. 18
0
class AbstractPasswordFileStore(Component):
    """Base class for managing password files.

    Derived classes support different formats such as
    Apache's htpasswd and htdigest format.
    See these concrete sub-classes for usage information.
    """
    abstract = True

    # DEVEL: This option is subject to removal after next major release.
    filename = EnvRelativePathOption(
        'account-manager',
        'password_file',
        '',
        doc=N_("""Path relative to Trac environment or full host machine
                path to password file"""))

    def has_user(self, user):
        return user in self.get_users()

    def get_users(self):
        filename = str(self.filename)
        if not os.path.exists(filename):
            self.log.error('acct_mgr: get_users() -- '
                           'Can\'t locate password file "%s"' % filename)
            return []
        return self._get_users(filename)

    def set_password(self, user, password, old_password=None):
        user = user.encode('utf-8')
        password = password.encode('utf-8')
        return not self._update_file(self.prefix(user),
                                     self.userline(user, password))

    def delete_user(self, user):
        user = user.encode('utf-8')
        return self._update_file(self.prefix(user), None)

    def check_password(self, user, password):
        filename = str(self.filename)
        if not os.path.exists(filename):
            self.log.error('acct_mgr: check_password() -- '
                           'Can\'t locate password file "%s"' % filename)
            return False
        user = user.encode('utf-8')
        password = password.encode('utf-8')
        prefix = self.prefix(user)
        try:
            f = open(filename, 'rU')
            for line in f:
                if line.startswith(prefix):
                    return self._check_userline(
                        user, password, line[len(prefix):].rstrip('\n'))
        # DEVEL: Better use new 'finally' statement here, but
        #   still need to care for Python 2.4 (RHEL5.x) for now
        except:
            self.log.error('acct_mgr: check_password() -- '
                           'Can\'t read password file "%s"' % filename)
            pass
        if isinstance(f, file):
            f.close()
        return None

    def _update_file(self, prefix, userline):
        """Add or remove user and change password.

        If `userline` is empty, the line starting with `prefix` is removed
        from the user file. Otherwise the line starting with `prefix`
        is updated to `userline`.  If no line starts with `prefix`,
        the `userline` is appended to the file.

        Returns `True` if a line matching `prefix` was updated,
        `False` otherwise.
        """
        filename = str(self.filename)
        matched = False
        new_lines = []
        try:
            # Open existing file read-only to read old content.
            # DEVEL: Use `with` statement available in Python >= 2.5
            #   as soon as we don't need to support 2.4 anymore.
            eol = '\n'
            f = open(filename, 'r')
            lines = f.readlines()

            # DEVEL: Beware, in shared use there is a race-condition,
            #   since file changes by other programs that occure from now on
            #   are currently not detected and will get overwritten.
            #   This could be fixed by file locking, but a cross-platform
            #   implementation is certainly non-trivial.
            # DEVEL: I've seen the AtomicFile object in trac.util lately,
            #   that may be worth a try.
            if len(lines) > 0:
                # predict eol style for lines without eol characters
                if not os.linesep == '\n':
                    if lines[-1].endswith('\r') and os.linesep == '\r':
                        # antique MacOS newline style safeguard
                        # DEVEL: is this really still needed?
                        eol = '\r'
                    elif lines[-1].endswith('\r\n') and os.linesep == '\r\n':
                        # Windows newline style safeguard
                        eol = '\r\n'

                for line in lines:
                    if line.startswith(prefix):
                        if not matched and userline:
                            new_lines.append(userline + eol)
                        matched = True
                    # preserve existing lines with proper eol
                    elif line.endswith(eol) and not \
                            (eol == '\n' and line.endswith('\r\n')):
                        new_lines.append(line)
                    # unify eol style using confirmed default and
                    # make sure the (last) line has a newline anyway
                    else:
                        new_lines.append(line.rstrip('\r\n') + eol)
        except EnvironmentError, e:
            if e.errno == errno.ENOENT:
                # Ignore, when file doesn't exist and create it below.
                pass
            elif e.errno == errno.EACCES:
                raise TracError(
                    _("""The password file could not be read. Trac requires
                    read and write access to both the password file
                    and its parent directory."""))
            else:
                raise

        # Finally add the new line here, if it wasn't used before
        # to update or delete a line, creating content for a new file as well.
        if not matched and userline:
            new_lines.append(userline + eol)

        # Try to (re-)open file write-only now and save new content.
        try:
            f = open(filename, 'w')
            f.writelines(new_lines)
        except EnvironmentError, e:
            if e.errno == errno.EACCES or e.errno == errno.EROFS:
                raise TracError(
                    _("""The password file could not be updated. Trac requires
                    read and write access to both the password file
                    and its parent directory."""))
            else:
                raise