e8429f9194
Adds an API endpoint, `/v1/organization/<orgname>/collaborators`, that lists an organization's "outside collaborators", i.e. users that have direct permissions on one or more repositories belonging to the organization, but who aren't members of any teams in the organization.
158 lines
8.5 KiB
Python
158 lines
8.5 KiB
Python
from mock import patch
|
|
|
|
import pytest
|
|
from flask_principal import AnonymousIdentity
|
|
|
|
from endpoints.api import api
|
|
from endpoints.api.organization import OrganizationCollaboratorList
|
|
from endpoints.api.repositorynotification import RepositoryNotification
|
|
from endpoints.api.permission import RepositoryUserTransitivePermission
|
|
from endpoints.api.team import OrganizationTeamSyncing
|
|
from endpoints.api.test.shared import conduct_api_call
|
|
from endpoints.api.repository import RepositoryTrust
|
|
from endpoints.api.signing import RepositorySignatures
|
|
from endpoints.api.search import ConductRepositorySearch
|
|
from endpoints.api.superuser import SuperUserRepositoryBuildLogs, SuperUserRepositoryBuildResource
|
|
from endpoints.api.superuser import SuperUserRepositoryBuildStatus
|
|
from endpoints.api.appspecifictokens import AppTokens, AppToken
|
|
from endpoints.api.trigger import BuildTrigger
|
|
from endpoints.test.shared import client_with_identity, toggle_feature
|
|
|
|
from test.fixtures import *
|
|
|
|
ORG_PARAMS = {'orgname': 'buynlarge'}
|
|
TEAM_PARAMS = {'orgname': 'buynlarge', 'teamname': 'owners'}
|
|
BUILD_PARAMS = {'build_uuid': 'test-1234'}
|
|
REPO_PARAMS = {'repository': 'devtable/someapp'}
|
|
SEARCH_PARAMS = {'query': ''}
|
|
NOTIFICATION_PARAMS = {'namespace': 'devtable', 'repository': 'devtable/simple', 'uuid': 'some uuid'}
|
|
TOKEN_PARAMS = {'token_uuid': 'someuuid'}
|
|
TRIGGER_PARAMS = {'repository': 'devtable/simple', 'trigger_uuid': 'someuuid'}
|
|
|
|
@pytest.mark.parametrize('resource,method,params,body,identity,expected', [
|
|
(AppTokens, 'GET', {}, {}, None, 401),
|
|
(AppTokens, 'GET', {}, {}, 'freshuser', 200),
|
|
(AppTokens, 'GET', {}, {}, 'reader', 200),
|
|
(AppTokens, 'GET', {}, {}, 'devtable', 200),
|
|
|
|
(AppTokens, 'POST', {}, {}, None, 403),
|
|
(AppTokens, 'POST', {}, {}, 'freshuser', 400),
|
|
(AppTokens, 'POST', {}, {}, 'reader', 400),
|
|
(AppTokens, 'POST', {}, {}, 'devtable', 400),
|
|
|
|
(AppToken, 'GET', TOKEN_PARAMS, {}, None, 401),
|
|
(AppToken, 'GET', TOKEN_PARAMS, {}, 'freshuser', 404),
|
|
(AppToken, 'GET', TOKEN_PARAMS, {}, 'reader', 404),
|
|
(AppToken, 'GET', TOKEN_PARAMS, {}, 'devtable', 404),
|
|
|
|
(AppToken, 'DELETE', TOKEN_PARAMS, {}, None, 403),
|
|
(AppToken, 'DELETE', TOKEN_PARAMS, {}, 'freshuser', 404),
|
|
(AppToken, 'DELETE', TOKEN_PARAMS, {}, 'reader', 404),
|
|
(AppToken, 'DELETE', TOKEN_PARAMS, {}, 'devtable', 404),
|
|
|
|
(OrganizationCollaboratorList, 'GET', ORG_PARAMS, None, None, 401),
|
|
(OrganizationCollaboratorList, 'GET', ORG_PARAMS, None, 'freshuser', 403),
|
|
(OrganizationCollaboratorList, 'GET', ORG_PARAMS, None, 'reader', 403),
|
|
(OrganizationCollaboratorList, 'GET', ORG_PARAMS, None, 'devtable', 200),
|
|
|
|
(OrganizationTeamSyncing, 'POST', TEAM_PARAMS, {}, None, 403),
|
|
(OrganizationTeamSyncing, 'POST', TEAM_PARAMS, {}, 'freshuser', 403),
|
|
(OrganizationTeamSyncing, 'POST', TEAM_PARAMS, {}, 'reader', 403),
|
|
(OrganizationTeamSyncing, 'POST', TEAM_PARAMS, {}, 'devtable', 400),
|
|
|
|
(OrganizationTeamSyncing, 'DELETE', TEAM_PARAMS, {}, None, 403),
|
|
(OrganizationTeamSyncing, 'DELETE', TEAM_PARAMS, {}, 'freshuser', 403),
|
|
(OrganizationTeamSyncing, 'DELETE', TEAM_PARAMS, {}, 'reader', 403),
|
|
(OrganizationTeamSyncing, 'DELETE', TEAM_PARAMS, {}, 'devtable', 200),
|
|
|
|
(ConductRepositorySearch, 'GET', SEARCH_PARAMS, None, None, 200),
|
|
(ConductRepositorySearch, 'GET', SEARCH_PARAMS, None, 'freshuser', 200),
|
|
(ConductRepositorySearch, 'GET', SEARCH_PARAMS, None, 'reader', 200),
|
|
(ConductRepositorySearch, 'GET', SEARCH_PARAMS, None, 'devtable', 200),
|
|
|
|
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, None, 401),
|
|
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, 'freshuser', 403),
|
|
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, 'reader', 403),
|
|
(SuperUserRepositoryBuildLogs, 'GET', BUILD_PARAMS, None, 'devtable', 400),
|
|
|
|
(SuperUserRepositoryBuildStatus, 'GET', BUILD_PARAMS, None, None, 401),
|
|
(SuperUserRepositoryBuildStatus, 'GET', BUILD_PARAMS, None, 'freshuser', 403),
|
|
(SuperUserRepositoryBuildStatus, 'GET', BUILD_PARAMS, None, 'reader', 403),
|
|
(SuperUserRepositoryBuildStatus, 'GET', BUILD_PARAMS, None, 'devtable', 400),
|
|
|
|
(SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, None, 401),
|
|
(SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, 'freshuser', 403),
|
|
(SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, 'reader', 403),
|
|
(SuperUserRepositoryBuildResource, 'GET', BUILD_PARAMS, None, 'devtable', 404),
|
|
|
|
(RepositorySignatures, 'GET', REPO_PARAMS, {}, 'freshuser', 403),
|
|
(RepositorySignatures, 'GET', REPO_PARAMS, {}, 'reader', 403),
|
|
(RepositorySignatures, 'GET', REPO_PARAMS, {}, 'devtable', 404),
|
|
|
|
(RepositoryNotification, 'POST', NOTIFICATION_PARAMS, {}, None, 403),
|
|
(RepositoryNotification, 'POST', NOTIFICATION_PARAMS, {}, 'freshuser', 403),
|
|
(RepositoryNotification, 'POST', NOTIFICATION_PARAMS, {}, 'reader', 403),
|
|
(RepositoryNotification, 'POST', NOTIFICATION_PARAMS, {}, 'devtable', 400),
|
|
|
|
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, None, 403),
|
|
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'freshuser', 403),
|
|
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'reader', 403),
|
|
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'devtable', 404),
|
|
|
|
(BuildTrigger, 'GET', TRIGGER_PARAMS, {}, None, 401),
|
|
(BuildTrigger, 'GET', TRIGGER_PARAMS, {}, 'freshuser', 403),
|
|
(BuildTrigger, 'GET', TRIGGER_PARAMS, {}, 'reader', 403),
|
|
(BuildTrigger, 'GET', TRIGGER_PARAMS, {}, 'devtable', 404),
|
|
|
|
(BuildTrigger, 'DELETE', TRIGGER_PARAMS, {}, None, 403),
|
|
(BuildTrigger, 'DELETE', TRIGGER_PARAMS, {}, 'freshuser', 403),
|
|
(BuildTrigger, 'DELETE', TRIGGER_PARAMS, {}, 'reader', 403),
|
|
(BuildTrigger, 'DELETE', TRIGGER_PARAMS, {}, 'devtable', 404),
|
|
|
|
(BuildTrigger, 'PUT', TRIGGER_PARAMS, {}, None, 403),
|
|
(BuildTrigger, 'PUT', TRIGGER_PARAMS, {}, 'freshuser', 403),
|
|
(BuildTrigger, 'PUT', TRIGGER_PARAMS, {}, 'reader', 403),
|
|
(BuildTrigger, 'PUT', TRIGGER_PARAMS, {}, 'devtable', 400),
|
|
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'public/publicrepo'}, None, None, 401),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'public/publicrepo'}, None, 'freshuser', 403),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'public/publicrepo'}, None, 'reader', 403),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'public/publicrepo'}, None, 'devtable', 403),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'devtable/shared'}, None, None, 401),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'devtable/shared'}, None, 'freshuser', 403),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'devtable/shared'}, None, 'reader', 403),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'devtable/shared'}, None, 'devtable', 404),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'buynlarge/orgrepo'}, None, None, 401),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'buynlarge/orgrepo'}, None, 'freshuser', 403),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'buynlarge/orgrepo'}, None, 'reader', 403),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'A2O9','repository': 'buynlarge/orgrepo'}, None, 'devtable', 404),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'devtable','repository': 'devtable/shared'}, None, 'devtable', 200),
|
|
(RepositoryUserTransitivePermission, 'GET', {'username': 'devtable','repository': 'devtable/nope'}, None, 'devtable', 404),
|
|
])
|
|
def test_api_security(resource, method, params, body, identity, expected, client):
|
|
with client_with_identity(identity, client) as cl:
|
|
conduct_api_call(cl, resource, method, params, body, expected)
|
|
|
|
|
|
@pytest.mark.parametrize('is_superuser', [
|
|
(True),
|
|
(False),
|
|
])
|
|
@pytest.mark.parametrize('allow_nonsuperuser', [
|
|
(True),
|
|
(False),
|
|
])
|
|
@pytest.mark.parametrize('method, expected', [
|
|
('POST', 400),
|
|
('DELETE', 200),
|
|
])
|
|
def test_team_sync_security(is_superuser, allow_nonsuperuser, method, expected, client):
|
|
def is_superuser_method(_):
|
|
return is_superuser
|
|
|
|
with patch('auth.permissions.superusers.is_superuser', is_superuser_method):
|
|
with toggle_feature('NONSUPERUSER_TEAM_SYNCING_SETUP', allow_nonsuperuser):
|
|
with client_with_identity('devtable', client) as cl:
|
|
expect_success = is_superuser or allow_nonsuperuser
|
|
expected_status = expected if expect_success else 403
|
|
conduct_api_call(cl, OrganizationTeamSyncing, method, TEAM_PARAMS, {}, expected_status)
|