Loading portal/rest/__init__.py +2 −1 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ def register_namespaces(app: Flask): # pylint: disable=cyclic-import from portal.rest.workers import workers_namespace from portal.rest.users import users_namespace from portal.rest.management import management_namespace from portal.rest.client import client_namespace from portal.rest.client import client_namespace, clients_namespace rest_api.add_namespace(auth_namespace) rest_api.add_namespace(courses_namespace) rest_api.add_namespace(roles_namespace) Loading @@ -48,6 +48,7 @@ def register_namespaces(app: Flask): # pylint: disable=cyclic-import rest_api.add_namespace(users_namespace) rest_api.add_namespace(management_namespace) rest_api.add_namespace(client_namespace) rest_api.add_namespace(clients_namespace) if app.config.get('GITLAB_URL'): from portal.rest.gitlab import oauth_namespace Loading portal/rest/client.py +63 −1 Original line number Diff line number Diff line Loading @@ -3,10 +3,13 @@ from flask_restplus import Namespace, Resource from portal import logger from portal.database.models import ClientType from portal.rest import rest_helpers from portal.rest.schemas import SCHEMAS from portal.service import permissions from portal.service import permissions, general from portal.service.secrets import create_secret, delete_secret client_namespace = Namespace('client') clients_namespace = Namespace('clients') log = logger.get_logger(__name__) Loading @@ -20,3 +23,62 @@ class ClientController(Resource): client = perm_service.client_owner schema = SCHEMAS.user() if client.type == ClientType.USER else SCHEMAS.worker() return schema.dump(client)[0], 200 @clients_namespace.route('/<string:cid>/secrets') @clients_namespace.param('wid', "Client's id") @clients_namespace.response(404, 'Client not found') class ClientSecretsController(Resource): @jwt_required @clients_namespace.response(200, 'List client secrets') def get(self, cid: str): permissions.PermissionsService().require.sysadmin_or_self(cid) client = general.find_client(cid) return SCHEMAS.dump('secrets', client.secrets) @jwt_required # @workers_namespace.response(201, 'Created worker secret', model=secret_schema) def post(self, cid: str): permissions.PermissionsService().require.sysadmin_or_self(cid) data = rest_helpers.parse_request_data(action='create', resource='secret') client = general.find_client(cid) new_secret, value = create_secret(client, **data) return {'id': new_secret.id, 'value': value}, 201 @clients_namespace.route('/<string:cid>/secrets/<string:sid>') @clients_namespace.param('wid', "Client's id") @clients_namespace.param('sid', "Secret id") @clients_namespace.response(404, 'Client not found') class ClientSecretController(Resource): @jwt_required @clients_namespace.response(200, 'Client secret detail') def get(self, cid: str, sid: str): permissions.PermissionsService().require.sysadmin() client = general.find_client(cid) secret = general.find_secret(client, sid) return SCHEMAS.dump('secret', secret) @jwt_required @clients_namespace.response(204, 'Client secret deleted') def delete(self, cid: str, sid: str): permissions.PermissionsService().require.sysadmin_or_self(cid) worker = general.find_client(cid) delete_secret(worker, sid) return '', 204 @jwt_required @clients_namespace.response(204, 'Client secret updated') @clients_namespace.response(403, 'Not allowed to update client secret') def put(self, cid: str, sid: str): permissions.PermissionsService().require.sysadmin_or_self(cid) data = rest_helpers.parse_request_data(action='update', resource='secret', partial=True) client = general.find_client(cid) secret = general.find_secret(client, sid) general.update_entity(secret, data, ['name', 'expires_at']) return '', 204 portal/rest/users.py +0 −57 Original line number Diff line number Diff line Loading @@ -12,7 +12,6 @@ from portal.rest import rest_helpers from portal.rest.schemas import SCHEMAS from portal.service import auth, errors, general, permissions from portal.service.general import find_client_owner from portal.service.secrets import create_secret, delete_secret from portal.service.users import UserService users_namespace = Namespace('users') Loading Loading @@ -207,62 +206,6 @@ class UserEffectivePermissions(Resource): return perm @users_namespace.route('/<string:uid>/secrets') @users_namespace.param('uid', "User's id") @users_namespace.response(404, 'User not found') class UserSecretsController(Resource): @jwt_required @users_namespace.response(200, 'List user secrets') def get(self, uid: str): user = general.find_user(uid) permissions.PermissionsService().require.sysadmin_or_self(uid) return SCHEMAS.dump('secrets', user.secrets) @jwt_required # @users_namespace.response(201, 'Created user secret', model=secret_schema) def post(self, uid): user = general.find_user(uid) permissions.PermissionsService().require.sysadmin_or_self(uid) data = rest_helpers.parse_request_data(action='create', resource='secret') new_secret, value = create_secret(user, **data) return {'id': new_secret.id, 'value': value}, 201 @users_namespace.route('/<string:uid>/secrets/<string:sid>') @users_namespace.param('uid', "User's id") @users_namespace.param('sid', "Secret id") @users_namespace.response(404, 'User not found') class UserSecretController(Resource): @jwt_required @users_namespace.response(200, 'User secret detail') def get(self, uid: str, sid: str): permissions.PermissionsService().require.sysadmin() user = general.find_user(uid) secret = general.find_secret(user, sid) return SCHEMAS.dump('secret', secret) @jwt_required @users_namespace.response(204, 'User secret deleted') def delete(self, uid: str, sid: str): user = general.find_user(uid) permissions.PermissionsService().require.sysadmin_or_self(uid) delete_secret(user, sid) return '', 204 @jwt_required @users_namespace.response(204, 'User secret updated') @users_namespace.response(403, 'Not allowed to update user secret') def put(self, uid: str, sid: str): permissions.PermissionsService().require.sysadmin_or_self(uid) data = rest_helpers.parse_request_data(action='update', resource='secret', partial=True) user = general.find_user(uid) secret = general.find_secret(user, sid) general.update_entity(secret, data, ['name', 'expires_at']) return '', 204 # # Helper functions # Loading portal/rest/workers.py +1 −59 Original line number Diff line number Diff line Loading @@ -5,7 +5,6 @@ from portal import logger from portal.rest import rest_helpers from portal.rest.schemas import SCHEMAS from portal.service import general, permissions from portal.service.secrets import create_secret, delete_secret from portal.service.workers import WorkerService workers_namespace = Namespace('workers') Loading Loading @@ -65,60 +64,3 @@ class WorkerResource(Resource): worker = general.find_worker(wid) WorkerService(worker=worker).update_worker(data) return '', 204 No newline at end of file @workers_namespace.route('/<string:wid>/secrets') @workers_namespace.param('wid', "Worker's id") @workers_namespace.response(404, 'Worker not found') class WorkerSecretsController(Resource): @jwt_required @workers_namespace.response(200, 'List worker secrets') def get(self, wid: str): permissions.PermissionsService().require.sysadmin() worker = general.find_worker(wid) return SCHEMAS.dump('secrets', worker.secrets) @jwt_required # @workers_namespace.response(201, 'Created worker secret', model=secret_schema) def post(self, wid: str): permissions.PermissionsService().require.sysadmin() data = rest_helpers.parse_request_data(action='create', resource='secret') worker = general.find_worker(wid) new_secret, value = create_secret(worker, **data) return {'id': new_secret.id, 'value': value}, 201 @workers_namespace.route('/<string:wid>/secrets/<string:sid>') @workers_namespace.param('wid', "Worker's id") @workers_namespace.param('sid', "Secret id") @workers_namespace.response(404, 'Worker not found') class WorkerSecretController(Resource): @jwt_required @workers_namespace.response(200, 'Worker secret detail') def get(self, wid: str, sid: str): permissions.PermissionsService().require.sysadmin() worker = general.find_worker(wid) secret = general.find_secret(worker, sid) return SCHEMAS.dump('secret', secret) @jwt_required @workers_namespace.response(204, 'Worker secret deleted') def delete(self, wid: str, sid: str): permissions.PermissionsService().require.sysadmin() worker = general.find_worker(wid) delete_secret(worker, sid) return '', 204 @jwt_required @workers_namespace.response(204, 'Worker secret updated') @workers_namespace.response(403, 'Not allowed to update worker secret') def put(self, wid: str, sid: str): permissions.PermissionsService().require.sysadmin() data = rest_helpers.parse_request_data(action='update', resource='secret', partial=True) worker = general.find_worker(wid) secret = general.find_secret(worker, sid) general.update_entity(secret, data, ['name', 'expires_at']) return '', 204 tests/rest/test_client.pydeleted 100644 → 0 +0 −47 Original line number Diff line number Diff line import json import pytest from tests.rest import utils @pytest.fixture def student_credentials(): return json.dumps({ "type": "username_password", "identifier": "student1", "secret": "123456" }) @pytest.fixture def worker_credentials(): return json.dumps({ "type": "secret", "identifier": "executor", "secret": "executor_secret" }) def test_client_detail_using_student_cred(client, student_credentials): path = f'/client' response = utils.make_request( client, path, 'get', credentials=student_credentials) print(f"Response: {response.data}") assert response.status_code == 200 assert response.mimetype == 'application/json' data = response.json assert data['username'] == 'student1' assert data['type'] == 'ClientType.USER' def test_client_detail_using_executor_cred(client, worker_credentials): path = f'/client' response = utils.make_request( client, path, 'get', credentials=worker_credentials) print(f"Response: {response.data}") assert response.status_code == 200 assert response.mimetype == 'application/json' data = response.json assert data['name'] == 'executor' assert data['type'] == 'ClientType.WORKER' Loading
portal/rest/__init__.py +2 −1 Original line number Diff line number Diff line Loading @@ -37,7 +37,7 @@ def register_namespaces(app: Flask): # pylint: disable=cyclic-import from portal.rest.workers import workers_namespace from portal.rest.users import users_namespace from portal.rest.management import management_namespace from portal.rest.client import client_namespace from portal.rest.client import client_namespace, clients_namespace rest_api.add_namespace(auth_namespace) rest_api.add_namespace(courses_namespace) rest_api.add_namespace(roles_namespace) Loading @@ -48,6 +48,7 @@ def register_namespaces(app: Flask): # pylint: disable=cyclic-import rest_api.add_namespace(users_namespace) rest_api.add_namespace(management_namespace) rest_api.add_namespace(client_namespace) rest_api.add_namespace(clients_namespace) if app.config.get('GITLAB_URL'): from portal.rest.gitlab import oauth_namespace Loading
portal/rest/client.py +63 −1 Original line number Diff line number Diff line Loading @@ -3,10 +3,13 @@ from flask_restplus import Namespace, Resource from portal import logger from portal.database.models import ClientType from portal.rest import rest_helpers from portal.rest.schemas import SCHEMAS from portal.service import permissions from portal.service import permissions, general from portal.service.secrets import create_secret, delete_secret client_namespace = Namespace('client') clients_namespace = Namespace('clients') log = logger.get_logger(__name__) Loading @@ -20,3 +23,62 @@ class ClientController(Resource): client = perm_service.client_owner schema = SCHEMAS.user() if client.type == ClientType.USER else SCHEMAS.worker() return schema.dump(client)[0], 200 @clients_namespace.route('/<string:cid>/secrets') @clients_namespace.param('wid', "Client's id") @clients_namespace.response(404, 'Client not found') class ClientSecretsController(Resource): @jwt_required @clients_namespace.response(200, 'List client secrets') def get(self, cid: str): permissions.PermissionsService().require.sysadmin_or_self(cid) client = general.find_client(cid) return SCHEMAS.dump('secrets', client.secrets) @jwt_required # @workers_namespace.response(201, 'Created worker secret', model=secret_schema) def post(self, cid: str): permissions.PermissionsService().require.sysadmin_or_self(cid) data = rest_helpers.parse_request_data(action='create', resource='secret') client = general.find_client(cid) new_secret, value = create_secret(client, **data) return {'id': new_secret.id, 'value': value}, 201 @clients_namespace.route('/<string:cid>/secrets/<string:sid>') @clients_namespace.param('wid', "Client's id") @clients_namespace.param('sid', "Secret id") @clients_namespace.response(404, 'Client not found') class ClientSecretController(Resource): @jwt_required @clients_namespace.response(200, 'Client secret detail') def get(self, cid: str, sid: str): permissions.PermissionsService().require.sysadmin() client = general.find_client(cid) secret = general.find_secret(client, sid) return SCHEMAS.dump('secret', secret) @jwt_required @clients_namespace.response(204, 'Client secret deleted') def delete(self, cid: str, sid: str): permissions.PermissionsService().require.sysadmin_or_self(cid) worker = general.find_client(cid) delete_secret(worker, sid) return '', 204 @jwt_required @clients_namespace.response(204, 'Client secret updated') @clients_namespace.response(403, 'Not allowed to update client secret') def put(self, cid: str, sid: str): permissions.PermissionsService().require.sysadmin_or_self(cid) data = rest_helpers.parse_request_data(action='update', resource='secret', partial=True) client = general.find_client(cid) secret = general.find_secret(client, sid) general.update_entity(secret, data, ['name', 'expires_at']) return '', 204
portal/rest/users.py +0 −57 Original line number Diff line number Diff line Loading @@ -12,7 +12,6 @@ from portal.rest import rest_helpers from portal.rest.schemas import SCHEMAS from portal.service import auth, errors, general, permissions from portal.service.general import find_client_owner from portal.service.secrets import create_secret, delete_secret from portal.service.users import UserService users_namespace = Namespace('users') Loading Loading @@ -207,62 +206,6 @@ class UserEffectivePermissions(Resource): return perm @users_namespace.route('/<string:uid>/secrets') @users_namespace.param('uid', "User's id") @users_namespace.response(404, 'User not found') class UserSecretsController(Resource): @jwt_required @users_namespace.response(200, 'List user secrets') def get(self, uid: str): user = general.find_user(uid) permissions.PermissionsService().require.sysadmin_or_self(uid) return SCHEMAS.dump('secrets', user.secrets) @jwt_required # @users_namespace.response(201, 'Created user secret', model=secret_schema) def post(self, uid): user = general.find_user(uid) permissions.PermissionsService().require.sysadmin_or_self(uid) data = rest_helpers.parse_request_data(action='create', resource='secret') new_secret, value = create_secret(user, **data) return {'id': new_secret.id, 'value': value}, 201 @users_namespace.route('/<string:uid>/secrets/<string:sid>') @users_namespace.param('uid', "User's id") @users_namespace.param('sid', "Secret id") @users_namespace.response(404, 'User not found') class UserSecretController(Resource): @jwt_required @users_namespace.response(200, 'User secret detail') def get(self, uid: str, sid: str): permissions.PermissionsService().require.sysadmin() user = general.find_user(uid) secret = general.find_secret(user, sid) return SCHEMAS.dump('secret', secret) @jwt_required @users_namespace.response(204, 'User secret deleted') def delete(self, uid: str, sid: str): user = general.find_user(uid) permissions.PermissionsService().require.sysadmin_or_self(uid) delete_secret(user, sid) return '', 204 @jwt_required @users_namespace.response(204, 'User secret updated') @users_namespace.response(403, 'Not allowed to update user secret') def put(self, uid: str, sid: str): permissions.PermissionsService().require.sysadmin_or_self(uid) data = rest_helpers.parse_request_data(action='update', resource='secret', partial=True) user = general.find_user(uid) secret = general.find_secret(user, sid) general.update_entity(secret, data, ['name', 'expires_at']) return '', 204 # # Helper functions # Loading
portal/rest/workers.py +1 −59 Original line number Diff line number Diff line Loading @@ -5,7 +5,6 @@ from portal import logger from portal.rest import rest_helpers from portal.rest.schemas import SCHEMAS from portal.service import general, permissions from portal.service.secrets import create_secret, delete_secret from portal.service.workers import WorkerService workers_namespace = Namespace('workers') Loading Loading @@ -65,60 +64,3 @@ class WorkerResource(Resource): worker = general.find_worker(wid) WorkerService(worker=worker).update_worker(data) return '', 204 No newline at end of file @workers_namespace.route('/<string:wid>/secrets') @workers_namespace.param('wid', "Worker's id") @workers_namespace.response(404, 'Worker not found') class WorkerSecretsController(Resource): @jwt_required @workers_namespace.response(200, 'List worker secrets') def get(self, wid: str): permissions.PermissionsService().require.sysadmin() worker = general.find_worker(wid) return SCHEMAS.dump('secrets', worker.secrets) @jwt_required # @workers_namespace.response(201, 'Created worker secret', model=secret_schema) def post(self, wid: str): permissions.PermissionsService().require.sysadmin() data = rest_helpers.parse_request_data(action='create', resource='secret') worker = general.find_worker(wid) new_secret, value = create_secret(worker, **data) return {'id': new_secret.id, 'value': value}, 201 @workers_namespace.route('/<string:wid>/secrets/<string:sid>') @workers_namespace.param('wid', "Worker's id") @workers_namespace.param('sid', "Secret id") @workers_namespace.response(404, 'Worker not found') class WorkerSecretController(Resource): @jwt_required @workers_namespace.response(200, 'Worker secret detail') def get(self, wid: str, sid: str): permissions.PermissionsService().require.sysadmin() worker = general.find_worker(wid) secret = general.find_secret(worker, sid) return SCHEMAS.dump('secret', secret) @jwt_required @workers_namespace.response(204, 'Worker secret deleted') def delete(self, wid: str, sid: str): permissions.PermissionsService().require.sysadmin() worker = general.find_worker(wid) delete_secret(worker, sid) return '', 204 @jwt_required @workers_namespace.response(204, 'Worker secret updated') @workers_namespace.response(403, 'Not allowed to update worker secret') def put(self, wid: str, sid: str): permissions.PermissionsService().require.sysadmin() data = rest_helpers.parse_request_data(action='update', resource='secret', partial=True) worker = general.find_worker(wid) secret = general.find_secret(worker, sid) general.update_entity(secret, data, ['name', 'expires_at']) return '', 204
tests/rest/test_client.pydeleted 100644 → 0 +0 −47 Original line number Diff line number Diff line import json import pytest from tests.rest import utils @pytest.fixture def student_credentials(): return json.dumps({ "type": "username_password", "identifier": "student1", "secret": "123456" }) @pytest.fixture def worker_credentials(): return json.dumps({ "type": "secret", "identifier": "executor", "secret": "executor_secret" }) def test_client_detail_using_student_cred(client, student_credentials): path = f'/client' response = utils.make_request( client, path, 'get', credentials=student_credentials) print(f"Response: {response.data}") assert response.status_code == 200 assert response.mimetype == 'application/json' data = response.json assert data['username'] == 'student1' assert data['type'] == 'ClientType.USER' def test_client_detail_using_executor_cred(client, worker_credentials): path = f'/client' response = utils.make_request( client, path, 'get', credentials=worker_credentials) print(f"Response: {response.data}") assert response.status_code == 200 assert response.mimetype == 'application/json' data = response.json assert data['name'] == 'executor' assert data['type'] == 'ClientType.WORKER'