API Write Access with OAuth2 #104

This commit is contained in:
Daniel Grams 2021-02-05 16:31:19 +01:00
parent 742aa64350
commit 635d814aac
5 changed files with 215 additions and 4 deletions

View File

@ -18,6 +18,9 @@ from project.api.event.schemas import (
from project.api.organizer.schemas import (
OrganizerListRequestSchema,
OrganizerListResponseSchema,
OrganizerIdSchema,
OrganizerPostRequestSchema,
OrganizerPostRequestLoadSchema,
)
from project.api.event_reference.schemas import (
EventReferenceListRequestSchema,
@ -115,6 +118,26 @@ class OrganizationOrganizerListResource(BaseResource):
pagination = get_organizer_query(admin_unit.id, name).paginate()
return pagination
@doc(
summary="Add new organizer",
tags=["Organizations", "Organizers"],
security=[{"oauth2": ["organizer:write"]}],
)
@use_kwargs(OrganizerPostRequestSchema, location="json")
@marshal_with(OrganizerIdSchema, 201)
@require_oauth("organizer:write")
def post(self, id, **kwargs):
login_api_user_or_401(current_token.user)
admin_unit = get_admin_unit_for_manage_or_404(id)
access_or_401(admin_unit, "organizer:create")
organizer = OrganizerPostRequestLoadSchema().load(kwargs, session=db.session)
organizer.admin_unit_id = admin_unit.id
db.session.add(organizer)
db.session.commit()
return organizer, 201
class OrganizationPlaceListResource(BaseResource):
@doc(summary="List places of organization", tags=["Organizations", "Places"])

View File

@ -1,8 +1,19 @@
from project.api import add_api_resource
from flask_apispec import marshal_with, doc
from flask import make_response
from flask_apispec import marshal_with, doc, use_kwargs
from project.api.resources import BaseResource
from project.api.organizer.schemas import OrganizerSchema
from project.api.organizer.schemas import (
OrganizerSchema,
OrganizerPostRequestSchema,
OrganizerPostRequestLoadSchema,
OrganizerPatchRequestSchema,
OrganizerPatchRequestLoadSchema,
)
from project.models import EventOrganizer
from project.oauth2 import require_oauth
from authlib.integrations.flask_oauth2 import current_token
from project import db
from project.access import access_or_401, login_api_user_or_401
class OrganizerResource(BaseResource):
@ -11,6 +22,63 @@ class OrganizerResource(BaseResource):
def get(self, id):
return EventOrganizer.query.get_or_404(id)
@doc(
summary="Update organizer",
tags=["Organizers"],
security=[{"oauth2": ["organizer:write"]}],
)
@use_kwargs(OrganizerPostRequestSchema, location="json")
@marshal_with(None, 204)
@require_oauth("organizer:write")
def put(self, id, **kwargs):
login_api_user_or_401(current_token.user)
organizer = EventOrganizer.query.get_or_404(id)
access_or_401(organizer.adminunit, "organizer:update")
organizer = OrganizerPostRequestLoadSchema().load(
kwargs, session=db.session, instance=organizer
)
db.session.commit()
return make_response("", 204)
@doc(
summary="Patch organizer",
tags=["Organizers"],
security=[{"oauth2": ["organizer:write"]}],
)
@use_kwargs(OrganizerPatchRequestSchema, location="json")
@marshal_with(None, 204)
@require_oauth("organizer:write")
def patch(self, id, **kwargs):
login_api_user_or_401(current_token.user)
organizer = EventOrganizer.query.get_or_404(id)
access_or_401(organizer.adminunit, "organizer:update")
organizer = OrganizerPatchRequestLoadSchema().load(
kwargs, session=db.session, instance=organizer
)
db.session.commit()
return make_response("", 204)
@doc(
summary="Delete organizer",
tags=["Organizers"],
security=[{"oauth2": ["organizer:write"]}],
)
@marshal_with(None, 204)
@require_oauth("organizer:write")
def delete(self, id):
login_api_user_or_401(current_token.user)
organizer = EventOrganizer.query.get_or_404(id)
access_or_401(organizer.adminunit, "organizer:delete")
db.session.delete(organizer)
db.session.commit()
return make_response("", 204)
add_api_resource(
OrganizerResource,

View File

@ -1,7 +1,13 @@
from marshmallow import fields
from marshmallow import fields, validate
from project.api import marshmallow
from project.models import EventOrganizer
from project.api.location.schemas import LocationSchema
from project.api.location.schemas import (
LocationSchema,
LocationPostRequestSchema,
LocationPostRequestLoadSchema,
LocationPatchRequestSchema,
LocationPatchRequestLoadSchema,
)
from project.api.image.schemas import ImageSchema
from project.api.organization.schemas import OrganizationRefSchema
from project.api.schemas import PaginationRequestSchema, PaginationResponseSchema
@ -50,3 +56,50 @@ class OrganizerListResponseSchema(PaginationResponseSchema):
items = fields.List(
fields.Nested(OrganizerRefSchema), metadata={"description": "Organizers"}
)
class OrganizerPostRequestSchema(marshmallow.SQLAlchemySchema):
class Meta:
model = EventOrganizer
name = fields.Str(required=True, validate=validate.Length(min=3, max=255))
url = fields.Str(validate=[validate.URL(), validate.Length(max=255)], missing=None)
email = fields.Str(
validate=[validate.Email(), validate.Length(max=255)], missing=None
)
phone = fields.Str(validate=validate.Length(max=255), missing=None)
fax = fields.Str(validate=validate.Length(max=255), missing=None)
location = fields.Nested(LocationPostRequestSchema, missing=None)
class OrganizerPostRequestLoadSchema(OrganizerPostRequestSchema):
class Meta:
model = EventOrganizer
load_instance = True
location = fields.Nested(LocationPostRequestLoadSchema, missing=None)
class OrganizerPatchRequestSchema(marshmallow.SQLAlchemySchema):
class Meta:
model = EventOrganizer
name = fields.Str(validate=validate.Length(min=3, max=255), allow_none=True)
url = fields.Str(
validate=[validate.URL(), validate.Length(max=255)], allow_none=True
)
email = fields.Str(
validate=[validate.Email(), validate.Length(max=255)], allow_none=True
)
phone = fields.Str(validate=validate.Length(max=255), allow_none=True)
fax = fields.Str(validate=validate.Length(max=255), allow_none=True)
location = fields.Nested(LocationPatchRequestSchema, allow_none=True)
class OrganizerPatchRequestLoadSchema(OrganizerPatchRequestSchema):
class Meta:
model = EventOrganizer
load_instance = True
location = fields.Nested(LocationPatchRequestLoadSchema, allow_none=True)

View File

@ -38,6 +38,27 @@ def test_organizers(client, seeder, utils):
utils.get_ok(url)
def test_organizers_post(client, seeder, utils, app):
user_id, admin_unit_id = seeder.setup_api_access()
url = utils.get_url(
"api_v1_organization_organizer_list", id=admin_unit_id, name="crew"
)
response = utils.post_json(url, {"name": "Neuer Organisator"})
utils.assert_response_created(response)
assert "id" in response.json
with app.app_context():
from project.models import EventOrganizer
organizer = (
EventOrganizer.query.filter(EventOrganizer.admin_unit_id == admin_unit_id)
.filter(EventOrganizer.name == "Neuer Organisator")
.first()
)
assert organizer is not None
def test_places(client, seeder, utils):
user_id, admin_unit_id = seeder.setup_base()
seeder.upsert_default_event_place(admin_unit_id)

View File

@ -4,3 +4,49 @@ def test_read(client, seeder, utils):
url = utils.get_url("api_v1_organizer", id=organizer_id)
utils.get_ok(url)
def test_put(client, seeder, utils, app):
user_id, admin_unit_id = seeder.setup_api_access()
organizer_id = seeder.upsert_default_event_organizer(admin_unit_id)
url = utils.get_url("api_v1_organizer", id=organizer_id)
response = utils.put_json(url, {"name": "Neuer Name"})
utils.assert_response_no_content(response)
with app.app_context():
from project.models import EventOrganizer
organizer = EventOrganizer.query.get(organizer_id)
assert organizer.name == "Neuer Name"
def test_patch(client, seeder, utils, app):
user_id, admin_unit_id = seeder.setup_api_access()
organizer_id = seeder.upsert_default_event_organizer(admin_unit_id)
url = utils.get_url("api_v1_organizer", id=organizer_id)
response = utils.patch_json(url, {"phone": "55555"})
utils.assert_response_no_content(response)
with app.app_context():
from project.models import EventOrganizer
organizer = EventOrganizer.query.get(organizer_id)
assert organizer.name == "Meine Crew"
assert organizer.phone == "55555"
def test_delete(client, seeder, utils, app):
user_id, admin_unit_id = seeder.setup_api_access()
organizer_id = seeder.upsert_default_event_organizer(admin_unit_id)
url = utils.get_url("api_v1_organizer", id=organizer_id)
response = utils.delete(url)
utils.assert_response_no_content(response)
with app.app_context():
from project.models import EventOrganizer
organizer = EventOrganizer.query.get(organizer_id)
assert organizer is None