mirror of https://github.com/logos-co/open-law.git
require_permission, start connecting permissions to routes
This commit is contained in:
parent
1554015e31
commit
face847c69
|
@ -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")
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue