CVE-2022-4314 (Python, published 2022-12-12T18:15Z)
Description: value: Improper Privilege Management in GitHub repository ikus060/rdiffweb prior to 2.5.2.
Severity: nan (CVSS v3 base score: 9.8)
Weakness: CWE-269 \u2014 Improper Privilege Management
The product does not properly assign, modify, track, or check privileges for an actor, creating an unintended sphere of control for that actor.
Repository: https://github.com/ikus060/rdiffweb
Files changed:
README.md
rdiffweb/controller/tests/test_page_admin_users.py
rdiffweb/core/librdiff.py
rdiffweb/core/model/_repo.py
rdiffweb/core/model/tests/test_user.py
rdiffweb/core/rdw_templating.py
rdiffweb/core/tests/test_librdiff.py
tox.ini
Commit message: Block repository access when user_root directory is empty or a relative path


Vulnerable code:
## Next Rlease - 2.5.1
        # Delete all user's
        for user in UserObject.query.all():
            if user.username != self.USERNAME:
                user.delete().commit()
        user = UserObject.get_user('admin')
    def __init__(self, user_root, path, encoding):
        if isinstance(user_root, str):
            user_root = os.fsencode(user_root)
        if isinstance(path, str):
            path = os.fsencode(path)
        assert isinstance(user_root, bytes)
        assert isinstance(path, bytes)
        assert encoding
        assert self._encoding
        self.path = path.strip(b"/")
        if self.path:
            self.full_path = os.path.normpath(os.path.join(user_root, self.path))
        else:
            self.full_path = os.path.normpath(user_root)
            # For repository we use either path if defined or the directory base name
            if not self.path:
                return self._decode(unquote(os.path.basename(self.full_path)))
            return self._decode(unquote(self.path))
        RdiffRepo.__init__(
            self, self.user.user_root, self.repopath, encoding=self.encoding or RepoObject.DEFAULT_REPO_ENCODING
        )

    @property
    def displayname(self):
        # Repository displayName is the "repopath" too.
        return self.repopath.strip('/')
        if hasattr(chunk, 'owner') and hasattr(chunk, 'path'):
            path += rdw_helpers.quote_url(chunk.path.strip(b"/"))
        RdiffRepo.__init__(self, os.path.dirname(p), os.path.basename(p), encoding='utf-8')
        self.repo = RdiffRepo(self.temp_dir, b'testcases', encoding='utf-8')
        self.repo = RdiffRepo(self.temp_dir, '/testcases', encoding='utf-8')
        self.repo = RdiffRepo(self.temp_dir, 'invalid', encoding='utf-8')
        self.assertEqual(b'invalid', self.repo.path)
        self.repo = RdiffRepo(self.temp_dir, b'testcases', encoding='utf-8')
        self.repo = RdiffRepo(self.temp_dir, b'testcases', encoding='utf-8')
  E203 # whitespace before ':'
  E501 # line too long (86 > 79 characters)
  W503 # line break before binary operator
  E741 # ambiguous variable name 'I'

Fixed code:
## Next Rlease 2.5.2

* Block repository access when user_root directory is empty or a relative path

## 2.5.1 (2022-11-11)
        user = UserObject.get_user(self.USERNAME)
    def __init__(self, full_path, encoding):
        assert encoding, 'encoding is required'
        assert self._encoding, 'encoding must be a valid charset'

        # Validate and sanitize the full_path
        assert full_path, 'full path is required'
        self.full_path = os.fsencode(full_path) if isinstance(full_path, str) else full_path
        assert os.path.isabs(self.full_path), 'full_path must be absolute path'
        self.full_path = os.path.normpath(self.full_path)
            # For repository the directory base name
            return self._decode(unquote(os.path.basename(self.full_path)))
        # RdiffRepo required an absolute full path, When the user_root is invalid, let generate an invalid full path.
        if not self.user.user_root:
            full_path = os.path.join('/user_has_an_empty_user_root/', self.repopath.strip('/'))
        elif not os.path.isabs(self.user.user_root):
            full_path = os.path.join('/user_has_a_relative_user_root/', self.repopath.strip('/'))
        else:
            full_path = os.path.join(self.user.user_root, self.repopath.strip('/'))
        RdiffRepo.__init__(self, full_path, encoding=self.encoding or RepoObject.DEFAULT_REPO_ENCODING)
    def test_refresh_repos_with_empty_userroot(self):
        # Given a user with valid repositories relative to root
        userobj = UserObject.get_user(self.USERNAME)
        for repo in userobj.repo_objs:
            repo.repopath = self.testcases[1:] + '/' + repo.repopath
            repo.add().commit()
        userobj.user_root = '/'
        userobj.add().commit()
        self.assertEqual(['interrupted', 'ok'], sorted([r.status[0] for r in userobj.repo_objs]))
        # When updating it's userroot directory to an empty value
        userobj.user_root = ''
        userobj.add().commit()
        UserObject.session.expire_all()
        # Then close session
        cherrypy.tools.db.on_end_resource()
        # Then repo status is "broken"
        userobj = UserObject.get_user(self.USERNAME)
        self.assertFalse(userobj.valid_user_root())
        self.assertEqual(['failed', 'failed'], [r.status[0] for r in userobj.repo_objs])

        if hasattr(chunk, 'owner') and hasattr(chunk, 'repopath'):
            path += rdw_helpers.quote_url(chunk.repopath.strip("/"))
        RdiffRepo.__init__(self, p, encoding='utf-8')
        self.repo = RdiffRepo(os.path.join(self.temp_dir, 'testcases'), encoding='utf-8')
        self.repo = RdiffRepo(os.path.join(self.temp_dir, '/testcases'), encoding='utf-8')
        self.repo = RdiffRepo(os.path.join(self.temp_dir, 'invalid'), encoding='utf-8')
        self.repo = RdiffRepo(os.path.join(self.temp_dir, 'testcases'), encoding='utf-8')
        self.repo = RdiffRepo(os.path.join(self.temp_dir, 'testcases'), encoding='utf-8')
  # whitespace before ':'
  E203
  # line too long (86 > 79 characters)
  E501
  # line break before binary operator
  W503
  # ambiguous variable name 'I'
  E741