require_permission, start connecting permissions to routes

This commit is contained in:
SvyatoslavArtymovych 2023-05-29 16:14:00 +03:00
parent 1554015e31
commit face847c69
8 changed files with 143 additions and 24 deletions

View File

@ -40,9 +40,9 @@ def book_validator() -> Response | None:
book_id = request_args.get("book_id")
if book_id:
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.is_deleted or book.owner != current_user:
if not book or book.is_deleted:
log(log.INFO, "User: [%s] is not owner of book: [%s]", current_user, book)
flash("You are not owner of this book!", "danger")
flash("Book not found!", "danger")
return redirect(url_for("book.my_library"))
collection_id = request_args.get("collection_id")

View File

@ -0,0 +1,82 @@
from flask_login import current_user
from flask import flash, redirect, url_for, request, make_response
import functools
from app import models as m, db
from app.logger import log
def check_permissions(
entity_type: m.Permission.Entity,
access: list[m.Permission.Access],
model: m,
entity_id_field: str,
):
request_args = (
{**request.view_args, **request.args} if request.view_args else {**request.args}
)
entity_id = request_args.get(entity_id_field)
if entity_id is None:
raise ValueError("entity_id not found")
entity: m.Book | m.Collection | m.Section | m.Interpretation = db.session.get(
model, entity_id
)
if not entity or not entity.access_groups:
flash("You do not have permission", "warning")
return make_response(redirect(url_for("home.get_all")))
# check if user is not owner of book
if entity.access_groups[0].book.user_id == current_user.id:
return None
access_group_query = (
m.AccessGroup.query.join(
m.PermissionAccessGroups,
m.PermissionAccessGroups.access_group_id == m.AccessGroup.id,
)
.join(m.Permission, m.PermissionAccessGroups.permission_id == m.Permission.id)
.filter(
m.AccessGroup.id.in_(
[access_group.id for access_group in entity.access_groups]
)
)
.filter(m.AccessGroup.users.any(id=current_user.id))
.filter(m.Permission.entity_type == entity_type)
)
for access in access:
access_group_query = access_group_query.filter(
m.Permission.access.op("&")(access) > 0
)
access_groups = access_group_query.all()
if access_groups:
return
flash("You do not have permission", "danger")
return make_response(redirect(url_for("home.get_all")))
def require_permission(
entity_type: m.Permission.Entity,
access: list[m.Permission.Access],
model: m,
entity_id_field: str,
):
def decorator(f):
@functools.wraps(f)
def permission_checker(*args, **kwargs):
if response := check_permissions(
entity_type=entity_type,
access=access,
model=model,
entity_id_field=entity_id_field,
):
return response
return f(*args, **kwargs)
return permission_checker
return decorator

View File

@ -21,6 +21,7 @@ from app.controllers.create_access_groups import (
create_editor_group,
create_moderator_group,
)
from app.controllers.require_permission import require_permission
from app import models as m, db, forms as f
from app.logger import log
from .bp import bp
@ -112,6 +113,12 @@ def create():
@bp.route("/<int:book_id>/edit", methods=["POST"])
@register_book_verify_route(bp.name)
@require_permission(
entity_type=m.Permission.Entity.BOOK,
access=[m.Permission.Access.U],
model=m.Book,
entity_id_field="book_id",
)
@login_required
def edit(book_id: int):
form = f.EditBookForm()
@ -144,7 +151,7 @@ def delete(book_id: int):
if not book or book.is_deleted:
log(log.INFO, "User: [%s] is not owner of book: [%s]", current_user, book)
flash("You are not owner of this book!", "danger")
flash("Book not found!", "danger")
return redirect(url_for("book.my_library"))
book.is_deleted = True

View File

@ -35,7 +35,7 @@ def create_comment(
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.is_deleted:
log(log.INFO, "User: [%s] is not owner of book: [%s]", current_user, book)
flash("You are not owner of this book!", "danger")
flash("Book not found!", "danger")
return redirect(url_for("book.my_library"))
collection: m.Collection = db.session.get(m.Collection, collection_id)

View File

@ -276,7 +276,7 @@ def qa_view(
book: m.Book = db.session.get(m.Book, book_id)
if not book or book.is_deleted:
log(log.INFO, "User: [%s] is not owner of book: [%s]", current_user, book)
flash("You are not owner of this book!", "danger")
flash("Book not found!", "danger")
return redirect(url_for("book.my_library"))
collection: m.Collection = db.session.get(m.Collection, collection_id)

View File

@ -10,12 +10,19 @@ from app.controllers import (
register_book_verify_route,
)
from app import models as m, db, forms as f
from app.controllers.require_permission import require_permission
from app.logger import log
from .bp import bp
@bp.route("/<int:book_id>/settings", methods=["GET"])
@register_book_verify_route(bp.name)
@require_permission(
entity_type=m.Permission.Entity.BOOK,
access=[m.Permission.Access.U],
model=m.Book,
entity_id_field="book_id",
)
@login_required
def settings(book_id: int):
book: m.Book = db.session.get(m.Book, book_id)
@ -27,6 +34,12 @@ def settings(book_id: int):
@bp.route("/<int:book_id>/add_contributor", methods=["POST"])
@register_book_verify_route(bp.name)
@require_permission(
entity_type=m.Permission.Entity.BOOK,
access=[m.Permission.Access.U],
model=m.Book,
entity_id_field="book_id",
)
@login_required
def add_contributor(book_id: int):
form = f.AddContributorForm()
@ -71,6 +84,12 @@ def add_contributor(book_id: int):
@bp.route("/<int:book_id>/delete_contributor", methods=["POST"])
@register_book_verify_route(bp.name)
@require_permission(
entity_type=m.Permission.Entity.BOOK,
access=[m.Permission.Access.U],
model=m.Book,
entity_id_field="book_id",
)
@login_required
def delete_contributor(book_id: int):
form = f.DeleteContributorForm()
@ -124,6 +143,12 @@ def delete_contributor(book_id: int):
@bp.route("/<int:book_id>/edit_contributor_role", methods=["POST"])
@register_book_verify_route(bp.name)
@require_permission(
entity_type=m.Permission.Entity.BOOK,
access=[m.Permission.Access.U],
model=m.Book,
entity_id_field="book_id",
)
@login_required
def edit_contributor_role(book_id: int):
form = f.EditContributorRoleForm()

View File

@ -87,7 +87,7 @@ def test_create_edit_delete_book(client: FlaskClient):
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
assert b"Book not found!" in response.data
response: Response = client.post(
f"/book/{book.id}/edit",
@ -132,7 +132,7 @@ def test_add_delete_contributor(client: FlaskClient):
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
assert b"You do not have permission" in response.data
book: m.Book = create_test_book(user.id)
m.BookVersion(semver="1.0.0", book_id=book.id).save()
@ -164,7 +164,7 @@ def test_add_delete_contributor(client: FlaskClient):
user=moderator, book=book
).first()
assert contributor.role == m.BookContributor.Roles.MODERATOR
assert len(book.contributors) == 1
assert len(book.contributors) == 2
editor = m.User(username="Editor", password="test").save()
response: Response = client.post(
@ -180,7 +180,7 @@ def test_add_delete_contributor(client: FlaskClient):
user=editor, book=book
).first()
assert contributor.role == m.BookContributor.Roles.EDITOR
assert len(book.contributors) == 2
assert len(book.contributors) == 3
contributor_to_delete = m.BookContributor.query.filter_by(
user_id=moderator.id, book_id=book.id
@ -214,17 +214,19 @@ def test_add_delete_contributor(client: FlaskClient):
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
assert b"Book not found!" in response.data
def test_edit_contributor_role(client: FlaskClient, runner: FlaskCliRunner):
_, user = login(client)
user: m.User
# add dummmy data
runner.invoke(args=["db-populate"])
book = create_test_book(user.id)
# for contributor in m.BookContributor.query.all():
# db.session.delete(contributor)
# db.session.commit()
book = db.session.get(m.Book, 1)
book.user_id = user.id
book.save()
@ -249,7 +251,7 @@ def test_edit_contributor_role(client: FlaskClient, runner: FlaskCliRunner):
moderator = m.User(username="Moderator", password="test").save()
moderators_book: m.Book = m.Book(label="Test Book", user_id=moderator.id).save()
moderators_book: m.Book = create_test_book(moderator.id)
response: Response = client.post(
f"/book/{moderators_book.id}/add_contributor",
data=dict(user_id=moderator.id, role=m.BookContributor.Roles.MODERATOR),
@ -257,7 +259,7 @@ def test_edit_contributor_role(client: FlaskClient, runner: FlaskCliRunner):
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
assert b"You do not have permission" in response.data
response: Response = client.post(
f"/book/999/add_contributor",
@ -266,7 +268,7 @@ def test_edit_contributor_role(client: FlaskClient, runner: FlaskCliRunner):
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
assert b"Book not found!" in response.data
def test_crud_collection(client: FlaskClient):
@ -298,7 +300,7 @@ def test_crud_collection(client: FlaskClient):
follow_redirects=True,
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
assert b"Book not found!" in response.data
collection: m.Collection = m.Collection.query.filter_by(
label="Test Collection #1 Label"
@ -349,7 +351,7 @@ def test_crud_collection(client: FlaskClient):
follow_redirects=True,
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
assert b"Book not found!" in response.data
edited_collection: m.Collection = m.Collection.query.filter_by(
label=new_label, about=new_about
@ -394,7 +396,7 @@ def test_crud_collection(client: FlaskClient):
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
assert b"Book not found!" in response.data
def test_crud_subcollection(client: FlaskClient):
@ -423,7 +425,7 @@ def test_crud_subcollection(client: FlaskClient):
follow_redirects=True,
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
assert b"Book not found!" in response.data
response: Response = client.post(
f"/book/{book.id}/{leaf_collection.id}/create_sub_collection",
@ -1094,7 +1096,6 @@ def test_access_to_settings_page(client: FlaskClient):
)
assert response.status_code == 200
assert b"You are not owner of this book!" not in response.data
logout(client)
@ -1104,7 +1105,7 @@ def test_access_to_settings_page(client: FlaskClient):
)
assert response.status_code == 200
assert b"You are not owner of this book!" in response.data
assert b"You do not have permission" in response.data
def test_interpretation_in_home_last_inter_section(

View File

@ -30,7 +30,9 @@ def logout(client):
return client.get("/logout", follow_redirects=True)
def create_test_book(owner_id: int, entity_id: int = randint(1, 100)):
def create_test_book(owner_id: int, entity_id: int = 0):
if not entity_id:
entity_id = randint(1, 100)
book: m.Book = m.Book(
label=f"Book {entity_id}", about=f"About {entity_id}", user_id=owner_id
).save()
@ -122,7 +124,9 @@ def create_test_book(owner_id: int, entity_id: int = randint(1, 100)):
interpretation_id=section.id, access_group_id=access_group.id
).save()
# -------------
# Contributors
u = m.User(username=f"Bob {entity_id}").save()
m.BookContributor(book_id=book.id, user_id=u.id).save()
return book