Unverified Commit 03c1e2fc authored by Peter Stanko's avatar Peter Stanko
Browse files

Ultimate permission refactor

parent bce9eba4
......@@ -3,18 +3,16 @@ Main application module
- Creates instance of the flask app named app
- registers commands
"""
import logging as logger
import click
from flask import Flask
from flask.cli import AppGroup, run_command
import portal
import portal.logging as logging_config
from management.data import DataManagement
from portal import create_app, db
from portal import create_app, db, logger
logging_config.load_config()
logger.load_config()
log = logger.getLogger(__name__)
......
......@@ -7,9 +7,10 @@ import random
import string
from flask_sqlalchemy import SQLAlchemy
from portal import logger
from portal.database.models import Worker, Course, Group, Project, ReviewItem, Role, User
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
def password_generator(size=16, chars=string.ascii_letters + string.digits):
......
......@@ -2,9 +2,9 @@
Main Portal module
"""
import logging
import os
from typing import Union
from celery import Celery
from flask import Flask
from flask_cors import CORS
......@@ -14,7 +14,7 @@ from flask_oauthlib.client import OAuth
from flask_sqlalchemy import SQLAlchemy
from storage import Storage
from portal import rest
from portal import logger, rest
from portal.config import CONFIGURATIONS
from portal.tools.gitlab_client import GitlabFactory
from portal.tools.ldap_client import LDAPWrapper
......@@ -29,7 +29,7 @@ migrate = Migrate(db=db)
gitlab_factory = GitlabFactory()
ldap_wrapper = LDAPWrapper()
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
def configure_app(app: Flask, env: str = None,
......@@ -80,7 +80,7 @@ def configure_storage(app: Flask) -> Flask:
submissions_dir=app.config.get('PORTAL_STORAGE_SUBMISSIONS_DIR'),
workspace_dir=app.config.get('PORTAL_STORAGE_WORKSPACE_DIR'),
results_dir=app.config.get('PORTAL_STORAGE_RESULTS_DIR')
)
)
storage.init_storage(**storage_config)
return app
......@@ -128,4 +128,3 @@ def get_celery(app: Flask) -> Union[Celery, None]:
return None
from portal.async_celery import celery_app
return celery_app
......@@ -3,10 +3,11 @@ from pathlib import Path
from storage import UploadedEntity
from portal import logger
from portal.database import Project, Submission, SubmissionState
from portal.service import general
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
class SubmissionProcessor:
......
......@@ -8,13 +8,13 @@ Types:
"""
import datetime
import logging
import os
import tempfile
from portal import logger
from portal.tools import paths
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
# pylint: disable=too-few-public-methods
......
......@@ -2,7 +2,9 @@
Logging configuration module
"""
import logging
from logging.config import dictConfig
import coloredlogs
FORMATTERS = {
......@@ -49,4 +51,9 @@ def load_config(conf_type=None):
if conf_type is not None:
print("Not implemented yet!")
dictConfig(LOGGING_CONF)
coloredlogs.install()
def getLogger(*args, **kwargs):
logger = logging.getLogger(*args, **kwargs)
coloredlogs.install(logger=logger)
return logger
import logging
from flask import request
from flask_jwt_extended import jwt_required
from flask_restplus import Namespace, Resource
from portal import logger
from portal.rest import rest_helpers
from portal.rest.schemas import course_import_schema, course_schema, courses_schema, users_schema
from portal.service import permissions
......@@ -12,10 +12,9 @@ from portal.service.courses import copy_course, create_course, delete_course, fi
from portal.service.errors import ForbiddenError, PortalAPIError
from portal.service.filters import filter_course_dump
from portal.service.general import find_course
from portal.service.permissions import check_client, require_client
courses_namespace = Namespace('courses')
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
@courses_namespace.route('')
......@@ -24,10 +23,8 @@ class CourseList(Resource):
# @courses_namespace.response(200, 'Courses list', model=courses_schema)
@courses_namespace.response(403, 'Not allowed to see courses')
def get(self):
client = find_client()
# authorization
if not permissions.check_sysadmin(client):
raise ForbiddenError(uid=client.id)
permissions.PermissionsService().require.sysadmin()
courses_list = find_all_courses()
return courses_schema.dump(courses_list)
......@@ -36,10 +33,7 @@ class CourseList(Resource):
# @courses_namespace.response(200, 'Created course', model=course_schema)
# @courses_namespace.response(403, 'Not allowed to create course', model=course_schema)
def post(self):
client = find_client()
# authorization
if not permissions.check_sysadmin(client):
raise ForbiddenError(uid=client.id)
permissions.PermissionsService().require.sysadmin()
data = rest_helpers.parse_request_data(
course_schema, resource='course', action='create')
......@@ -59,14 +53,11 @@ class CourseResource(Resource):
client = find_client()
course = find_course(cid)
# authorization
if (check_client(client=client,
course=course,
permissions=['view_course_full'])):
perm_service = permissions.PermissionsService(course=course)
if perm_service.check.client(['view_course_full']):
return course_schema.dump(course)
elif (check_client(client=client,
course=course,
permissions=['view_course_limited'])):
elif perm_service.check.client(['view_course_limited']):
dump = course_schema.dump(course)
filtered_course = filter_course_dump(course, dump.data, client)
return filtered_course
......@@ -77,11 +68,7 @@ class CourseResource(Resource):
@courses_namespace.response(204, 'Course deleted')
@courses_namespace.response(403, 'Not allowed to delete course')
def delete(self, cid: str):
client = find_client()
# authorization
if not permissions.check_sysadmin(client):
raise ForbiddenError(uid=client.id)
permissions.PermissionsService().require.sysadmin()
course = find_course(cid)
delete_course(course)
return '', 204
......@@ -90,12 +77,9 @@ class CourseResource(Resource):
@courses_namespace.response(204, 'Course updated')
@courses_namespace.response(403, 'Not allowed to update course')
def put(self, cid: str):
client = find_client()
course = find_course(cid)
# authorization
if not (check_client(client=client, course=course,
permissions=['update_course'])):
raise ForbiddenError(uid=client.id)
permissions.PermissionsService(course=course).require.client(['update_course'])
data = rest_helpers.parse_request_data(
schema=course_schema, action='update', resource='course', partial=True
......@@ -113,12 +97,9 @@ class CourseNotesToken(Resource):
403, 'Not allowed to manipulate with course\'s notes access token')
@courses_namespace.response(200, 'Course notes token')
def get(self, cid):
client = find_client()
course = find_course(cid)
# authorization
perm = ['handle_notes_access_token']
require_client(client=client, course=course, permissions=perm)
permissions.PermissionsService(course=course).require.client(['handle_notes_access_token'])
return course.notes_access_token
@jwt_required
......@@ -126,11 +107,9 @@ class CourseNotesToken(Resource):
@courses_namespace.response(
403, 'Not allowed to update course\'s notes access token')
def put(self, cid):
client = find_client()
course = find_course(cid)
# authorization
perm = ['handle_notes_access_token']
require_client(client=client, course=course, permissions=perm)
permissions.PermissionsService(course=course).require.client(['handle_notes_access_token'])
json_data = rest_helpers.require_data(
action='update_notes_token', resource='course')
......@@ -148,11 +127,9 @@ class CourseImport(Resource):
@courses_namespace.response(403, 'Not allowed to import course')
@courses_namespace.response(400, 'Cannot import course to itself.')
def put(self, cid: str):
client = find_client()
course = find_course(cid)
# authorization
require_client(client=client, course=course,
permissions=['update_course'])
permissions.PermissionsService(course=course).require.client(['update_course'])
data = rest_helpers.parse_request_data(
course_import_schema, action='import', resource='course'
......@@ -177,12 +154,9 @@ class CourseUsers(Resource):
# @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):
client = find_client()
course = find_course(cid)
permissions.PermissionsService(course=course).require.client(['view_course_full'])
group_ids = request.args.getlist('group')
role_ids = request.args.getlist('role')
# authorization
perm = ['view_course_full']
require_client(client=client, course=course, permissions=perm)
users = get_users_filtered(course, group_ids, role_ids)
return users_schema.dump(users)[0]
......@@ -7,10 +7,11 @@ from flask_restplus import abort
from marshmallow.exceptions import ValidationError
from sqlalchemy.exc import SQLAlchemyError
from portal import logger
from portal.rest import rest_api
from portal.service.errors import PortalAPIError, IncorrectCredentialsError
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
def load_errors(app: Flask):
......
......@@ -4,12 +4,12 @@ from flask import Flask, Response, make_response, redirect, request, session
from flask_oauthlib.client import OAuth, OAuthRemoteApp
from flask_restplus import Namespace, Resource
from portal import oauth
from portal import oauth, logger
from portal.rest import rest_api
from portal.service import general
from portal.service.users import create_user
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
oauth_namespace = Namespace('oauth')
......
import logging
from flask import request
from flask_jwt_extended import jwt_required
from flask_restplus import Namespace, Resource
from portal import logger
from portal.rest import rest_helpers
from portal.rest.schemas import group_import_schema, group_schema, groups_schema, projects_schema, \
client_list_update_schema, users_schema
from portal.service import auth, general
from portal.service.general import find_client_owner
from portal.service.errors import ForbiddenError
from portal.rest.schemas import client_list_update_schema, group_import_schema, group_schema, \
groups_schema, projects_schema, users_schema
from portal.service import auth, general, permissions
from portal.service.groups import add_project_to_group, add_single_user_to_group, create_group, \
delete_group, find_projects_in_group, find_users_in_group_by_role, import_group, list_groups, \
remove_project_from_group, remove_single_user_from_group, update_group, \
update_user_group_membership
from portal.service.permissions import check_client, require_client
groups_namespace = Namespace('')
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
@groups_namespace.route('/courses/<string:cid>/groups')
......@@ -37,11 +33,9 @@ class GroupsList(Resource):
# @groups_namespace.response(201, 'Group created', model=group_schema)
@groups_namespace.response(403, 'Not allowed to create group')
def post(self, cid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
require_client(client=client, course=course,
permissions=['update_course'])
permissions.PermissionsService(course=course).require.update_course()
data = rest_helpers.parse_request_data(
group_schema, action='create', resource='group'
......@@ -61,11 +55,9 @@ class GroupResource(Resource):
# @groups_namespace.response(201, 'Group', model=group_schema)
@groups_namespace.response(403, 'Not allowed to get group')
def get(self, cid: str, gid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
permissions = ['view_course_full', 'view_course_limited']
require_client(client=client, course=course, permissions=permissions)
permissions.PermissionsService(course=course).require.view_course()
group = general.find_group(course, gid)
return group_schema.dump(group)
......@@ -74,11 +66,9 @@ class GroupResource(Resource):
@groups_namespace.response(204, 'Group deleted')
@groups_namespace.response(403, 'Not allowed to delete group')
def delete(self, cid: str, gid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
require_client(client=client, course=course,
permissions=['update_course'])
permissions.PermissionsService(course=course).require.update_course()
group = general.find_group(course, gid)
delete_group(group)
......@@ -88,11 +78,9 @@ class GroupResource(Resource):
@groups_namespace.response(204, 'Group updated')
@groups_namespace.response(403, 'Not allowed to update group')
def put(self, cid: str, gid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
permissions = ['write_groups', 'update_course']
require_client(client=client, course=course, permissions=permissions)
permissions.PermissionsService(course=course).require.write_groups()
data = rest_helpers.parse_request_data(
group_schema, action='update', resource='group', partial=True
......@@ -111,16 +99,12 @@ class GroupUsersList(Resource):
# @groups_namespace.response(200, 'List of users in the group', model=users_schema)
@groups_namespace.response(403, 'Not allowed to get users in the group')
def get(self, cid: str, gid: str):
client = auth.find_client()
course = general.find_course(cid)
role_id = request.args.get('role')
group = general.find_group(course, gid)
# authorization
if not check_client(client=client, course=course, permissions=['view_course_full']) or \
(check_client(client=client, course=course, permissions=['view_course_limited'])
and find_client_owner(client) in group.users):
raise ForbiddenError(uid=client.id)
permissions.PermissionsService(course=course).require.belongs_to_group(group)
users = find_users_in_group_by_role(course, group, role_id)
return users_schema.dump(users)
......@@ -129,11 +113,9 @@ class GroupUsersList(Resource):
@groups_namespace.response(204, 'User\'s list updated')
@groups_namespace.response(403, 'Not allowed to add users to the group')
def put(self, cid: str, gid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
permissions = ['write_groups', 'update_course']
require_client(client=client, course=course, permissions=permissions)
permissions.PermissionsService(course=course).require.write_groups()
data = rest_helpers.parse_request_data(
client_list_update_schema, action='update', resource='group_membership'
......@@ -159,12 +141,10 @@ class GroupAddOrDeleteSingleUser(Resource):
@groups_namespace.response(204, 'User\'s list updated')
@groups_namespace.response(403, 'Not allowed to add user to the group')
def put(self, cid: str, gid: str, uid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
permissions = ['write_groups', 'update_course']
require_client(client=client, course=course, permissions=permissions)
permissions.PermissionsService(course=course).require.write_groups()
group = general.find_group(course, gid)
user = general.find_user(uid)
......@@ -175,12 +155,9 @@ class GroupAddOrDeleteSingleUser(Resource):
@groups_namespace.response(204, 'User\'s list updated')
@groups_namespace.response(403, 'Not allowed to delete users to the group')
def delete(self, cid: str, gid: str, uid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
permissions = ['write_groups', 'update_course']
require_client(client=client, course=course, permissions=permissions)
permissions.PermissionsService(course=course).require.write_groups()
group = general.find_group(course, gid)
user = general.find_user(uid)
......@@ -196,14 +173,11 @@ class GroupAddOrDeleteSingleUser(Resource):
class GroupProjectsList(Resource):
@jwt_required
def get(self, cid: str, gid: str):
client = auth.find_client()
course = general.find_course(cid)
group = general.find_group(course, gid)
# authorization
if not check_client(client=client, course=course, permissions=['view_course_full']) or \
(check_client(client=client, course=course, permissions=['view_course_limited'])
and find_client_owner(client) in group.users):
raise ForbiddenError(uid=client.id)
permissions.PermissionsService(course=course).require.belongs_to_group(group)
projects = find_projects_in_group(group)
return projects_schema.dump(projects)
......@@ -221,12 +195,10 @@ class GroupAddOrDeleteProject(Resource):
@groups_namespace.response(204, 'Projects\'s list updated')
@groups_namespace.response(403, 'Not allowed to add project to the group')
def put(self, cid: str, gid: str, pid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
permissions = ['write_groups', 'update_course']
require_client(client=client, course=course, permissions=permissions)
permissions.PermissionsService(course=course).require.write_groups()
group = general.find_group(course, gid)
project = general.find_project(course, pid)
......@@ -238,12 +210,10 @@ class GroupAddOrDeleteProject(Resource):
@groups_namespace.response(
403, 'Not allowed to delete project from the group')
def delete(self, cid: str, gid: str, pid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
permissions = ['write_groups', 'update_course']
require_client(client=client, course=course, permissions=permissions)
permissions.PermissionsService(course=course).require.write_groups()
group = general.find_group(course, gid)
project = general.find_project(course, pid)
......@@ -260,12 +230,9 @@ class GroupImport(Resource):
@groups_namespace.response(404, 'Group not found')
@groups_namespace.response(403, 'Not allowed to import to the group')
def put(self, cid: str):
client = auth.find_client()
target_course = general.find_course(cid)
# authorization
permissions = ['write_groups', 'update_course']
require_client(client=client, course=target_course,
permissions=permissions)
permissions.PermissionsService(course=target_course).require.write_groups()
data = rest_helpers.parse_request_data(
group_import_schema, action='import', resource='group'
)
......
......@@ -5,13 +5,13 @@ from flask_jwt_extended import create_access_token, create_refresh_token, get_jw
jwt_refresh_token_required, jwt_required
from flask_restplus import Namespace, Resource, fields
from portal import jwt
from portal import jwt, logger
from portal.database.models import Client
from portal.service.auth import login_gitlab, login_username_password, login_secret
from portal.service.errors import PortalAPIError, UnauthorizedError
from portal.service.general import find_client, find_client_owner
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
auth_namespace = Namespace('auth')
......@@ -67,13 +67,6 @@ class Logout(Resource):
return dict(access_token=None, refresh_token=None)
#@jwt.user_claims_loader
#def add_claims_to_access_token(client_id: str):
# client = find_client(client_id)
# owner = find_client_owner(client)
# return dict(owner_id=owner.id)
def authorized_client():
client = get_jwt_identity()
if not client:
......
import logging
from flask_restplus import Namespace, Resource, fields
from portal import logger
management_namespace = Namespace('management')
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
status_schema = management_namespace.model('StatusSchema', {
'status': fields.String()
......
import json
import logging
from flask import request
from flask_jwt_extended import jwt_required
from flask_restplus import Namespace, Resource
from portal import logger
from portal.database.models import ProjectState
from portal.rest import rest_helpers
from portal.rest.schemas import config_schema, config_schema_reduced, project_schema, \
projects_schema, submission_create_schema, submission_schema, submissions_schema
from portal.service import auth, general
from portal.service import auth, general, permissions
from portal.service.errors import ForbiddenError, SubmissionRefusedError
from portal.service.permissions import check_client, require_client
from portal.service.projects import can_create_submission, create_project, delete_project, \
find_project_submissions, list_projects, update_project, update_project_config, update_project_test_files_hash
find_project_submissions, list_projects, update_project, update_project_config, \
update_project_test_files_hash
from portal.service.submissions import SubmissionsService
from portal.tools import time
projects_namespace = Namespace('') # pylint: disable=invalid-name
log = logging.getLogger(__name__)
log = logger.getLogger(__name__)
@projects_namespace.route('/courses/<string:cid>/projects')
......@@ -38,11 +39,9 @@ class ProjectsList(Resource):
@projects_namespace.response(404, 'Course not found')
# @projects_namespace.response(201, 'Project created', model=project_schema)
def post(self, cid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
perm = ['update_course']
require_client(client=client, course=course, permissions=perm)
permissions.PermissionsService(course=course).require.update_course()
data = rest_helpers.parse_request_data(
project_schema, action='create', resource='project'
......@@ -62,11 +61,9 @@ class ProjectResource(Resource):
# @projects_namespace.response(200, 'Project found', model=project_schema)
@projects_namespace.response(403, 'Not allowed to get project')
def get(self, cid: str, pid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
perm = ['view_course_full', 'view_course_limited']
require_client(client=client, course=course, permissions=perm)
permissions.PermissionsService(course=course).require.view_course()
project = general.find_project(course, pid)
return project_schema.dump(project)
......@@ -75,11 +72,9 @@ class ProjectResource(Resource):
@projects_namespace.response(204, 'Project deleted')
@projects_namespace.response(403, 'Not allowed to delete project')
def delete(self, cid: str, pid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
perm = ['update_course']
require_client(client=client, course=course, permissions=perm)
permissions.PermissionsService(course=course).require.update_course()
project = general.find_project(course, pid)
delete_project(project)
......@@ -89,11 +84,9 @@ class ProjectResource(Resource):
@projects_namespace.response(204, 'Project updated')
@projects_namespace.response(403, 'Not allowed to update project')
def put(self, cid: str, pid: str):
client = auth.find_client()
course = general.find_course(cid)
# authorization
perm = ['write_projects', 'update_course']
require_client(client=client