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
f1ce5ea4
Commit
f1ce5ea4
authored
Mar 25, 2018
by
Barbora Kompišová
Browse files
Authorization + login&tests refactor
parent
0052e7df
Changes
25
Hide whitespace changes
Inline
Side-by-side
portal/database/__init__.py
View file @
f1ce5ea4
import
enum
class
SubmissionState
(
enum
.
Enum
):
CREATED
=
1
READY
=
2
QUEUED
=
3
IN_PROGRESS
=
4
FINISHED
=
5
CANCELLED
=
6
ABORTED
=
7
ARCHIVED
=
8
\ No newline at end of file
portal/database/models.py
View file @
f1ce5ea4
...
...
@@ -6,6 +6,7 @@ from sqlalchemy import event
from
sqlalchemy.ext.hybrid
import
hybrid_property
from
werkzeug.security
import
generate_password_hash
,
check_password_hash
from
database
import
SubmissionState
from
portal
import
db
from
portal.database.mixins
import
EntityBase
from
portal.database.exceptions
import
PortalDbError
...
...
@@ -136,6 +137,8 @@ class Project(db.Model, EntityBase):
)
def
state
(
self
,
timestamp
=
datetime
.
datetime
.
now
())
->
ProjectState
:
if
not
(
self
.
config
.
submissions_allowed_from
or
self
.
config
.
submissions_allowed_to
or
self
.
config
.
archive_from
):
return
ProjectState
.
INACTIVE
if
self
.
config
.
submissions_allowed_from
<=
timestamp
<
self
.
config
.
submissions_allowed_to
:
return
ProjectState
.
ACTIVE
elif
timestamp
>=
self
.
config
.
archive_from
:
...
...
@@ -268,30 +271,38 @@ class RolePermissions(db.Model, EntityBase):
role
=
db
.
relationship
(
"Role"
,
back_populates
=
"permissions"
,
uselist
=
False
)
# all default to False, explicit setting via role.set_permissions is required
viewCourseLimited
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
viewCourseFull
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
updateCourse
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
setNotesAccessToken
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
assignRoles
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
writeRole
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
readRole
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
writeProject
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
deleteProject
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
archiveProject
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
writeGroup
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
readGroup
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
deleteGroup
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
readAllSubmissions
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
readOwnSubmissions
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
readGroupSubmissions
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
# OPTIONAL
viewAllSubmissionFiles
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
createSubmission
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
resubmitSubmission
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
readAllReviews
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
readOwnReviews
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
writeOwnReview
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
writeAllReview
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
evaluateSubmission
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
view_course_limited
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
view_course_full
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
update_course
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
handle_notes_access_token
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
assign_roles
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
write_roles
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
read_roles
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
write_groups
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
read_groups
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
write_projects
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
read_projects
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
archive_projects
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
create_submissions
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
resubmit_submissions
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
evaluate_submissions
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
read_submissions_all
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
read_submissions_groups
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
read_submissions_own
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
read_all_submission_files
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
read_reviews_all
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
read_reviews_groups
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
read_reviews_own
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
write_reviews_all
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
write_reviews_group
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
write_reviews_own
=
db
.
Column
(
db
.
Boolean
,
default
=
False
,
nullable
=
False
)
def
__init__
(
self
,
role
):
self
.
role
=
role
...
...
@@ -326,17 +337,6 @@ class Group(db.Model, EntityBase):
return
self
.
id
==
other
.
id
class
SubmissionState
(
enum
.
Enum
):
CREATED
=
1
READY
=
2
QUEUED
=
3
IN_PROGRESS
=
4
FINISHED
=
5
CANCELLED
=
6
ABORTED
=
7
ARCHIVED
=
8
class
Submission
(
db
.
Model
,
EntityBase
):
__tablename__
=
'submission'
id
=
db
.
Column
(
db
.
Text
(
length
=
36
),
default
=
lambda
:
str
(
uuid
.
uuid4
()),
primary_key
=
True
)
...
...
@@ -418,7 +418,7 @@ class Component(db.Model, EntityBase):
__tablename__
=
'component'
id
=
db
.
Column
(
db
.
Text
(
length
=
36
),
default
=
lambda
:
str
(
uuid
.
uuid4
()),
primary_key
=
True
)
name
=
db
.
Column
(
db
.
String
(
30
),
nullable
=
False
,
unique
=
True
)
access_token
=
db
.
Column
(
db
.
String
(
10
0
))
secret
=
db
.
Column
(
db
.
String
(
25
0
))
ip_address
=
db
.
Column
(
db
.
String
(
50
))
...
...
portal/rest/__init__.py
View file @
f1ce5ea4
from
marshmallow
import
Schema
,
fields
,
validates_schema
,
ValidationError
from
marshmallow_enum
import
EnumField
from
portal.database.models
import
SubmissionState
from
portal.database.models
import
ProjectState
from
database
import
SubmissionState
class
UserSchema
(
Schema
):
id
=
fields
.
Str
(
dump_only
=
True
,
required
=
True
)
uco
=
fields
.
Int
(
required
=
True
)
email
=
fields
.
Email
(
required
=
True
)
username
=
fields
.
Str
(
required
=
True
)
username
=
fields
.
Str
()
name
=
fields
.
Str
()
is_admin
=
fields
.
Bool
(
default
=
False
)
is_admin
=
fields
.
Bool
(
default
=
False
,
missing
=
False
)
submissions
=
fields
.
Nested
(
'portal.rest.SubmissionSchema'
,
only
=
(
'id'
,
'note'
,
'state'
),
many
=
True
)
review_items
=
fields
.
Nested
(
'portal.rest.ReviewItemSchema'
,
only
=
(
'id'
,),
many
=
True
)
...
...
@@ -54,12 +55,12 @@ class SubmissionCreateSchema(Schema):
class
ProjectSchema
(
Schema
):
# TODO: add state as enum
id
=
fields
.
Str
(
dump_only
=
True
)
name
=
fields
.
Str
(
required
=
True
)
config
=
fields
.
Nested
(
'portal.rest.ProjectConfigSchema'
,
exclude
=
(
'id'
,
'_submissions_allowed_from'
,
'_submissions_allowed_to'
,
'_archive_from'
))
# TODO: check hybrid property behaviour
course
=
fields
.
Nested
(
CourseSchema
,
only
=
(
'id'
,
'name'
,
'codename'
))
# schema name in '' failed
config
=
fields
.
Nested
(
'portal.rest.ProjectConfigSchema'
,
exclude
=
(
'id'
,
'_submissions_allowed_from'
,
'_submissions_allowed_to'
,
'_archive_from'
))
state
=
EnumField
(
ProjectState
)
course
=
fields
.
Nested
(
CourseSchema
,
only
=
(
'id'
,
'name'
,
'codename'
))
submissions
=
fields
.
Nested
(
'portal.rest.SubmissionSchema'
,
many
=
True
,
only
=
(
'id'
,
'note'
,
'state'
,
'user'
))
...
...
@@ -90,30 +91,38 @@ class RoleSchema(Schema):
class
RolePermissionsSchema
(
Schema
):
id
=
fields
.
Str
(
dump_only
=
True
)
role
=
fields
.
Nested
(
RoleSchema
,
only
=
(
'id'
,
'name'
,
'description'
))
viewCourseLimited
=
fields
.
Bool
()
viewCourseFull
=
fields
.
Bool
()
updateCourse
=
fields
.
Bool
()
setNotesAccessToken
=
fields
.
Bool
()
assignRoles
=
fields
.
Bool
()
writeRole
=
fields
.
Bool
()
readRole
=
fields
.
Bool
()
writeProject
=
fields
.
Bool
()
deleteProject
=
fields
.
Bool
()
archiveProject
=
fields
.
Bool
()
writeGroup
=
fields
.
Bool
()
readGroup
=
fields
.
Bool
()
deleteGroup
=
fields
.
Bool
()
readAllSubmissions
=
fields
.
Bool
()
readOwnSubmissions
=
fields
.
Bool
()
readGroupSubmissions
=
fields
.
Bool
()
viewAllSubmissionFiles
=
fields
.
Bool
()
createSubmission
=
fields
.
Bool
()
resubmitSubmission
=
fields
.
Bool
()
readAllReviews
=
fields
.
Bool
()
readOwnReviews
=
fields
.
Bool
()
writeOwnReview
=
fields
.
Bool
()
writeAllReview
=
fields
.
Bool
()
evaluateSubmission
=
fields
.
Bool
()
view_course_limited
=
fields
.
Bool
()
view_course_full
=
fields
.
Bool
()
update_course
=
fields
.
Bool
()
handle_notes_access_token
=
fields
.
Bool
()
assign_roles
=
fields
.
Bool
()
write_roles
=
fields
.
Bool
()
read_roles
=
fields
.
Bool
()
write_groups
=
fields
.
Bool
()
read_groups
=
fields
.
Bool
()
write_projects
=
fields
.
Bool
()
read_projects
=
fields
.
Bool
()
archive_projects
=
fields
.
Bool
()
create_submissions
=
fields
.
Bool
()
resubmit_submissions
=
fields
.
Bool
()
evaluate_submissions
=
fields
.
Bool
()
read_submissions_all
=
fields
.
Bool
()
read_submissions_groups
=
fields
.
Bool
()
read_submissions_own
=
fields
.
Bool
()
read_all_submission_files
=
fields
.
Bool
()
read_reviews_all
=
fields
.
Bool
()
read_reviews_groups
=
fields
.
Bool
()
read_reviews_own
=
fields
.
Bool
()
write_reviews_all
=
fields
.
Bool
()
write_reviews_group
=
fields
.
Bool
()
write_reviews_own
=
fields
.
Bool
()
class
GroupSchema
(
Schema
):
...
...
@@ -178,6 +187,7 @@ class GroupImportSchema(Schema):
user_schema
=
UserSchema
(
strict
=
True
)
user_schema_reduced
=
UserSchema
(
strict
=
True
,
only
=
(
'id'
,
'username'
,
'uco'
,
'email'
,
'name'
))
users_schema
=
UserSchema
(
many
=
True
,
only
=
(
'id'
,
'username'
,
'uco'
,
'email'
))
submission_schema
=
SubmissionSchema
()
...
...
@@ -193,6 +203,7 @@ roles_schema = RoleSchema(many=True, only=('id', 'name', 'description', 'course'
permissions_schema
=
RolePermissionsSchema
()
course_schema
=
CourseSchema
(
strict
=
True
)
course_schema_reduced
=
CourseSchema
(
strict
=
True
,
only
=
(
'id'
,
'name'
,
'codename'
,
))
courses_schema
=
CourseSchema
(
many
=
True
,
only
=
(
'id'
,
'name'
,
'codename'
))
course_import_schema
=
CourseImportSchema
(
strict
=
True
)
...
...
portal/rest/auth/login.py
View file @
f1ce5ea4
...
...
@@ -3,37 +3,68 @@ from flask_jwt_extended import create_access_token, create_refresh_token, \
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
from
portal
import
jwt
from
portal.service.auth
import
login_user
,
login_component
from
portal.service.errors
import
UnauthorizedError
,
PortalAPIError
from
service
import
service
auth
=
Blueprint
(
'auth'
,
__name__
,
url_prefix
=
'/auth'
)
auth_api
=
Api
(
auth
)
@
jwt
.
user_claims_loader
def
add_claims_to_access_token
(
identity
):
data
=
'user'
if
service
.
find_component
(
identity
,
throws
=
False
):
data
=
'component'
return
{
'type'
:
data
}
# basic login - username + password (user) / name + secret (component)
class
Login
(
Resource
):
def
post
(
self
):
username
=
request
.
get_json
().
get
(
'username'
,
None
)
password
=
request
.
get_json
().
get
(
'password'
,
None
)
gitlab_access_token
=
request
.
get_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
)
data
=
request
.
get_json
()
if
not
data
.
get
(
'type'
):
raise
PortalAPIError
(
400
,
message
=
"Missing login type."
)
if
data
[
'type'
]
==
'user'
:
username
=
data
.
get
(
'username'
)
password
=
data
.
get
(
'password'
)
gitlab_access_token
=
data
.
get
(
'gitlab_access_token'
,
None
)
client
=
login_user
(
gitlab_access_token
,
password
,
username
)
elif
data
[
'type'
]
==
'component'
:
name
=
data
.
get
(
'name'
)
if
not
name
:
raise
PortalAPIError
(
400
,
message
=
"Missing component name."
)
secret
=
data
.
get
(
'secret'
)
if
not
secret
:
raise
PortalAPIError
(
400
,
message
=
"Missing component secret."
)
sender_ip_address
=
request
.
remote_addr
client
=
login_component
(
name
,
sender_ip_address
,
secret
)
else
:
raise
PortalAPIError
(
400
,
message
=
"Invalid login type."
)
response
=
{
'access_token'
:
create_access_token
(
identity
=
client
.
id
),
'refresh_token'
:
create_refresh_token
(
identity
=
client
.
id
)
}
return
ret
,
200
return
response
,
200
class
Refresh
(
Resource
):
@
jwt_refresh_token_required
def
post
(
self
):
c
urrent_user
=
get_jwt_identity
()
if
not
c
urrent_user
:
c
lient
=
get_jwt_identity
()
if
not
c
lient
:
raise
UnauthorizedError
()
ret
=
{
'access_token'
:
create_access_token
(
identity
=
c
urrent_user
)
'access_token'
:
create_access_token
(
identity
=
c
lient
)
}
return
jsonify
(
ret
),
200
...
...
portal/rest/courses/courses.py
View file @
f1ce5ea4
from
flask
import
Blueprint
,
request
from
flask_jwt_extended
import
jwt_required
from
flask_restful
import
Api
,
Resource
from
portal.database.models
import
Course
from
portal.rest
import
course_schema
,
courses_schema
,
course_import_schema
from
portal.service
import
service
from
portal.service.errors
import
PortalAPIError
from
portal.service.errors
import
PortalAPIError
,
ForbiddenError
from
portal.tools.decorators
import
error_handler
from
portal.tools.logging
import
log
from
service
import
policies
courses
=
Blueprint
(
'courses'
,
__name__
,
url_prefix
=
'/courses'
)
courses_api
=
Api
(
courses
)
...
...
@@ -14,22 +16,45 @@ courses_api = Api(courses)
class
CourseResource
(
Resource
):
@
error_handler
@
jwt_required
def
get
(
self
,
cid
):
log
.
info
(
f
"GET to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
return
course_schema
.
dump
(
course
)
# authorization
if
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'view_course_full'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'view_course_full'
])):
return
course_schema
.
dump
(
course
)
elif
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'view_course_limited'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'view_course_limited'
])):
return
course_schema
.
dump
(
service
.
filter_course_info
(
course
,
client
))
raise
ForbiddenError
(
uid
=
client
.
id
)
@
error_handler
@
jwt_required
def
delete
(
self
,
cid
):
client
=
service
.
find_client
()
log
.
info
(
f
"DELETE to
{
request
.
url
}
"
)
course
=
service
.
find_course
(
cid
)
service
.
delete_entity
(
course
)
return
''
,
204
# authorization
if
policies
.
check_sysadmin
(
client
):
service
.
delete_entity
(
course
)
return
''
,
204
raise
ForbiddenError
(
uid
=
client
.
id
)
@
error_handler
@
jwt_required
def
put
(
self
,
cid
):
log
.
info
(
f
"PUT to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
# authorization
if
not
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'update_course'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'update_course'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
json_data
=
request
.
get_json
()
if
not
json_data
:
raise
PortalAPIError
(
400
,
message
=
'No data provided for course update.'
)
...
...
@@ -45,14 +70,26 @@ class CourseResource(Resource):
class
CourseList
(
Resource
):
@
error_handler
@
jwt_required
def
get
(
self
):
log
.
info
(
f
"GET to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
# authorization
if
not
policies
.
check_sysadmin
(
client
):
raise
ForbiddenError
(
uid
=
client
.
id
)
courses
=
Course
.
query
.
all
()
return
courses_schema
.
dump
(
courses
)
@
error_handler
@
jwt_required
def
post
(
self
):
log
.
info
(
f
"POST to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
# authorization
if
not
policies
.
check_sysadmin
(
client
):
raise
ForbiddenError
(
uid
=
client
.
id
)
json_data
=
request
.
get_json
()
if
not
json_data
:
raise
PortalAPIError
(
400
,
message
=
'No data provided for course creation.'
)
...
...
@@ -70,15 +107,29 @@ class CourseList(Resource):
class
CourseNotesToken
(
Resource
):
@
error_handler
@
jwt_required
def
get
(
self
,
cid
):
log
.
info
(
f
"GET to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
# authorization
if
not
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'handleNotesAccessToken'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'handleNotesAccessToken'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
return
course
.
notes_access_token
@
error_handler
@
jwt_required
def
put
(
self
,
cid
):
log
.
info
(
f
"PUT to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
# authorization
if
not
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'handleNotesAccessToken'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'handleNotesAccessToken'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
json_data
=
request
.
get_json
()
if
not
json_data
:
raise
PortalAPIError
(
400
,
message
=
'No data provided for notes token update.'
)
...
...
@@ -91,10 +142,17 @@ class CourseNotesToken(Resource):
class
CourseImport
(
Resource
):
@
error_handler
@
jwt_required
def
put
(
self
,
cid
):
# TODO: import from IS MU
log
.
info
(
f
"PUT to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
# authorization
if
not
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'update_course'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'update_course'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
json_data
=
request
.
get_json
()
if
not
json_data
:
raise
PortalAPIError
(
400
,
message
=
'No data provided for notes token update.'
)
...
...
@@ -110,6 +168,6 @@ class CourseImport(Resource):
courses_api
.
add_resource
(
CourseResource
,
'/<string:cid>'
)
courses_api
.
add_resource
(
CourseList
,
'
/
'
)
courses_api
.
add_resource
(
CourseList
,
''
)
courses_api
.
add_resource
(
CourseNotesToken
,
"/<string:cid>/notes_access_token"
)
courses_api
.
add_resource
(
CourseImport
,
"/<string:cid>/import"
)
portal/rest/groups/groups.py
View file @
f1ce5ea4
from
flask
import
Blueprint
,
request
from
flask_jwt_extended
import
jwt_required
from
flask_restful
import
Api
,
Resource
from
portal.service
import
service
from
portal.tools.decorators
import
error_handler
from
portal.tools.logging
import
log
from
portal.database.models
import
Group
,
Role
from
portal.database.models
import
Group
from
portal.rest
import
group_schema
,
groups_schema
,
users_schema
,
user_list_update_schema
,
group_import_schema
from
portal.service.errors
import
PortalAPIError
from
portal.service.errors
import
PortalAPIError
,
ForbiddenError
from
service
import
policies
groups
=
Blueprint
(
'groups'
,
__name__
,
url_prefix
=
'/courses/<string:cid>/groups'
)
groups_api
=
Api
(
groups
)
...
...
@@ -14,25 +16,48 @@ groups_api = Api(groups)
class
GroupResource
(
Resource
):
@
error_handler
@
jwt_required
def
get
(
self
,
cid
,
gid
):
log
.
info
(
f
"GET to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
group
=
service
.
find_group
(
course
,
gid
)
# authorization
# note: read_groups should be enabled for students of a course TODO make default?
if
not
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'read_groups'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'read_groups'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
return
group_schema
.
dump
(
group
)
@
error_handler
@
jwt_required
def
delete
(
self
,
cid
,
gid
):
log
.
info
(
f
"DELETE to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
group
=
service
.
find_group
(
course
,
gid
)
# authorization
if
not
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'write_groups'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'write_groups'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
service
.
delete_entity
(
group
)
return
''
,
204
@
error_handler
@
jwt_required
def
put
(
self
,
cid
,
gid
):
log
.
info
(
f
"PUT to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
group
=
service
.
find_group
(
course
,
gid
)
# authorization
if
not
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'write_groups'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'write_groups'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
json_data
=
request
.
get_json
()
if
not
json_data
:
raise
PortalAPIError
(
400
,
message
=
'No data provided for group update.'
)
...
...
@@ -41,22 +66,35 @@ class GroupResource(Resource):
group
.
name
=
data
[
'name'
]
service
.
write_entity
(
group
)
log
.
debug
(
f
"Updated group
{
g
id
}
to
{
group
}
."
)
log
.
debug
(
f
"Updated group
{
g
roup
.
name
}
to
{
group
}
."
)
return
''
,
204
class
GroupsList
(
Resource
):
@
error_handler
@
jwt_required
def
get
(
self
,
cid
):
log
.
info
(
f
"GET to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
# authorization
if
not
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'read_groups'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'read_groups'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
return
groups_schema
.
dump
(
course
.
groups
)
@
error_handler
@
jwt_required
def
post
(
self
,
cid
):
log
.
info
(
f
"POST to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
# authorization
if
not
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'write_groups'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'write_groups'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)
json_data
=
request
.
get_json
()
if
not
json_data
:
raise
PortalAPIError
(
400
,
message
=
'No data provided for group creation.'
)
...
...
@@ -65,17 +103,23 @@ class GroupsList(Resource):
new_group
=
Group
(
course
=
course
,
name
=
data
[
'name'
])
service
.
write_entity
(
new_group
)
log
.
info
(
f
"Created group=
{
new_group
}
for course
{
c
id
}
"
)
log
.
info
(
f
"Created group=
{
new_group
}
for course
{
c
ourse
.
codename
}
"
)
return
group_schema
.
dump
(
new_group
)[
0
],
201
class
GroupUsersList
(
Resource
):
@
error_handler
@
jwt_required
def
get
(
self
,
cid
,
gid
):
log
.
info
(
f
"GET to
{
request
.
url
}
"
)
client
=
service
.
find_client
()
course
=
service
.
find_course
(
cid
)
group
=
service
.
find_group
(
course
,
gid
)
# authorization
if
not
(
policies
.
check_component
(
component
=
client
,
course
=
course
,
permissions
=
[
'read_groups'
])
or
policies
.
check_user
(
user
=
client
,
course
=
course
,
permissions
=
[
'read_groups'
])):
raise
ForbiddenError
(
uid
=
client
.
id
)