CVE-2022-4722 (Python, published 2022-12-27T15:15Z)
Description: value: Authentication Bypass by Primary Weakness in GitHub repository ikus060/rdiffweb prior to 2.5.5.
Severity: nan (CVSS v3 base score: 7.2)
Weakness: CWE-287 \u2014 Improper Authentication
When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.
Repository: https://github.com/ikus060/rdiffweb
Files changed:
README.md
rdiffweb/controller/tests/test_page_login.py
rdiffweb/controller/tests/test_page_prefs_general.py
rdiffweb/core/login.py
rdiffweb/core/model/__init__.py
rdiffweb/core/model/_user.py
rdiffweb/core/model/tests/test_user.py
rdiffweb/core/tests/test_rdw_templating.py
Commit message: Make username case-insensitive


Vulnerable code:
Rdiffweb is written in Python and is released as open source project under the 
The Rdiffweb website is https://rdiffweb.org/.
Bug reports should be reported on the Rdiffweb Gitlab at https://gitlab.com/ikus-soft/rdiffweb/-/issues
* Debian package: Remove dh-systemd from Debian build dependencies (https://bugs.debian.org/871312we)
        # Then database is updated with fullname
        user = UserObject.query.filter(UserObject.username == self.USERNAME).first()
            user = UserObject.query.filter(UserObject.username == self.USERNAME).first()
        user = UserObject.query.filter(UserObject.username == self.USERNAME).first()
        user = UserObject.query.filter(UserObject.username == self.USERNAME).first()
        user = UserObject.query.filter_by(username=username).first()
        userobj = UserObject.query.filter_by(username=username).first()
from ._user import DuplicateSSHKeyError, UserObject  # noqa
    def exists(column):
        table_name = column.table.fullname
        column_name = column.name
        if 'SQLite' in connection.engine.dialect.__class__.__name__:
            sql = "SELECT COUNT(*) FROM pragma_table_info('%s') WHERE LOWER(name)=LOWER('%s')" % (
                table_name,
                column_name,
            )
        else:
            sql = "SELECT COUNT(*) FROM information_schema.columns WHERE table_name='%s' and column_name='%s'" % (
                table_name,
                column_name,
            )
        data = connection.engine.execute(sql).first()
        return data[0] >= 1

    def add_column(column):
        if exists(column):
            return
        table_name = column.table.fullname
        column_name = column.name
        column_type = column.type.compile(connection.engine.dialect)
        connection.engine.execute('ALTER TABLE %s ADD COLUMN %s %s' % (table_name, column_name, column_type))

    add_column(RepoObject.__table__.c.Encoding)
    add_column(RepoObject.__table__.c.keepdays)
    if not exists(UserObject.__table__.c.role):
        add_column(UserObject.__table__.c.role)
    add_column(UserObject.__table__.c.fullname)
    add_column(UserObject.__table__.c.mfa)
    if not exists(SessionObject.__table__.c.Number):
from sqlalchemy import Column, Integer, SmallInteger, String, and_, event, inspect, or_
    username = Column('Username', String, nullable=False, unique=True)
        """Return a user object."""
        return UserObject.query.filter(UserObject.username == user).first()
        user = UserObject.query.filter(UserObject.username == 'admin').first()

Fixed code:
Rdiffweb is written in Python and is released as open source project under the
The Rdiffweb website is <https://rdiffweb.org/>.
Bug reports should be reported on the Rdiffweb Gitlab at <https://gitlab.com/ikus-soft/rdiffweb/-/issues>
* Username are not case-insensitive - credits to [raiders0786](https://www.linkedin.com/in/chirag-agrawal-770488144/)

Breaking changes:

* Username with different cases (e.g.: admin vs Ammin) are not supported. If your database contains such username make sure to remove them before upgrading otherwise Rdiffweb will not start.
* Debian package: Remove dh-systemd from Debian build dependencies (<https://bugs.debian.org/871312we>)
    def test_login_case_insensitive(self):
        # When authenticating with valid credentials with all uppercase username
        self.getPage('/login/', method='POST', body={'login': self.USERNAME.upper(), 'password': self.PASSWORD})
        # Then a new session_id is generated
        self.assertStatus('303 See Other')
        self.assertHeaderItemValue('Location', self.baseurl + '/')
        self.getPage('/')
        self.assertStatus(200)

        # Then database is not updated with new username.
        user = UserObject.get_user(self.USERNAME)
            user = UserObject.get_user(self.USERNAME)
        user = UserObject.get_user(self.USERNAME)
        user = UserObject.get_user(self.USERNAME)
        user = UserObject.get_user(username)
        userobj = UserObject.get_user(username)
import logging
import sys

from sqlalchemy.exc import IntegrityError
from ._user import DuplicateSSHKeyError, UserObject, user_username_index  # noqa
logger = logging.getLogger(__name__)


def _column_add(connection, column):
    if _column_exists(connection, column):
        return
    table_name = column.table.fullname
    column_name = column.name
    column_type = column.type.compile(connection.engine.dialect)
    connection.engine.execute('ALTER TABLE %s ADD COLUMN %s %s' % (table_name, column_name, column_type))


def _column_exists(connection, column):
    table_name = column.table.fullname
    column_name = column.name
    if 'SQLite' in connection.engine.dialect.__class__.__name__:
        sql = "SELECT COUNT(*) FROM pragma_table_info('%s') WHERE LOWER(name)=LOWER('%s')" % (
            table_name,
            column_name,
        )
    else:
        sql = "SELECT COUNT(*) FROM information_schema.columns WHERE table_name='%s' and column_name='%s'" % (
            table_name,
            column_name,
        )
    data = connection.engine.execute(sql).first()
    return data[0] >= 1


def _index_exists(connection, index_name):
    if 'SQLite' in connection.engine.dialect.__class__.__name__:
        sql = "SELECT name FROM sqlite_master WHERE type = 'index' AND name = '%s';" % (index_name)
    else:
        sql = "SELECT * FROM pg_indexes WHERE indexname = '%s'" % (index_name)
    return connection.engine.execute(sql).first() is not None

    _column_add(connection, RepoObject.__table__.c.Encoding)
    _column_add(connection, RepoObject.__table__.c.keepdays)
    if not _column_exists(connection, UserObject.__table__.c.role):
        _column_add(connection, UserObject.__table__.c.role)
    _column_add(connection, UserObject.__table__.c.fullname)
    _column_add(connection, UserObject.__table__.c.mfa)
    if not _column_exists(connection, SessionObject.__table__.c.Number):


    # Fix username case insensitive unique
    if not _index_exists(connection, 'user_username_index'):
        duplicate_users = (
            UserObject.query.with_entities(func.lower(UserObject.username))
            .group_by(func.lower(UserObject.username))
            .having(func.count(UserObject.username) > 1)
        ).all()
        try:
            user_username_index.create()
        except IntegrityError:
            msg = (
                'Failure to upgrade your database to make Username case insensitive. '
                'You must downgrade and deleted duplicate Username. '
                '%s' % '\n'.join([str(k) for k in duplicate_users]),
            )
            logger.error(msg)
            print(msg, file=sys.stderr)
            raise SystemExit(12)
from sqlalchemy import Column, Index, Integer, SmallInteger, String, and_, event, func, inspect, or_
    username = Column('Username', String, nullable=False)
        """Return a user object with username case-insensitive"""
        return UserObject.query.filter(func.lower(UserObject.username) == user.lower()).first()
# Username should be case insensitive
user_username_index = Index('user_username_index', func.lower(UserObject.username), unique=True)


    def test_add_user_with_duplicate_caseinsensitive(self):
        """Add user to database."""
        user = UserObject.add_user('denise')
        user.commit()
        self.listener.user_added.reset_mock()
        with self.assertRaises(ValueError):
            UserObject.add_user('dEnIse')
        # Check if listener called
        self.listener.user_added.assert_not_called()

    def test_get_user_case_insensitive(self):
        userobj1 = UserObject.get_user(self.USERNAME)
        userobj2 = UserObject.get_user(self.USERNAME.lower())
        userobj3 = UserObject.get_user(self.USERNAME.upper())
        self.assertEqual(userobj1, userobj2)
        self.assertEqual(userobj2, userobj3)

        user = UserObject.get_user('admin')