Verified Commit 9a32bcd4 authored by Peter Stanko's avatar Peter Stanko
Browse files

Add gitlab_url as cookie

parent a866d4e9
...@@ -16,7 +16,7 @@ from werkzeug.security import check_password_hash, generate_password_hash ...@@ -16,7 +16,7 @@ from werkzeug.security import check_password_hash, generate_password_hash
from portal import db from portal import db
from portal.database.exceptions import PortalDbError from portal.database.exceptions import PortalDbError
from portal.database.mixins import EntityBase, NamedMixin from portal.database.mixins import EntityBase, NamedMixin
from portal.database.types import JSONEncodedDict from portal.database.types import JSONEncodedDict, YAMLEncodedDict
from portal.tools import time from portal.tools import time
from portal.tools.time import normalize_time from portal.tools.time import normalize_time
...@@ -580,10 +580,10 @@ class ProjectConfig(db.Model, EntityBase): ...@@ -580,10 +580,10 @@ class ProjectConfig(db.Model, EntityBase):
test_files_subdir = db.Column(db.String(50)) # git subdir test_files_subdir = db.Column(db.String(50)) # git subdir
test_files_commit_hash = db.Column(db.String(128)) # hash of the latest revision of test_files test_files_commit_hash = db.Column(db.String(128)) # hash of the latest revision of test_files
file_whitelist = db.Column(db.Text) file_whitelist = db.Column(db.Text)
pre_submit_script = db.Column(db.Text) pre_submit_script = db.Column(YAMLEncodedDict)
post_submit_script = db.Column(db.Text) post_submit_script = db.Column(YAMLEncodedDict)
submission_parameters = db.Column(db.Text) submission_parameters = db.Column(YAMLEncodedDict)
submission_scheduler_config = db.Column(db.Text) submission_scheduler_config = db.Column(YAMLEncodedDict)
_submissions_allowed_from = db.Column(db.TIMESTAMP(timezone=True)) _submissions_allowed_from = db.Column(db.TIMESTAMP(timezone=True))
_submissions_allowed_to = db.Column(db.TIMESTAMP(timezone=True)) _submissions_allowed_to = db.Column(db.TIMESTAMP(timezone=True))
......
import json import json
import sqlalchemy import sqlalchemy
import yaml
from sqlalchemy import TypeDecorator from sqlalchemy import TypeDecorator
...@@ -17,4 +18,20 @@ class JSONEncodedDict(TypeDecorator): ...@@ -17,4 +18,20 @@ class JSONEncodedDict(TypeDecorator):
def process_result_value(self, value, dialect): def process_result_value(self, value, dialect):
if value is not None: if value is not None:
value = json.loads(value) value = json.loads(value)
return value return value
\ No newline at end of file
class YAMLEncodedDict(TypeDecorator):
"Represents an immutable structure as a yaml-encoded string."
impl = sqlalchemy.Text
def process_bind_param(self, value, dialect):
if value is not None:
value = yaml.safe_dump(value)
return value
def process_result_value(self, value, dialect):
if value is not None:
value = yaml.safe_load(value)
return value
...@@ -106,6 +106,7 @@ class GitlabFacade(GeneralFacade): ...@@ -106,6 +106,7 @@ class GitlabFacade(GeneralFacade):
'gitlab_token_type': token_type, 'gitlab_token_type': token_type,
'gitlab_username': user_info.username, 'gitlab_username': user_info.username,
'username': user_info.username, 'username': user_info.username,
'gitlab_url': self._config.get('GITLAB_URL')
} }
return flask.jsonify(params) if not redir else self._make_login_response(params) return flask.jsonify(params) if not redir else self._make_login_response(params)
......
...@@ -9,6 +9,25 @@ from portal.service.projects import ProjectService ...@@ -9,6 +9,25 @@ from portal.service.projects import ProjectService
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
def _check_gl_repo_size(gl_project, project):
max_repo_size = project.config.submission_scheduler_config.get('repo_max_size')
if max_repo_size:
repo_size = gl_project.statistics['repository_size']
if repo_size > max_repo_size:
raise errors.SubmissionRefusedError(f"Your repository is bigger than the limit:"
f" {repo_size} > {max_repo_size}")
def _check_gl_repo_visibility(gl_project, project):
required_repo_visibility = project.config.submission_scheduler_config.get('repo_visibility')
if required_repo_visibility:
repo_visibility = gl_project.visibility
if required_repo_visibility != repo_visibility:
raise errors.SubmissionRefusedError(f"Your repository visibility "
f"is not {required_repo_visibility}."
f" Actual: {repo_visibility}")
class ProjectsFacade(GeneralCRUDFacade): class ProjectsFacade(GeneralCRUDFacade):
def __init__(self): def __init__(self):
super().__init__(ProjectService, 'Project') super().__init__(ProjectService, 'Project')
...@@ -67,6 +86,8 @@ class ProjectsFacade(GeneralCRUDFacade): ...@@ -67,6 +86,8 @@ class ProjectsFacade(GeneralCRUDFacade):
""" """
client = client or self.client client = client or self.client
result = self._service(project).check_submission_create(client) result = self._service(project).check_submission_create(client)
if self._services.kontr_gitlab.is_authorized and not project.submit_configurable:
result = self._check_gitlab_params(client, project)
return result return result
def get_test_files(self, project: Project): def get_test_files(self, project: Project):
...@@ -100,3 +121,11 @@ class ProjectsFacade(GeneralCRUDFacade): ...@@ -100,3 +121,11 @@ class ProjectsFacade(GeneralCRUDFacade):
f"{self.client_name}") f"{self.client_name}")
result = self._services.is_api(project.course).create_notepad(project=project) result = self._services.is_api(project.course).create_notepad(project=project)
return result return result
def _check_gitlab_params(self, client: Client, project: Project):
gl_repo = self.services.kontr_gitlab.build_repo_name(client, project)
gl_project = self._services.kontr_gitlab.get_project(gl_repo)
_check_gl_repo_size(gl_project, project)
_check_gl_repo_visibility(gl_project, project)
return True
import flask
from flask import request from flask import request
from flask_jwt_extended import jwt_required from flask_jwt_extended import jwt_required
from flask_restplus import Namespace from flask_restplus import Namespace
...@@ -209,6 +210,37 @@ class ProjectSubmissions(CustomResource): ...@@ -209,6 +210,37 @@ class ProjectSubmissions(CustomResource):
self.facades.projects.check_submission_create(project, client=user) self.facades.projects.check_submission_create(project, client=user)
@projects_namespace.route(
'/courses/<string:cid>/projects/<string:pid>/submissions/check')
@projects_namespace.param('cid', 'Course id')
@projects_namespace.param('pid', 'Project id')
@projects_namespace.response(404, 'Course not found')
@projects_namespace.response(404, 'Project not found')
class ProjectSubmissionsCheck(CustomResource):
@jwt_required
@access_log
# @projects_namespace.response(201, 'Project submission create', model=submission_schema)
def get(self, cid: str, pid: str):
course = self.find.course(cid)
user = request.args.get('user')
user = self.find.user(user) if user is not None else self.client
# authorization
self.permissions(course=course).require.create_submission(user)
# check if a new submission can be created by this user in this project
project = self.find.project(course, pid)
self._check_user_can_create_submission(project, user)
log.info(f"[REST] Check Create submission in {project.log_name} "
f"by {self.client.log_name} for user {user.log_name}")
return flask.jsonify({'message': 'User is able to create submission'}), 200
def _check_user_can_create_submission(self, project, user):
if self.client.type == ClientType.USER:
client_instance = self.find.user(self.client.id)
if not client_instance.is_admin:
self.facades.projects.check_submission_create(project, client=user)
@projects_namespace.route('/courses/<string:cid>/projects/<string:pid>/files') @projects_namespace.route('/courses/<string:cid>/projects/<string:pid>/files')
@projects_namespace.param('cid', 'Course id') @projects_namespace.param('cid', 'Course id')
@projects_namespace.param('pid', 'Project id') @projects_namespace.param('pid', 'Project id')
......
...@@ -2,11 +2,14 @@ ...@@ -2,11 +2,14 @@
Errors in the service layer Errors in the service layer
""" """
import json import json
import logging
from typing import Union from typing import Union
from portal.database import Course, Project, User from portal.database import Course, Project, User
from portal.tools import time from portal.tools import time
log = logging.getLogger(__name__)
class PortalError(Exception): class PortalError(Exception):
"""Base exception class """Base exception class
...@@ -37,6 +40,7 @@ class PortalAPIError(PortalError): ...@@ -37,6 +40,7 @@ class PortalAPIError(PortalError):
super().__init__() super().__init__()
self._code = code self._code = code
self._message = message self._message = message
log.error(f"[ERR] API ERROR[{code}]: {message}")
@property @property
def code(self) -> int: def code(self) -> int:
......
import logging import logging
from typing import List, Optional from typing import List, Optional
from urllib.parse import urlparse
import gitlab import gitlab
from gitlab.v4 import objects from gitlab.v4 import objects
from portal import tools from portal import tools
from portal.database import Submission from portal.database import Submission, models
from portal.service import errors from portal.service import errors
from portal.service.general import GeneralService from portal.service.general import GeneralService
...@@ -34,8 +35,7 @@ class GitlabService(GeneralService): ...@@ -34,8 +35,7 @@ class GitlabService(GeneralService):
@property @property
def gl_client(self): def gl_client(self):
if self._client is None: if self._client is None:
log.debug(f"[GL_CLIENT] Creating GL client for " log.debug(f"[GL_CLIENT] Creating GL client for token type - [{self.token_type}]")
f"token[{self.token_type}]: {self.token}")
self._client = self.get_client(token=self._token, token_type=self._token_type) self._client = self.get_client(token=self._token, token_type=self._token_type)
return self._client return self._client
...@@ -53,7 +53,8 @@ class GitlabService(GeneralService): ...@@ -53,7 +53,8 @@ class GitlabService(GeneralService):
params[f'{token_type}_token'] = token params[f'{token_type}_token'] = token
gitlab_url = self._config.get('GITLAB_URL') gitlab_url = self._config.get('GITLAB_URL')
instance: gitlab.Gitlab = gitlab.Gitlab(gitlab_url, **params) instance: gitlab.Gitlab = gitlab.Gitlab(gitlab_url, **params)
log.debug(f"[CL_CLIENT] Instance {instance.url} - {params}") reducted_params = {(k, v) for (k, v) in params.items() if not k.endswith('_token')}
log.debug(f"[CL_CLIENT] Instance {instance.url} - {reducted_params}")
try: try:
if token: if token:
...@@ -149,6 +150,16 @@ class GitlabService(GeneralService): ...@@ -149,6 +150,16 @@ class GitlabService(GeneralService):
return None return None
return full_name return full_name
@property
def git_domain(self) -> str:
return self._config.get('GITLAB_BASE_DOMAIN') or urlparse(self.gl_client.url).hostname
def build_ssh_url(self, client: models.Client, project: models.Project):
return f"git@{self.git_domain}:{self.build_repo_name(client, project)}.git"
def build_repo_name(self, client: models.Client, project: models.Project):
return f"{client.codename}/{project.course.codename}"
class KontrGitlabService(GitlabService): class KontrGitlabService(GitlabService):
def __init__(self): def __init__(self):
......
...@@ -137,7 +137,8 @@ class ProjectService(GeneralService): ...@@ -137,7 +137,8 @@ class ProjectService(GeneralService):
if not ProjectService(self.project).can_create_submission(client): if not ProjectService(self.project).can_create_submission(client):
log.warning(f"[WARN] Already active submission for: {self.project.log_name}") log.warning(f"[WARN] Already active submission for: {self.project.log_name}")
raise errors.SubmissionRefusedError( raise errors.SubmissionRefusedError(
f"Submission for project {self.project.log_name} already active.") f"User {user.log_name} has already active "
f"submission for project {self.project.log_name}")
self._check_latest_submission(user) self._check_latest_submission(user)
return True return True
......
...@@ -231,8 +231,9 @@ def test_set_gitlab_service_cookies(client, gl_api_url, gl_user, gl_cookies): ...@@ -231,8 +231,9 @@ def test_set_gitlab_service_cookies(client, gl_api_url, gl_user, gl_cookies):
response = rest_tools.make_request(client, f'/gitlab/service_token') response = rest_tools.make_request(client, f'/gitlab/service_token')
assert_response(response) assert_response(response)
res_proj = rest_tools.extract_data(response) res_proj = rest_tools.extract_data(response)
assert len(res_proj) == 4 assert len(res_proj) == 5
assert res_proj['username'] == gl_cookies['username'] assert res_proj['username'] == gl_cookies['username']
assert res_proj['gitlab_username'] == gl_cookies['gitlab_username'] assert res_proj['gitlab_username'] == gl_cookies['gitlab_username']
assert res_proj['gitlab_token'] == gl_cookies['gitlab_token'] assert res_proj['gitlab_token'] == gl_cookies['gitlab_token']
assert res_proj['gitlab_token_type'] == gl_cookies['gitlab_token_type'] assert res_proj['gitlab_token_type'] == gl_cookies['gitlab_token_type']
assert res_proj['gitlab_url']
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment