Commit a026872c authored by Barbora Kompišová's avatar Barbora Kompišová
Browse files

auth first draft, decorator fixes

parent 3db66ef3
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
flask = "*"
flask-sqlalchemy = "*"
pytest = "*"
flask-restful = "*"
marshmallow = "*"
flask-jwt-extended = "*"
marshmallow-enum = "*"
storage = { git="git@gitlab.fi.muni.cz:grp-kontr2/kontr-storage-module.git", editable='true' }
gitpython = "*"
[dev-packages]
[requires]
python_version = "3.6"
[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"
[packages]
flask = "*"
flask-sqlalchemy = "*"
pytest = "*"
flask-restful = "*"
marshmallow = "*"
flask-jwt-extended = "*"
marshmallow-enum = "*"
storage = { git="git@gitlab.fi.muni.cz:grp-kontr2/kontr-storage-module.git", editable='true' }
gitpython = "*"
[dev-packages]
[requires]
python_version = "3.6"
\ No newline at end of file
from flask import Flask
from flask_jwt_extended import JWTManager
from portal.config import CONFIGURATIONS
from flask_sqlalchemy import SQLAlchemy
import os
from storage import Storage
db = SQLAlchemy()
jwt = JWTManager()
# TODO - urgent
storage = Storage({
"submissions_dir": "todo",
......@@ -32,11 +33,8 @@ def create_app():
config_app(app)
# database bind to app
db.init_app(app)
# init the jwt
jwt.init_app(app)
return app
# app = create_app()
import portal.database.models
......@@ -4,11 +4,14 @@ import datetime
from sqlalchemy import event
from sqlalchemy.ext.hybrid import hybrid_property
from werkzeug.security import generate_password_hash, check_password_hash
from portal import db
from portal.database.mixins import EntityBase
from portal.database.exceptions import PortalDbError
from portal.tools import time
# uuid as primary key source:
# https://stackoverflow.com/questions/36806403/cant-render-element-of-type-class-sqlalchemy-dialects-postgresql-base-uuid
......@@ -29,12 +32,30 @@ class User(db.Model, EntityBase):
username = db.Column(db.String(15), unique=True, nullable=False)
name = db.Column(db.String(50))
is_admin = db.Column(db.Boolean, default=False)
password_hash = db.Column(db.String(50), default=None) # optional - after password change
password_hash = db.Column(db.String(60), default=None) # optional - after password change
submissions = db.relationship("Submission", back_populates="user", cascade="all, delete-orphan",
passive_deletes=True)
review_items = db.relationship("ReviewItem", back_populates="user")
def set_password(self, password: str):
"""Sets password for the user
Args:
password(str): Unhashed password
"""
self.password_hash = generate_password_hash(password)
def verify_password(self, password: str) -> bool:
"""
Args:
password(str): Unhashed password
Returns(bool): True if the password is valid, False otherwise
"""
return check_password_hash(self.password_hash, password)
@property
def courses(self):
result = []
......@@ -65,9 +86,12 @@ class Course(db.Model, EntityBase):
# Info on cascades: http://docs.sqlalchemy.org/en/latest/orm/cascades.html
# Cascades here simulate a composition - roles, groups and projects exist only when associated to a course
roles = db.relationship("Role", back_populates="course", cascade="all, delete-orphan", passive_deletes=True)
groups = db.relationship("Group", back_populates="course", cascade="all, delete-orphan", passive_deletes=True)
projects = db.relationship("Project", back_populates="course", cascade="all, delete-orphan", passive_deletes=True)
roles = db.relationship("Role", back_populates="course", cascade="all, delete-orphan",
passive_deletes=True)
groups = db.relationship("Group", back_populates="course", cascade="all, delete-orphan",
passive_deletes=True)
projects = db.relationship("Project", back_populates="course", cascade="all, delete-orphan",
passive_deletes=True)
def __init__(self, name, codename) -> None:
self.name = name
......@@ -96,7 +120,8 @@ class Project(db.Model, EntityBase):
course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False)
course = db.relationship("Course", back_populates="projects", uselist=False)
submissions = db.relationship("Submission", back_populates="project", cascade="all, delete-orphan",
submissions = db.relationship("Submission", back_populates="project",
cascade="all, delete-orphan",
passive_deletes=True)
__table_args__ = (
......@@ -113,9 +138,10 @@ class Project(db.Model, EntityBase):
def set_config(self, **kwargs):
for k, w in kwargs.items():
if k in ('test_files_source', 'file_whitelist', 'pre_submit_script', 'post_submit_script',
'submission_parameters', 'submission_scheduler_config', 'submissions_allowed_from',
'submissions_allowed_to', 'archive_from'):
if k in (
'test_files_source', 'file_whitelist', 'pre_submit_script', 'post_submit_script',
'submission_parameters', 'submission_scheduler_config', 'submissions_allowed_from',
'submissions_allowed_to', 'archive_from'):
setattr(self.config, k, w)
def __init__(self, course, name, test_files_source) -> None:
......@@ -203,7 +229,8 @@ class Role(db.Model, EntityBase):
course_id = db.Column(db.Integer, db.ForeignKey('course.id'), nullable=False)
course = db.relationship("Course", back_populates="roles", uselist=False)
permissions = db.relationship("RolePermissions", back_populates='role', cascade="all, delete-orphan",
permissions = db.relationship("RolePermissions", back_populates='role',
cascade="all, delete-orphan",
passive_deletes=True, lazy='joined', uselist=False)
def set_permissions(self, **kwargs):
......@@ -357,7 +384,8 @@ class ReviewItem(db.Model, EntityBase):
review_id = db.Column(db.Integer, db.ForeignKey('review.id'), nullable=False)
review = db.relationship("Review", uselist=False, back_populates="review_items")
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
user = db.relationship("User", uselist=False, back_populates="review_items") # the review item's author
user = db.relationship("User", uselist=False,
back_populates="review_items") # the review item's author
content = db.Column(db.Text)
file = db.Column(db.String(100), nullable=False)
line = db.Column(db.Integer, nullable=False)
......
from flask_jwt_extended import create_access_token, create_refresh_token, \
jwt_refresh_token_required, get_jwt_identity, jwt_required
from flask_restful import Resource, Api
from flask import Blueprint, request, jsonify
from portal.service.auth import login_user
from portal.service.errors import UnauthorizedError
auth = Blueprint('courses', __name__, url_prefix='/auth')
auth_api = Api(auth)
class Login(Resource):
def post(self):
username = request.json.get('username', None)
password = request.json.get('password', None)
gitlab_access_token = request.json.get('gitlab_access_token', None)
user = login_user(gitlab_access_token, password, username)
ret = {
'access_token': create_access_token(identity=user.id),
'refresh_token': create_refresh_token(identity=user.id)
}
return jsonify(ret), 200
class Refresh(Resource):
@jwt_refresh_token_required
def post(self):
current_user = get_jwt_identity()
if not current_user:
raise UnauthorizedError()
ret = {
'access_token': create_access_token(identity=current_user)
}
return jsonify(ret), 200
class Logout(Resource):
@jwt_required
def post(self):
# TODO
current_user = get_jwt_identity()
if not current_user:
raise UnauthorizedError()
ret = {
'access_token': None,
'refresh_token': None
}
return jsonify(ret), 200
auth_api.add_resource(Login, '/login')
auth_api.add_resource(Refresh, '/refresh')
auth_api.add_resource(Logout, '/logout')
......@@ -22,16 +22,12 @@ class CourseResource(Resource):
def get(self, cid):
log.info(f"GET to {request.url}")
course = service.find_course(cid)
if not course:
abort(404, message=f"Could not find course {cid}.")
return course_schema.dump(course)
@error_handler
def delete(self, cid):
log.info(f"DELETE to {request.url}")
course = service.find_course(cid)
if not course:
abort(404, message=f"Could not find course {cid}.")
service.delete_entity(course)
return '', 204
......@@ -39,7 +35,6 @@ class CourseResource(Resource):
def put(self, cid):
log.info(f"PUT to {request.url}")
course = service.find_course(cid)
json_data = request.get_json()
if not json_data:
abort(400, message='No data provided for course update.')
......
from portal.service.errors import IncorrectPasswordError
from portal.service.service import find_user
def login_user(gitlab_access_token=None, password=None, username=None):
if gitlab_access_token:
return auth_gitlab_access_token(
username=username,
gitlab_access_token=gitlab_access_token
)
return auth_username_password(username=username, password=password)
def auth_gitlab_access_token(username, gitlab_access_token):
"""Authentication using gitlab token
Steps:
Send token to gitlab
Verify that token is for user
Args:
gitlab_access_token:
Returns:
"""
def auth_username_password(username, password):
user = find_user(username)
if user.verify_password(password=password):
return user
raise IncorrectPasswordError()
......@@ -61,3 +61,10 @@ class ForbiddenError(PortalAPIError):
message['note'] = note
super().__init__(code=401, message=message)
class IncorrectPasswordError(UnauthorizedError):
def __init__(self):
super().__init__(note="Password is incorrect")
from portal.database.models import User, Course, Project, Group, Role, Submission, Review
from portal.database.models import User, Course, Project, Group, Role, Submission
def init_data(app, db):
......@@ -13,8 +13,10 @@ def init_data(app, db):
def create_users(db):
db.session.add(User(uco=445, email="foo@foo.cz", username="xfoo"))
db.session.add(User(uco=123, email="bar@bar.com", username="xbar"))
foo_user = User(uco=445, email="foo@bar.cz", username="xfoo")
foo_user.set_password('123456')
db.session.add(foo_user)
db.session.add(User(uco=123, email="bar@baz.cz", username="xbar"))
db.session.commit()
......
......@@ -39,8 +39,6 @@ import json
- reviews associated with the user's submissions: accessible through the user's submissions
'''
user_schema = UserSchema()
users_schema = UserSchema(many=True, only=('id', 'uco', 'username', 'email'))
def assert_user(expected: User, actual: dict):
......@@ -88,7 +86,6 @@ def test_create(client):
assert_user_in(db_users, resp_new_user)
#follow_redirects=True
def test_list(client):
response = client.get('/users/')
assert response.status_code == 200
......@@ -107,7 +104,6 @@ def test_read(client):
assert response.mimetype == 'application/json'
data = json.loads(str(response.get_data().decode("utf-8")))
print(f"-----------{data}--------------------")
assert_user(user, data)
......
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