diff --git a/tests/test_groups.py b/tests/test_groups.py index dc0294f5d03897ce3c053ccc81ee66170d72a1f2_dGVzdHMvdGVzdF9ncm91cHMucHk=..bec7cc14c4da8260eaa318619822c036e86479be_dGVzdHMvdGVzdF9ncm91cHMucHk= 100644 --- a/tests/test_groups.py +++ b/tests/test_groups.py @@ -3,5 +3,6 @@ needs, unique_name, ) +from .utils.access import GroupAccess from .utils.group import Group from .utils.project import Project @@ -6,5 +7,6 @@ from .utils.group import Group from .utils.project import Project +from .utils.user import User from .test_push import prepare_simple_repo @@ -80,6 +82,32 @@ } +def test_custom_attributes_perms(heptapod): + """Ensuring that these are as private as we think they are.""" + owner = heptapod.get_user('test_basic') + admin = heptapod.get_user('root') + with Group.api_create(heptapod, unique_name('test_group'), + user_name=owner.name) as group: + # first check that custom attributes do work + group.api_set_custom_attribute('location', 'secret lair', + user=admin) + assert group.api_get_custom_attribute('location', + user=admin) == 'secret lair' + + def assert_forbidden(user): + resp = group.api_get_custom_attribute('location', user=user, + check=False) + assert resp.status_code == 403 + + with User.create(heptapod, + unique_name('user_custom_attr')) as user: + for level in GroupAccess: + group.grant_member_access(user, level) + # Even OWNER is forbidden. If that were to + # change it probably wouldn't be really shocking. + assert_forbidden(owner) + + @needs.services @parametrize('hashed_storage', [True, False]) def test_subgroups(heptapod, hashed_storage): diff --git a/tests/test_projects.py b/tests/test_projects.py index dc0294f5d03897ce3c053ccc81ee66170d72a1f2_dGVzdHMvdGVzdF9wcm9qZWN0cy5weQ==..bec7cc14c4da8260eaa318619822c036e86479be_dGVzdHMvdGVzdF9wcm9qZWN0cy5weQ== 100644 --- a/tests/test_projects.py +++ b/tests/test_projects.py @@ -11,5 +11,6 @@ unique_name, needs, ) +from .utils.access import GroupAccess from .utils.project import ( Project, @@ -14,6 +15,5 @@ from .utils.project import ( Project, - ProjectAccess, ) from .utils.group import Group from .utils.hg import LocalRepo @@ -349,9 +349,8 @@ if how == 'bumped-quota': external_user.edit(projects_limit=1) elif how == 'group-member': - resp = test_group.api_add_member(external_user, - ProjectAccess.DEVELOPER) - assert resp.status_code == 201 + test_group.grant_member_access(external_user, + GroupAccess.DEVELOPER) # Now we have this big link in the middle of the page webdriver.get(heptapod.url) diff --git a/tests/utils/access.py b/tests/utils/access.py new file mode 100644 index 0000000000000000000000000000000000000000..bec7cc14c4da8260eaa318619822c036e86479be_dGVzdHMvdXRpbHMvYWNjZXNzLnB5 --- /dev/null +++ b/tests/utils/access.py @@ -0,0 +1,34 @@ +import enum + + +class Access(enum.IntEnum): + """Union of all possible values for Projects and Groups.""" + NO_ACCESS = 0 + MINIMAL = 5 # Introduced in GitLab 13.5 + GUEST = 10 + REPORTER = 20 + DEVELOPER = 30 + MAINTAINER = 40 + OWNER = 50 + + +class ProjectAccess(enum.IntEnum): + """Subset of Access suitable to set on a Project. + + - `OWNER` is only settable on Groups. + - `NO_ACCESS` is probably not settable on Projects + - `MINIMAL` is to be determined + """ + GUEST = Access.GUEST + REPORTER = Access.REPORTER + DEVELOPER = Access.DEVELOPER + MAINTAINER = Access.MAINTAINER + + +class GroupAccess(enum.IntEnum): + """Subset of Access suitable to set on a Group""" + GUEST = Access.GUEST + REPORTER = Access.REPORTER + DEVELOPER = Access.DEVELOPER + MAINTAINER = Access.MAINTAINER + OWNER = Access.OWNER diff --git a/tests/utils/group.py b/tests/utils/group.py index dc0294f5d03897ce3c053ccc81ee66170d72a1f2_dGVzdHMvdXRpbHMvZ3JvdXAucHk=..bec7cc14c4da8260eaa318619822c036e86479be_dGVzdHMvdXRpbHMvZ3JvdXAucHk= 100644 --- a/tests/utils/group.py +++ b/tests/utils/group.py @@ -1,6 +1,8 @@ import attr import requests +from .access import GroupAccess + class NameSpace: @@ -80,11 +82,12 @@ user_name = self.owner_name if self.owner_name is not None else 'root' return {'Private-Token': self.heptapod.users[user_name].token} - def api_get(self): - return requests.get(self.api_url, headers=self.private_token()) + def api_get(self, subpath=''): + return requests.get('/'.join((self.api_url, subpath)), + headers=self.private_token()) def api_post(self, subpath='', **params): return requests.post('/'.join((self.api_url, subpath)), headers=self.private_token(), data=params) @@ -85,13 +88,13 @@ def api_post(self, subpath='', **params): return requests.post('/'.join((self.api_url, subpath)), headers=self.private_token(), data=params) - def api_add_member(self, user, access_level): - return self.api_post(subpath='members', - user_id=user.id, - access_level=access_level) + def api_put(self, subpath='', **params): + return requests.put('/'.join((self.api_url, subpath)), + headers=self.private_token(), + data=params) @classmethod def api_search(cls, heptapod, group_name, user_name='root'): @@ -113,6 +116,59 @@ resp = requests.delete(self.api_url, headers=self.private_token()) assert resp.status_code in (202, 204) + def grant_member_access(self, user, level): + """Grant given user the given access level. + + It doesn't matter whether the user is already a member or not: this + method abstracts over it. + + This method is idempotent. + + TODO factorize with Project (as surely is done in the Rails app) + """ + assert level in GroupAccess + + user_id = user.id + resp = self.api_get(subpath='members/%d' % user_id) + if resp.status_code == 404: + subpath = 'members' + meth = self.api_post + else: + subpath = 'members/%d' % user_id + meth = self.api_put + + resp = meth(subpath=subpath, + user_id=user_id, + access_level=int(level)) + assert resp.status_code < 400 + + def custom_attribute_api_url(self, key): + return '/'.join((self.api_url, 'custom_attributes', key)) + + def api_set_custom_attribute(self, key, value, user=None): + if user is None: + user = self.heptapod.get_user('root') + + resp = requests.put(self.custom_attribute_api_url(key), + headers={'Private-Token': user.token}, + data=dict(value=value)) + + assert resp.status_code == 200 + return resp.json() + + def api_get_custom_attribute(self, key, check=True, user=None): + if user is None: + user = self.heptapod.get_user('root') + + resp = requests.get(self.custom_attribute_api_url(key), + headers={'Private-Token': user.token}) + + if not check: + return resp + + assert resp.status_code < 400 + return resp.json()['value'] + def fs_path(self): return '/'.join((self.heptapod.repositories_root, self.full_path)) diff --git a/tests/utils/project.py b/tests/utils/project.py index dc0294f5d03897ce3c053ccc81ee66170d72a1f2_dGVzdHMvdXRpbHMvcHJvamVjdC5weQ==..bec7cc14c4da8260eaa318619822c036e86479be_dGVzdHMvdXRpbHMvcHJvamVjdC5weQ== 100644 --- a/tests/utils/project.py +++ b/tests/utils/project.py @@ -18,6 +18,7 @@ from selenium.webdriver.support.ui import WebDriverWait from . import wait_assert +from .access import ProjectAccess from .constants import DATA_DIR from .hg import LocalRepo from .group import UserNameSpace @@ -27,15 +28,6 @@ logger = logging.getLogger(__name__) -class ProjectAccess: - NO_ACCESS = 0 - GUEST = 10 - REPORTER = 20 - DEVELOPER = 30 - MAINTAINER = 40 - OWNER = 50 - - def extract_gitlab_branch_titles(branches): return {name: info['commit']['title'] for name, info in branches.items()} @@ -724,6 +716,8 @@ This method is idempotent. """ + assert level in ProjectAccess + user_id = self.heptapod.get_user(user_name).id resp = self.owner_api_get(subpath='members/%d' % user_id) if resp.status_code == 404: @@ -734,7 +728,7 @@ meth = self.owner_api_put resp = meth(subpath=subpath, - data=dict(user_id=user_id, access_level=level)) + data=dict(user_id=user_id, access_level=int(level))) assert resp.status_code < 400 def load_tarball(self, tarball_path):