Unverified Commit 45860421 authored by Peter Stanko's avatar Peter Stanko
Browse files

Added clients endpoint for secrets management and tests for this endpoint

parent aab29ca1
Loading
Loading
Loading
Loading
+2 −1
Original line number Diff line number Diff line
@@ -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)
@@ -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
+63 −1
Original line number Diff line number Diff line
@@ -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__)

@@ -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


+0 −57
Original line number Diff line number Diff line
@@ -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')
@@ -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
#
+1 −59
Original line number Diff line number Diff line
@@ -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')
@@ -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.py

deleted100644 → 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