Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
Kontr 2.0
Portal API Backend
Commits
a026872c
Commit
a026872c
authored
Mar 18, 2018
by
Barbora Kompišová
Browse files
auth first draft, decorator fixes
parent
3db66ef3
Changes
9
Hide whitespace changes
Inline
Side-by-side
Pipfile
View file @
a026872c
[[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
portal/__init__.py
View file @
a026872c
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
portal/database/models.py
View file @
a026872c
...
...
@@ -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
(
5
0
),
default
=
None
)
# optional - after password change
password_hash
=
db
.
Column
(
db
.
String
(
6
0
),
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
)
...
...
portal/rest/auth/login.py
View file @
a026872c
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'
)
portal/rest/courses/courses.py
View file @
a026872c
...
...
@@ -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.'
)
...
...
portal/service/auth.py
0 → 100644
View file @
a026872c
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
()
portal/service/errors.py
View file @
a026872c
...
...
@@ -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"
)
sample_data/data_init.py
View file @
a026872c
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
()
...
...
tests/rest/test_user_rest.py
View file @
a026872c
...
...
@@ -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
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment