Unverified Commit 59f2ce4b authored by Peter Stanko's avatar Peter Stanko
Browse files

Full refactor of the service layer

parent 302dfef3
Pipeline #13268 passed with stage
in 13 minutes and 9 seconds
......@@ -8,8 +8,7 @@ from management.data.data_dev import init_dev_data
from management.data.data_prod import init_prod_data
from management.data.data_test import init_test_data
from portal.database.models import Course, Role, Secret, Submission, SubmissionState, User
from portal.service import general
from portal.service.general import write_entity
from portal.service.rest import RestService
from portal.tools import time
......@@ -23,6 +22,7 @@ class DataManagement(object):
self.app = app
self.db = db
self.creator = shared.DataFactory(self.db)
self.rest = RestService()
def reset_db(self):
"""Resets the database
......@@ -43,14 +43,14 @@ class DataManagement(object):
else:
init_dev_data(db=self.db, app=self.app)
def delete_user(self, name):
def delete_user(self, name: str):
"""Deletes user
Args:
name(str): name of the user
"""
with self.app.app_context():
user = general.find_user(name)
general.delete_entity(user)
user = self.rest.users.find(name)
self.rest.users(user).delete()
def update_users_password(self, name: str, password: str) -> User:
"""Updates user's password
......@@ -60,7 +60,7 @@ class DataManagement(object):
Returns(User): Updated user
"""
with self.app.app_context():
user = general.find_user(name)
user = self.rest.find.user(name)
user.set_password(password=password)
self.db.session.add(user)
self.db.session.commit()
......@@ -105,7 +105,7 @@ class DataManagement(object):
Returns(Role): Created role
"""
with self.app.app_context():
course = general.find_course(course_name)
course = self.rest.find.course(course_name)
role = self.creator.scaffold_role(
course, role_type=role_type, name=name)
self.db.session.commit()
......@@ -118,17 +118,17 @@ class DataManagement(object):
def activate_project(self, course: str, project: str):
with self.app.app_context():
course = general.find_course(course)
project = general.find_project(course, project)
course = self.rest.find.course(course)
project = self.rest.find.project(course, project)
days_allow_to = time.current_time() + datetime.timedelta(days=1000)
project.config.archive_from = None
project.config.submissions_allowed_to = days_allow_to
write_entity(project)
self.rest.projects.write_entity(project)
return project
def list_projects(self, course: str):
with self.app.app_context():
course = general.find_course(course)
course = self.rest.find.course(course)
for project in course.projects:
print(f"{project.name}: {project.description}")
......@@ -146,14 +146,14 @@ class DataManagement(object):
with self.app.app_context():
for submission in Submission.query.all():
submission.change_state(SubmissionState.CANCELLED)
write_entity(submission)
self.rest.submissions.write_entity(submission)
self.db.session.commit()
def cancel_submission(self, sid):
with self.app.app_context():
submission = general.find_submission(sid)
submission = self.rest.find.submission(sid)
submission.change_state(SubmissionState.CANCELLED)
write_entity(submission)
self.rest.submissions.write_entity(submission)
self.db.session.commit()
def clean_all_submissions(self):
......
......@@ -6,7 +6,6 @@ from storage import UploadedEntity
import portal.tools.worker_client
from portal import logger, tools
from portal.database import Project, Submission, SubmissionState, Worker
from portal.service import general
log = logger.get_logger(__name__)
......@@ -15,6 +14,8 @@ class SubmissionProcessor:
def __init__(self, submission: Submission, params: dict = None):
self._submission = submission
self._params = params
from portal.service.rest import RestService
self._rest = RestService()
@property
def submission(self) -> Submission:
......@@ -43,7 +44,7 @@ class SubmissionProcessor:
self.submission.state = state
self.submission.async_task_id = None
general.write_entity(self.submission)
self._rest.submissions.write_entity(self.submission)
def submission_enqueue_ended(self):
log.info(f"[ASYNC] Submission enqueue ended {self.submission.log_name}: {self.submission}")
......
......@@ -3,20 +3,21 @@ from storage import UploadedEntity
from portal import storage
from portal.async_celery import celery_app, submission_processor
from portal.service.general import find_course, find_project, find_submission, write_entity
log = get_task_logger(__name__)
@celery_app.task(name='upload-submission-to-storage')
def process_submission(new_submission_id: str):
new_submission = find_submission(new_submission_id)
from portal.service.rest import RestService
rest_service = RestService()
new_submission = rest_service.find.submission(new_submission_id)
project = new_submission.project
course = project.course
if not project.config.test_files_commit_hash:
log.warning(f"Project test files not found: {project.log_name}")
update_project_test_files(course_id=course.id, project_id=project.id)
new_submission = find_submission(new_submission_id)
new_submission = rest_service.find.submission(new_submission_id)
processor = submission_processor.SubmissionProcessor(new_submission)
processor.process_submission()
......@@ -24,7 +25,9 @@ def process_submission(new_submission_id: str):
@celery_app.task(name='upload-results-to-storage')
def upload_results_to_storage(new_submission_id: str, path: str):
path = str(path)
new_submission = find_submission(new_submission_id)
from portal.service.rest import RestService
rest_service = RestService()
new_submission = rest_service.find.submission(new_submission_id)
processor = submission_processor.SubmissionProcessor(new_submission)
file_params = dict(source=dict(url=path, type='zip'))
processor.upload_result(path=path, file_params=file_params)
......@@ -32,23 +35,31 @@ def upload_results_to_storage(new_submission_id: str, path: str):
@celery_app.task(name='clone-submission-files')
def clone_submission_files(source_id: str, target_id: str):
source = find_submission(source_id)
target = find_submission(target_id)
from portal.service.rest import RestService
rest_service = RestService()
source = rest_service.find.submission(source_id)
target = rest_service.find.submission(target_id)
processor = submission_processor.SubmissionProcessor(source)
processor.clone(target)
@celery_app.task(name='start-processing-submission')
def start_processing_submission(submission_id: str, submission_params):
submission = find_submission(submission_id)
from portal.service.rest import RestService
rest_service = RestService()
submission = rest_service.find.submission(submission_id)
processor = submission_processor.SubmissionProcessor(submission, submission_params)
processor.send_to_worker()
@celery_app.task(name='update-project-test-files')
def update_project_test_files(course_id: str, project_id: str):
course = find_course(course_id)
project = find_project(course, project_id)
from portal.service.rest import RestService
rest_service = RestService()
course = rest_service.find.course(course_id)
project = rest_service.find.project(course, project_id)
log.info(f"[ASYNC] Updating test files for project: {project.log_name}")
params = {
'from_dir': project.config.test_files_subdir,
......@@ -60,4 +71,4 @@ def update_project_test_files(course_id: str, project_id: str):
updated_entity: UploadedEntity = storage.test_files.update(entity_id=project.id, **params)
project.config.test_files_commit_hash = updated_entity.version
log.debug(f"Updated project config {project.log_name}: {project.config}")
write_entity(project.config)
rest_service.projects.write_entity(project.config)
from flask_jwt_extended import jwt_required
from flask_restplus import Namespace, Resource
from flask_restplus import Namespace
from portal import logger
from portal.database.models import ClientType
from portal.rest import rest_helpers
from portal.rest.custom_resource import CustomResource
from portal.rest.schemas import SCHEMAS
from portal.service import permissions, general
from portal.service.secrets import create_secret, delete_secret
client_namespace = Namespace('client')
clients_namespace = Namespace('clients')
......@@ -15,12 +14,11 @@ log = logger.get_logger(__name__)
@client_namespace.route('')
class ClientController(Resource):
class ClientController(CustomResource):
@jwt_required
# @users_namespace.response(200, 'List of users', model=users_schema)
def get(self):
perm_service = permissions.PermissionsService()
client = perm_service.client_owner
client = self.permissions.client_owner
schema = SCHEMAS.user() if client.type == ClientType.USER else SCHEMAS.worker()
return schema.dump(client)[0], 200
......@@ -28,21 +26,21 @@ class ClientController(Resource):
@clients_namespace.route('/<string:cid>/secrets')
@clients_namespace.param('wid', "Client's id")
@clients_namespace.response(404, 'Client not found')
class ClientSecretsController(Resource):
class ClientSecretsController(CustomResource):
@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)
self.permissions.require.sysadmin_or_self(cid)
client = self.rest.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)
self.permissions.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)
client = self.find.client(cid)
new_secret, value = self.rest.secrets(client=client).create(**data)
return {'id': new_secret.id, 'value': value}, 201
......@@ -50,35 +48,33 @@ class ClientSecretsController(Resource):
@clients_namespace.param('wid', "Client's id")
@clients_namespace.param('sid', "Secret id")
@clients_namespace.response(404, 'Client not found')
class ClientSecretController(Resource):
class ClientSecretController(CustomResource):
@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)
self.rest.permissions.require.sysadmin_or_self(cid)
client = self.rest.find.client(cid)
secret = self.rest.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)
self.rest.permissions.require.sysadmin_or_self(cid)
client = self.find.client(cid)
secret = self.find.secret(client, sid)
self.rest.secrets(secret).delete()
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)
self.rest.permissions.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'])
client = self.find.client(cid)
secret = self.find.secret(client, sid)
self.rest.secrets(secret).update(**data)
return '', 204
from flask import request
from flask_jwt_extended import jwt_required
from flask_restplus import Namespace, Resource
from flask_restplus import Namespace
from portal import logger
from portal.rest import rest_helpers
from portal.rest.custom_resource import CustomResource
from portal.rest.schemas import SCHEMAS
from portal.service import permissions
from portal.service.auth import find_client
from portal.service.courses import CourseService
from portal.service.errors import ForbiddenError, PortalAPIError
from portal.service.filters import filter_course_dump
from portal.service.general import find_course
courses_namespace = Namespace('courses')
log = logger.get_logger(__name__)
@courses_namespace.route('')
class CourseList(Resource):
class CourseList(CustomResource):
@jwt_required
# @courses_namespace.response(200, 'Courses list', model=courses_schema)
@courses_namespace.response(403, 'Not allowed to see courses')
def get(self):
# authorization
permissions.PermissionsService().require.sysadmin()
self.permissions.require.sysadmin()
courses_list = CourseService().find_all_courses()
courses_list = self.rest.courses.find_all()
return SCHEMAS.dump('courses', courses_list)
@jwt_required
# @courses_namespace.response(200, 'Created course', model=course_schema)
# @courses_namespace.response(403, 'Not allowed to create course', model=course_schema)
def post(self):
permissions.PermissionsService().require.sysadmin()
self.permissions.require.sysadmin()
data = rest_helpers.parse_request_data(resource='course', action='create')
new_course = CourseService().create_course(**data)
new_course = self.rest.courses.create(**data)
return SCHEMAS.dump('course', new_course), 201
@courses_namespace.route('/<string:cid>')
@courses_namespace.param('cid', 'Course id')
@courses_namespace.response(404, 'Course not found')
class CourseResource(Resource):
class CourseResource(CustomResource):
@jwt_required
# @courses_namespace.response(200, 'Course found', model=course_schema)
@courses_namespace.response(403, 'Not allowed to see course')
def get(self, cid: str):
client = find_client()
course = find_course(cid)
client = self.rest.auth.client
course = self.rest.find.course(cid)
# authorization
perm_service = permissions.PermissionsService(course=course)
perm_service = self.permissions(course=course)
if perm_service.check.permissions(['view_course_full']):
return SCHEMAS.dump('course', course)
......@@ -67,36 +64,36 @@ class CourseResource(Resource):
@courses_namespace.response(204, 'Course deleted')
@courses_namespace.response(403, 'Not allowed to delete course')
def delete(self, cid: str):
permissions.PermissionsService().require.sysadmin()
course = find_course(cid)
CourseService(course=course).delete_course()
self.permissions.require.sysadmin()
course = self.rest.find.course(cid)
self.rest.courses(course).delete()
return '', 204
@jwt_required
@courses_namespace.response(204, 'Course updated')
@courses_namespace.response(403, 'Not allowed to update course')
def put(self, cid: str):
course = find_course(cid)
course = self.rest.find.course(cid)
# authorization
permissions.PermissionsService(course=course).require.update_course()
self.permissions(course=course).require.update_course()
data = rest_helpers.parse_request_data(action='update', resource='course', partial=True)
CourseService(course=course).update_course(data)
self.rest.courses(course).update(**data)
return '', 204
@courses_namespace.route('/<string:cid>/notes_access_token')
@courses_namespace.param('cid', 'Course id')
@courses_namespace.response(404, 'Course not found')
class CourseNotesToken(Resource):
class CourseNotesToken(CustomResource):
@jwt_required
@courses_namespace.response(
403, 'Not allowed to manipulate with course\'s notes access token')
@courses_namespace.response(200, 'Course notes token')
def get(self, cid):
course = find_course(cid)
course = self.find.course(cid)
# authorization
permissions.PermissionsService(course=course).require.course_access_token()
self.permissions(course=course).require.course_access_token()
return course.notes_access_token
@jwt_required
......@@ -104,54 +101,54 @@ class CourseNotesToken(Resource):
@courses_namespace.response(
403, 'Not allowed to update course\'s notes access token')
def put(self, cid):
course = find_course(cid)
course = self.find.course(cid)
# authorization
permissions.PermissionsService(course=course).require.course_access_token()
self.permissions(course=course).require.course_access_token()
json_data = rest_helpers.require_data(action='update_notes_token', resource='course')
CourseService(course=course).update_notes_token(json_data['token'])
self.rest.courses(course).update_notes_token(json_data['token'])
return '', 204
@courses_namespace.route('/<string:cid>/import')
@courses_namespace.param('cid', 'Course id')
@courses_namespace.response(404, 'Course not found')
class CourseImport(Resource):
class CourseImport(CustomResource):
@jwt_required
# @courses_namespace.response(200, 'Course has been imported', model=course_schema)
@courses_namespace.response(403, 'Not allowed to import course')
@courses_namespace.response(400, 'Cannot import course to itself.')
def put(self, cid: str):
course = find_course(cid)
course = self.find.course(cid)
# authorization
permissions.PermissionsService(course=course).require.update_course()
self.permissions(course=course).require.update_course()
data = rest_helpers.parse_request_data(
schema=SCHEMAS.course_import, action='import', resource='course'
)
source_course = find_course(data['source_course'])
source_course = self.find.course(data['source_course'])
if source_course == course:
raise PortalAPIError(
400, f'[IMPORT] Cannot import course to itself. (id: {cid})')
config = data['config']
copied_course = CourseService(course=course).copy_course(course, config)
copied_course = self.rest.courses(course).copy_course(course, config)
return SCHEMAS.dump('course', copied_course)
@courses_namespace.route('/<string:cid>/users')
@courses_namespace.param('cid', 'Course id')
@courses_namespace.response(404, 'Course not found')
class CourseUsers(Resource):
class CourseUsers(CustomResource):
@jwt_required
# @courses_namespace.response(200, 'Users in the course', model=users_schema)
@courses_namespace.response(403, 'Not allowed to see users in the course')
def get(self, cid):
course = find_course(cid)
permissions.PermissionsService(course=course).require.permissions(['view_course_full'])
course = self.find.course(cid)
self.permissions(course=course).require.permissions(['view_course_full'])
group_ids = request.args.getlist('group')
role_ids = request.args.getlist('role')
users = CourseService(course=course).get_users_filtered(group_ids, role_ids)
users = self.rest.courses(course).get_users_filtered(group_ids, role_ids)
return SCHEMAS.dump('users', users)
from flask_restplus import Resource
from portal.service.find import FindService
from portal.service.permissions import PermissionsService
from portal.service.rest import RestService
class CustomResource(Resource):
@property
def rest(self) -> RestService:
if not hasattr(self, '__rest_service'):
setattr(self, '__rest_service', RestService())
return getattr(self, '__rest_service')
@property
def find(self) -> FindService:
return self.rest.find
@property
def permissions(self) -> PermissionsService:
return self.rest.permissions
from typing import Union
from flask import Flask, Response, make_response, redirect, request, session
from flask_oauthlib.client import OAuth, OAuthRemoteApp
from flask_restplus import Namespace, Resource
from flask_restplus import Namespace
from portal import oauth, logger
from portal import logger, oauth
from portal.rest import rest_api
from portal.service import general
from portal.service.users import UserService
from portal.rest.custom_resource import CustomResource
log = logger.get_logger(__name__)
......@@ -20,7 +20,7 @@ def extract_user_info(me: dict) -> dict:
name=me['name'],
username=me['username'],
email=me['email']
)
)
def gitlab_enabled(app: Flask) -> bool:
......@@ -46,14 +46,14 @@ def create_gitlab_app(oauth_app: OAuth) -> Union[OAuthRemoteApp, None]:
access_token_method='POST',
consumer_key=client_id,
consumer_secret=client_secret
)
)
gitlab = create_gitlab_app(oauth_app=oauth)
@oauth_namespace.route('/login')
class OAuthLogin(Resource):
class OAuthLogin(CustomResource):
def get(self):
if not gitlab:
return {'message': 'Gitlab OAuth is not enabled'}, 404
......@@ -64,20 +64,48 @@ class OAuthLogin(Resource):
return gitlab.authorize(callback=callback)
@oauth_namespace.route('/login/authorized')
class OAuthLoginAuthorized(CustomResource):
def get(self):
if not gitlab:
return {'message': 'Gitlab OAuth is not enabled'}, 404
resp = gitlab.authorized_response()
if resp is None:
return 'Access denied: reason=%s error=%s' % (
request.args['error'],
request.args['error_description']
)
token = extract_token(resp)
login = login_to_gitlab_with_token(token)
log.debug(f"[GITLAB] Gitlab Login response: {login}")
return login
@gitlab.tokengetter
def get_gitlab_oauth_token():
token = session.get('gitlab_token')
return token
def user_oauth_register(user_info):
new_user = UserService().create_user(
from portal.service.rest import RestService
rest = RestService()
new_user = rest.users.create(
uco=None,
username=user_info['username'],
name=user_info['name'],
email=user_info['email'],
is_admin=False
)
)
log.debug(f"[GITLAB] Created user={new_user}")
return new_user
def user_login(user_info) -> Response:
user = general.find_user(user_info['username'], throws=False)