mirror of https://github.com/logos-co/open-law.git
connected permissions checks and testing editor access
This commit is contained in:
parent
9032a3d4bf
commit
8754347ed2
|
@ -28,10 +28,7 @@ def create_app(environment="development"):
|
|||
permissions_blueprint,
|
||||
search_blueprint,
|
||||
)
|
||||
from app.models import (
|
||||
User,
|
||||
AnonymousUser,
|
||||
)
|
||||
from app.models import User, AnonymousUser, Permission
|
||||
|
||||
# Instantiate app.
|
||||
app = Flask(__name__)
|
||||
|
@ -75,12 +72,16 @@ def create_app(environment="development"):
|
|||
display_tags,
|
||||
build_qa_url_using_interpretation,
|
||||
recursive_render,
|
||||
has_permission,
|
||||
)
|
||||
|
||||
app.jinja_env.globals["Access"] = Permission.Access
|
||||
|
||||
app.jinja_env.globals["form_hidden_tag"] = form_hidden_tag
|
||||
app.jinja_env.globals["display_tags"] = display_tags
|
||||
app.jinja_env.globals["build_qa_url"] = build_qa_url_using_interpretation
|
||||
app.jinja_env.globals["recursive_render"] = recursive_render
|
||||
app.jinja_env.globals["has_permission"] = has_permission
|
||||
|
||||
# Error handlers.
|
||||
@app.errorhandler(HTTPException)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
from app import models as m
|
||||
|
||||
BOOK = {
|
||||
"model": m.Book,
|
||||
"entity_id_field": "book_id",
|
||||
}
|
||||
|
||||
COLLECTION = {
|
||||
"model": m.Collection,
|
||||
"entity_id_field": "collection_id",
|
||||
}
|
||||
|
||||
SECTION = {
|
||||
"model": m.Section,
|
||||
"entity_id_field": "section_id",
|
||||
}
|
||||
|
||||
INTERPRETATION = {
|
||||
"model": m.Interpretation,
|
||||
"entity_id_field": "interpretation_id",
|
||||
}
|
|
@ -3,6 +3,7 @@ import re
|
|||
from flask import current_app
|
||||
from flask_wtf import FlaskForm
|
||||
from flask import url_for, render_template
|
||||
from flask_login import current_user
|
||||
|
||||
from app import models as m
|
||||
|
||||
|
@ -51,9 +52,35 @@ def build_qa_url_using_interpretation(interpretation: m.Interpretation):
|
|||
return url
|
||||
|
||||
|
||||
# Using: {{ recursive_render("template.html", collection=collection, book=book) }}
|
||||
def recursive_render(template: str, collection: m.Collection, book: m.Book):
|
||||
return render_template(
|
||||
template,
|
||||
collection=collection,
|
||||
book=book,
|
||||
)
|
||||
|
||||
|
||||
# Using: {{ has_permission(entity=book, required_permissions=[Access.create]) }}
|
||||
def has_permission(
|
||||
entity: m.Book | m.Collection | m.Section | m.Interpretation,
|
||||
required_permissions: m.Permission.Access | list[m.Permission.Access],
|
||||
) -> bool:
|
||||
if type(required_permissions) == m.Permission.Access:
|
||||
required_permissions = [required_permissions]
|
||||
|
||||
access_groups: list[m.AccessGroup] = list(
|
||||
set(entity.access_groups).intersection(current_user.access_groups)
|
||||
)
|
||||
|
||||
if not access_groups:
|
||||
return False
|
||||
|
||||
for access_group in access_groups:
|
||||
for permission in access_group.permissions:
|
||||
permission: m.Permission
|
||||
for required_permission in required_permissions:
|
||||
if permission.access & required_permission:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
|
|
@ -3,41 +3,45 @@ 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],
|
||||
entities_data: list[dict] | dict,
|
||||
entities: list[dict],
|
||||
):
|
||||
if not current_user.is_authenticated:
|
||||
flash("You do not have permission", "danger")
|
||||
return make_response(redirect(url_for("home.get_all")))
|
||||
|
||||
request_args = (
|
||||
{**request.view_args, **request.args} if request.view_args else {**request.args}
|
||||
)
|
||||
if type(entities_data) == dict:
|
||||
entities_data = [entities_data]
|
||||
entity = None
|
||||
for entity_data in entities_data:
|
||||
model = entity_data.get("model")
|
||||
entity_id_field = entity_data.get("entity_id_field")
|
||||
if not model or entity_id_field is None:
|
||||
raise ValueError(
|
||||
"One of required arguments(model, entity_id_field) is missions"
|
||||
)
|
||||
|
||||
for model in entities:
|
||||
entity_id_field = (model.__name__ + "_id").lower()
|
||||
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 entity is None:
|
||||
flash("You do not have permission", "danger")
|
||||
return make_response(redirect(url_for("home.get_all")))
|
||||
|
||||
book_id = request_args.get("book_id")
|
||||
book: m.Book = db.session.get(m.Book, book_id)
|
||||
if book and book.user_id == current_user.id:
|
||||
# user has access because he is book owner
|
||||
return None
|
||||
|
||||
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:
|
||||
if not book and entity.access_groups[0].book.user_id == current_user.id:
|
||||
# user has access because he is book owner
|
||||
return None
|
||||
|
||||
access_group_query = (
|
||||
|
@ -72,7 +76,7 @@ def check_permissions(
|
|||
def require_permission(
|
||||
entity_type: m.Permission.Entity,
|
||||
access: list[m.Permission.Access],
|
||||
entities_data: list[dict] | dict,
|
||||
entities: list[dict],
|
||||
):
|
||||
def decorator(f):
|
||||
@functools.wraps(f)
|
||||
|
@ -80,7 +84,7 @@ def require_permission(
|
|||
if response := check_permissions(
|
||||
entity_type=entity_type,
|
||||
access=access,
|
||||
entities_data=entities_data,
|
||||
entities=entities,
|
||||
):
|
||||
return response
|
||||
return f(*args, **kwargs)
|
||||
|
|
File diff suppressed because one or more lines are too long
144831
app/static/js/main.js
144831
app/static/js/main.js
File diff suppressed because one or more lines are too long
|
@ -33,13 +33,20 @@
|
|||
{% if current_user.is_authenticated %}
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
{% if sub_collection.is_leaf and not sub_collection.children %}
|
||||
<li>
|
||||
<button type="button" id="callAddSectionModal" data-modal-target="add-section-modal" data-modal-toggle="add-section-modal" data-collection-id="{{collection.id}}" data-sub-collection-id="{{sub_collection.id}}" class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"> New Section </button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" id="callAddSectionModal" data-modal-target="add-section-modal" data-modal-toggle="add-section-modal" data-collection-id="{{collection.id}}" data-sub-collection-id="{{sub_collection.id}}" class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"> New Section </button>
|
||||
</li>
|
||||
{% elif not sub_collection.is_leaf and not sub_collection.children %}
|
||||
<li>
|
||||
<button type="button" id="callAddSectionModal" data-modal-target="add-section-modal" data-modal-toggle="add-section-modal" data-collection-id="{{collection.id}}" data-sub-collection-id="{{sub_collection.id}}" class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"> New Section </button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" id="callAddSubCollectionModal" data-modal-target="add-sub-collection-modal" data-modal-toggle="add-sub-collection-modal" data-collection-id="{{sub_collection.id}}" class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"> New Subcollection </button>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<button type="button" id="callAddSubCollectionModal" data-modal-target="add-sub-collection-modal" data-modal-toggle="add-sub-collection-modal" data-collection-id="{{sub_collection.id}}" class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"> New Subcollection </button>
|
||||
</li>
|
||||
<li>
|
||||
<button type="button" id="callAddSubCollectionModal" data-modal-target="add-sub-collection-modal" data-modal-toggle="add-sub-collection-modal" data-collection-id="{{sub_collection.id}}" class="w-full block px-4 py-2 hover:bg-gray-100 dark:hover:bg-gray-600 dark:hover:text-white"> New Subcollection </button>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<ul class="py-2 text-sm text-gray-700 dark:text-gray-200">
|
||||
|
|
|
@ -114,7 +114,7 @@
|
|||
{% endif %}
|
||||
|
||||
<!-- TODO check permissions -->
|
||||
{% if interpretation.book.owner == current_user %}
|
||||
{% if interpretation.book.owner == current_user or has_permission(interpretation, Access.D) %}
|
||||
<div class="approve-button select-none approve-btn mt-3 cursor-pointer" data-approve="comment" data-entity-id="{{ comment.id }}">
|
||||
<!-- outline -->
|
||||
<svg class="not-approved-icon w-6 h-6 {% if comment.approved %} hidden {% endif %}" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
|
||||
|
|
|
@ -138,13 +138,15 @@
|
|||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<!-- prettier-ignore -->
|
||||
<form class="mb-0 flex justify-end" action="{{ url_for('book.delete_contributor', book_id=book.id) }}" method="post">
|
||||
{{ form_hidden_tag() }}
|
||||
<input type="hidden" name="user_id" id="user_id" value="{{ contributor.user_id }}" />
|
||||
<button type="submit" class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-sm rounded-lg text-sm px-5 py-1.5 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800">
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
{% if current_user.id != contributor.user_id %}
|
||||
<form class="mb-0 flex justify-end" action="{{ url_for('book.delete_contributor', book_id=book.id) }}" method="post">
|
||||
{{ form_hidden_tag() }}
|
||||
<input type="hidden" name="user_id" id="user_id" value="{{ contributor.user_id }}" />
|
||||
<button type="submit" class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-sm rounded-lg text-sm px-5 py-1.5 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800">
|
||||
Delete
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -5,6 +5,7 @@ from flask import (
|
|||
from flask_login import login_required, current_user
|
||||
|
||||
from app import models as m, db
|
||||
from app.controllers.require_permission import require_permission
|
||||
from app.logger import log
|
||||
|
||||
bp = Blueprint("approve", __name__, url_prefix="/approve")
|
||||
|
@ -14,6 +15,11 @@ bp = Blueprint("approve", __name__, url_prefix="/approve")
|
|||
"/interpretation/<int:interpretation_id>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@require_permission(
|
||||
entity_type=m.Permission.Entity.INTERPRETATION,
|
||||
access=[m.Permission.Access.A],
|
||||
entities=[m.Interpretation],
|
||||
)
|
||||
@login_required
|
||||
def approve_interpretation(interpretation_id: int):
|
||||
interpretation: m.Interpretation = db.session.get(
|
||||
|
@ -23,16 +29,6 @@ def approve_interpretation(interpretation_id: int):
|
|||
log(log.WARNING, "Interpretation with id [%s] not found", interpretation_id)
|
||||
return jsonify({"message": "Interpretation not found"}), 404
|
||||
|
||||
# TODO check permission
|
||||
if interpretation.book.owner != current_user:
|
||||
log(
|
||||
log.WARNING,
|
||||
"User [%s] dont have permission to approve [%s]",
|
||||
current_user,
|
||||
interpretation,
|
||||
)
|
||||
return jsonify({"message": "You dont have permission"}), 404
|
||||
|
||||
already_approved_interpretations = (
|
||||
m.Interpretation.query.filter_by(
|
||||
approved=True, section_id=interpretation.section_id
|
||||
|
@ -68,6 +64,11 @@ def approve_interpretation(interpretation_id: int):
|
|||
"/comment/<int:interpretation_id>",
|
||||
methods=["POST"],
|
||||
)
|
||||
@require_permission(
|
||||
entity_type=m.Permission.Entity.COMMENT,
|
||||
access=[m.Permission.Access.A],
|
||||
entities=[m.Interpretation],
|
||||
)
|
||||
@login_required
|
||||
def approve_comment(interpretation_id: int):
|
||||
comment: m.Comment = db.session.get(m.Comment, interpretation_id)
|
||||
|
@ -75,16 +76,6 @@ def approve_comment(interpretation_id: int):
|
|||
log(log.WARNING, "Comment with id [%s] not found", interpretation_id)
|
||||
return jsonify({"message": "Comment not found"}), 404
|
||||
|
||||
# TODO check permission
|
||||
if comment.interpretation.book.owner != current_user:
|
||||
log(
|
||||
log.WARNING,
|
||||
"User [%s] dont have permission to approve [%s]",
|
||||
current_user,
|
||||
comment,
|
||||
)
|
||||
return jsonify({"message": "You dont have permission"}), 404
|
||||
|
||||
comment.approved = not comment.approved
|
||||
log(
|
||||
log.INFO,
|
||||
|
|
|
@ -116,10 +116,7 @@ def create():
|
|||
@require_permission(
|
||||
entity_type=m.Permission.Entity.BOOK,
|
||||
access=[m.Permission.Access.U],
|
||||
entity_data={
|
||||
"model": m.Book,
|
||||
"entity_id_field": "book_id",
|
||||
},
|
||||
entities=[m.Book],
|
||||
)
|
||||
@login_required
|
||||
def edit(book_id: int):
|
||||
|
@ -150,12 +147,7 @@ def edit(book_id: int):
|
|||
@require_permission(
|
||||
entity_type=m.Permission.Entity.BOOK,
|
||||
access=[m.Permission.Access.D],
|
||||
entities_data=[
|
||||
{
|
||||
"model": m.Book,
|
||||
"entity_id_field": "book_id",
|
||||
}
|
||||
],
|
||||
entities=[m.Book],
|
||||
)
|
||||
@login_required
|
||||
def delete(book_id: int):
|
||||
|
|
|
@ -39,8 +39,7 @@ def collection_view(book_id: int):
|
|||
@require_permission(
|
||||
entity_type=m.Permission.Entity.COLLECTION,
|
||||
access=[m.Permission.Access.C],
|
||||
model=m.Collection,
|
||||
entity_id_field="collection_id",
|
||||
entities=[m.Collection, m.Book],
|
||||
)
|
||||
@login_required
|
||||
def collection_create(book_id: int, collection_id: int | None = None):
|
||||
|
@ -119,6 +118,11 @@ def collection_create(book_id: int, collection_id: int | None = None):
|
|||
|
||||
@bp.route("/<int:book_id>/<int:collection_id>/edit", methods=["POST"])
|
||||
@register_book_verify_route(bp.name)
|
||||
@require_permission(
|
||||
entity_type=m.Permission.Entity.COLLECTION,
|
||||
access=[m.Permission.Access.U],
|
||||
entities=[m.Collection],
|
||||
)
|
||||
@login_required
|
||||
def collection_edit(book_id: int, collection_id: int):
|
||||
book: m.Book = db.session.get(m.Book, book_id)
|
||||
|
@ -174,6 +178,11 @@ def collection_edit(book_id: int, collection_id: int):
|
|||
|
||||
@bp.route("/<int:book_id>/<int:collection_id>/delete", methods=["POST"])
|
||||
@register_book_verify_route(bp.name)
|
||||
@require_permission(
|
||||
entity_type=m.Permission.Entity.COLLECTION,
|
||||
access=[m.Permission.Access.D],
|
||||
entities=[m.Collection],
|
||||
)
|
||||
@login_required
|
||||
def collection_delete(book_id: int, collection_id: int):
|
||||
collection: m.Collection = db.session.get(m.Collection, collection_id)
|
||||
|
|
|
@ -12,6 +12,7 @@ from app.controllers.delete_nested_book_entities import (
|
|||
delete_nested_interpretation_entities,
|
||||
)
|
||||
from app import models as m, db, forms as f
|
||||
from app.controllers.require_permission import require_permission
|
||||
from app.controllers.tags import set_interpretation_tags
|
||||
from app.logger import log
|
||||
from .bp import bp
|
||||
|
@ -111,6 +112,11 @@ def interpretation_create(
|
|||
"/<int:book_id>/<int:interpretation_id>/edit_interpretation", methods=["POST"]
|
||||
)
|
||||
@register_book_verify_route(bp.name)
|
||||
@require_permission(
|
||||
entity_type=m.Permission.Entity.INTERPRETATION,
|
||||
access=[m.Permission.Access.U],
|
||||
entities=[m.Interpretation],
|
||||
)
|
||||
@login_required
|
||||
def interpretation_edit(
|
||||
book_id: int,
|
||||
|
@ -155,6 +161,11 @@ def interpretation_edit(
|
|||
"/<int:book_id>/<int:interpretation_id>/delete_interpretation", methods=["POST"]
|
||||
)
|
||||
@register_book_verify_route(bp.name)
|
||||
@require_permission(
|
||||
entity_type=m.Permission.Entity.INTERPRETATION,
|
||||
access=[m.Permission.Access.D],
|
||||
entities=[m.Interpretation],
|
||||
)
|
||||
@login_required
|
||||
def interpretation_delete(book_id: int, interpretation_id: int):
|
||||
interpretation: m.Interpretation = db.session.get(
|
||||
|
|
|
@ -8,12 +8,18 @@ from flask_login import login_required
|
|||
from app.controllers import register_book_verify_route
|
||||
from app.controllers.delete_nested_book_entities import delete_nested_section_entities
|
||||
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>/<int:collection_id>/create_section", methods=["POST"])
|
||||
@register_book_verify_route(bp.name)
|
||||
@require_permission(
|
||||
entity_type=m.Permission.Entity.SECTION,
|
||||
access=[m.Permission.Access.C],
|
||||
entities=[m.Collection],
|
||||
)
|
||||
@login_required
|
||||
def section_create(book_id: int, collection_id: int):
|
||||
book: m.Book = db.session.get(m.Book, book_id)
|
||||
|
@ -58,6 +64,11 @@ def section_create(book_id: int, collection_id: int):
|
|||
|
||||
@bp.route("/<int:book_id>/<int:section_id>/edit_section", methods=["POST"])
|
||||
@register_book_verify_route(bp.name)
|
||||
@require_permission(
|
||||
entity_type=m.Permission.Entity.SECTION,
|
||||
access=[m.Permission.Access.U],
|
||||
entities=[m.Section],
|
||||
)
|
||||
@login_required
|
||||
def section_edit(book_id: int, section_id: int):
|
||||
section: m.Section = db.session.get(m.Section, section_id)
|
||||
|
@ -86,6 +97,11 @@ def section_edit(book_id: int, section_id: int):
|
|||
|
||||
@bp.route("/<int:book_id>/<int:section_id>/delete_section", methods=["POST"])
|
||||
@register_book_verify_route(bp.name)
|
||||
@require_permission(
|
||||
entity_type=m.Permission.Entity.SECTION,
|
||||
access=[m.Permission.Access.D],
|
||||
entities=[m.Section],
|
||||
)
|
||||
@login_required
|
||||
def section_delete(
|
||||
book_id: int,
|
||||
|
|
|
@ -20,8 +20,7 @@ from .bp import bp
|
|||
@require_permission(
|
||||
entity_type=m.Permission.Entity.BOOK,
|
||||
access=[m.Permission.Access.U],
|
||||
model=m.Book,
|
||||
entity_id_field="book_id",
|
||||
entities=[m.Book],
|
||||
)
|
||||
@login_required
|
||||
def settings(book_id: int):
|
||||
|
@ -37,8 +36,7 @@ def settings(book_id: int):
|
|||
@require_permission(
|
||||
entity_type=m.Permission.Entity.BOOK,
|
||||
access=[m.Permission.Access.U],
|
||||
model=m.Book,
|
||||
entity_id_field="book_id",
|
||||
entities=[m.Book],
|
||||
)
|
||||
@login_required
|
||||
def add_contributor(book_id: int):
|
||||
|
@ -87,8 +85,7 @@ def add_contributor(book_id: int):
|
|||
@require_permission(
|
||||
entity_type=m.Permission.Entity.BOOK,
|
||||
access=[m.Permission.Access.U],
|
||||
model=m.Book,
|
||||
entity_id_field="book_id",
|
||||
entities=[m.Book],
|
||||
)
|
||||
@login_required
|
||||
def delete_contributor(book_id: int):
|
||||
|
@ -146,8 +143,7 @@ def delete_contributor(book_id: int):
|
|||
@require_permission(
|
||||
entity_type=m.Permission.Entity.BOOK,
|
||||
access=[m.Permission.Access.U],
|
||||
model=m.Book,
|
||||
entity_id_field="book_id",
|
||||
entities=[m.Book],
|
||||
)
|
||||
@login_required
|
||||
def edit_contributor_role(book_id: int):
|
||||
|
|
|
@ -21,8 +21,7 @@ def test_approve_interpretation(client: FlaskClient):
|
|||
)
|
||||
|
||||
assert response
|
||||
assert response.status_code == 404
|
||||
assert response.json["message"] == "Interpretation not found"
|
||||
assert b"You do not have permission" in response.data
|
||||
|
||||
interpretation: m.Interpretation = m.Interpretation.query.filter_by(
|
||||
user_id=dummy_user.id
|
||||
|
@ -33,7 +32,7 @@ def test_approve_interpretation(client: FlaskClient):
|
|||
)
|
||||
|
||||
assert response
|
||||
assert response.json["message"] == "You dont have permission"
|
||||
assert b"You do not have permission" in response.data
|
||||
|
||||
interpretation: m.Interpretation = m.Interpretation.query.filter_by(
|
||||
user_id=user.id
|
||||
|
@ -78,8 +77,7 @@ def test_approve_comment(client: FlaskClient):
|
|||
)
|
||||
|
||||
assert response
|
||||
assert response.status_code == 404
|
||||
assert response.json["message"] == "Comment not found"
|
||||
assert b"You do not have permission" in response.data
|
||||
|
||||
comment: m.Comment = m.Comment.query.filter_by(user_id=dummy_user.id).first()
|
||||
response: Response = client.post(
|
||||
|
@ -88,7 +86,7 @@ def test_approve_comment(client: FlaskClient):
|
|||
)
|
||||
|
||||
assert response
|
||||
assert response.json["message"] == "You dont have permission"
|
||||
assert b"You do not have permission" in response.data
|
||||
|
||||
comment: m.Comment = m.Comment.query.filter_by(user_id=user.id).first()
|
||||
response: Response = client.post(
|
||||
|
|
|
@ -3,6 +3,7 @@ from flask import current_app as Response, url_for
|
|||
from flask.testing import FlaskClient, FlaskCliRunner
|
||||
|
||||
from app import models as m, db
|
||||
from app.controllers.create_access_groups import create_moderator_group
|
||||
from tests.utils import (
|
||||
login,
|
||||
logout,
|
||||
|
@ -562,7 +563,7 @@ def test_crud_sections(client: FlaskClient, runner: FlaskCliRunner):
|
|||
).first()
|
||||
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/{sub_collection.id}/create_section",
|
||||
f"/book/{book.id}/{collection.id}/create_section",
|
||||
data=dict(
|
||||
collection_id=collection.id,
|
||||
label="Test Section",
|
||||
|
@ -875,15 +876,23 @@ def test_crud_interpretation(client: FlaskClient):
|
|||
|
||||
# edit
|
||||
|
||||
m.Interpretation(
|
||||
i_1 = m.Interpretation(
|
||||
text="Test", section_id=section_in_collection.id, user_id=user.id
|
||||
).save()
|
||||
|
||||
m.Interpretation(
|
||||
i_2 = m.Interpretation(
|
||||
text="Test",
|
||||
section_id=section_in_subcollection.id,
|
||||
).save()
|
||||
|
||||
group = create_moderator_group(book.id)
|
||||
m.InterpretationAccessGroups(
|
||||
interpretation_id=i_1.id, access_group_id=group.id
|
||||
).save()
|
||||
m.InterpretationAccessGroups(
|
||||
interpretation_id=i_2.id, access_group_id=group.id
|
||||
).save()
|
||||
|
||||
interpretation: m.Interpretation = m.Interpretation.query.filter_by(
|
||||
section_id=section_in_collection.id
|
||||
).first()
|
||||
|
@ -996,6 +1005,10 @@ def test_crud_comment(client: FlaskClient, runner: FlaskCliRunner):
|
|||
collection_id=sub_collection.id,
|
||||
version_id=book.last_version.id,
|
||||
).save()
|
||||
group = create_moderator_group(book.id)
|
||||
m.SectionAccessGroups(
|
||||
section_id=section_in_subcollection.id, access_group_id=group.id
|
||||
).save()
|
||||
|
||||
label_1 = "Test Interpretation #1 Label"
|
||||
text_1 = "Test Interpretation #1 Text"
|
||||
|
@ -1142,6 +1155,13 @@ def test_interpretation_in_home_last_inter_section(
|
|||
collection_id=sub_collection.id,
|
||||
version_id=book.last_version.id,
|
||||
).save()
|
||||
group = create_moderator_group(book.id)
|
||||
m.SectionAccessGroups(
|
||||
section_id=section_in_subcollection.id, access_group_id=group.id
|
||||
).save()
|
||||
m.SectionAccessGroups(
|
||||
section_id=section_in_collection.id, access_group_id=group.id
|
||||
).save()
|
||||
|
||||
label_1 = "Test Interpretation #1 Label"
|
||||
text_1 = "Test Interpretation #1 Text"
|
||||
|
|
|
@ -0,0 +1,288 @@
|
|||
from random import randint
|
||||
|
||||
from flask import current_app as Response, url_for
|
||||
from flask.testing import FlaskClient, FlaskCliRunner
|
||||
|
||||
from app import models as m, db
|
||||
from app.controllers.create_access_groups import create_moderator_group
|
||||
from tests.utils import (
|
||||
login,
|
||||
logout,
|
||||
check_if_nested_book_entities_is_deleted,
|
||||
check_if_nested_collection_entities_is_deleted,
|
||||
check_if_nested_section_entities_is_deleted,
|
||||
check_if_nested_interpretation_entities_is_deleted,
|
||||
create_test_book,
|
||||
)
|
||||
|
||||
|
||||
def create_book(client):
|
||||
random_id = randint(1, 100)
|
||||
BOOK_NAME = f"TBook {random_id}"
|
||||
response: Response = client.post(
|
||||
"/book/create",
|
||||
data=dict(label=BOOK_NAME),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b"Book added!" in response.data
|
||||
|
||||
book: m.Book = m.Book.query.filter_by(label=BOOK_NAME).first()
|
||||
|
||||
assert book
|
||||
assert book.versions
|
||||
assert len(book.versions) == 1
|
||||
assert book.access_groups
|
||||
assert len(book.access_groups) == 2
|
||||
|
||||
root_collection: m.Collection = book.last_version.collections[0]
|
||||
assert root_collection
|
||||
assert root_collection.access_groups
|
||||
assert len(root_collection.access_groups) == 2
|
||||
|
||||
return book
|
||||
|
||||
|
||||
def create_collection(client, book_id):
|
||||
random_id = randint(1, 100)
|
||||
LABEL = f"TCollection {random_id}"
|
||||
response: Response = client.post(
|
||||
f"/book/{book_id}/create_collection",
|
||||
data=dict(label=LABEL),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b"Success!" in response.data
|
||||
|
||||
collection: m.Collection = m.Collection.query.filter_by(label=LABEL).first()
|
||||
|
||||
assert collection
|
||||
assert collection.access_groups
|
||||
assert len(collection.access_groups) == 2
|
||||
for access_group in collection.access_groups:
|
||||
access_group: m.AccessGroup
|
||||
assert access_group.book_id == collection.version.book_id
|
||||
|
||||
return collection, response
|
||||
|
||||
|
||||
def create_section(client, book_id, collection_id):
|
||||
random_id = randint(1, 100)
|
||||
LABEL = f"TSection {random_id}"
|
||||
response: Response = client.post(
|
||||
f"/book/{book_id}/{collection_id}/create_section",
|
||||
data=dict(collection_id=collection_id, label=LABEL),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
assert b"Success!" in response.data
|
||||
section: m.Section = m.Section.query.filter_by(
|
||||
label=LABEL, collection_id=collection_id
|
||||
).first()
|
||||
assert section
|
||||
assert section.collection_id == collection_id
|
||||
assert not section.interpretations
|
||||
|
||||
assert section.access_groups
|
||||
assert len(section.access_groups) == 2
|
||||
for access_group in section.access_groups:
|
||||
access_group: m.AccessGroup
|
||||
assert access_group.book_id == section.version.book_id
|
||||
|
||||
return section, response
|
||||
|
||||
|
||||
def create_interpretation(client, book_id, section_id):
|
||||
random_id = randint(1, 100)
|
||||
LABEL = f"TInterpretation {random_id}"
|
||||
response: Response = client.post(
|
||||
f"/book/{book_id}/{section_id}/create_interpretation",
|
||||
data=dict(section_id=section_id, text=LABEL),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
interpretation: m.Interpretation = m.Interpretation.query.filter_by(
|
||||
section_id=section_id, text=LABEL
|
||||
).first()
|
||||
assert interpretation
|
||||
assert interpretation.section_id == section_id
|
||||
assert not interpretation.comments
|
||||
|
||||
assert interpretation.access_groups
|
||||
assert len(interpretation.access_groups) == 2
|
||||
for access_group in interpretation.access_groups:
|
||||
access_group: m.AccessGroup
|
||||
assert access_group.book_id == interpretation.section.version.book_id
|
||||
|
||||
return interpretation, response
|
||||
|
||||
|
||||
def create_comment(client, book_id, interpretation_id):
|
||||
random_id = randint(1, 100)
|
||||
TEXT = f"TComment {random_id}"
|
||||
response: Response = client.post(
|
||||
f"/book/{book_id}/{interpretation_id}/create_comment",
|
||||
data=dict(
|
||||
text=TEXT,
|
||||
interpretation_id=interpretation_id,
|
||||
),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response
|
||||
assert response.status_code == 200
|
||||
assert b"Success" in response.data
|
||||
assert str.encode(TEXT) in response.data
|
||||
comment: m.Comment = m.Comment.query.filter_by(text=TEXT).first()
|
||||
assert comment
|
||||
|
||||
return comment, response
|
||||
|
||||
|
||||
def test_editor_access_to_entire_book(client):
|
||||
login(client)
|
||||
book = create_book(client)
|
||||
|
||||
editor = m.User(username="editor", password="editor").save()
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/add_contributor",
|
||||
data=dict(user_id=editor.id, role=m.BookContributor.Roles.EDITOR),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert response.status_code == 200
|
||||
assert b"Contributor was added!" in response.data
|
||||
logout(client)
|
||||
|
||||
login(client, "editor", "editor")
|
||||
|
||||
# access to settings page
|
||||
response: Response = client.get(f"/book/{book.id}/settings", follow_redirects=True)
|
||||
assert b"You do not have permission" not in response.data
|
||||
|
||||
# access to edit book
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/edit",
|
||||
data=dict(book_id=book.id, label="BookEdited"),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# dont have access to delete
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/delete",
|
||||
data=dict(book_id=book.id),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert b"You do not have permission" in response.data
|
||||
|
||||
# access to create collection
|
||||
collection, response = create_collection(client, book.id)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# access to edit collection
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/{collection.id}/edit",
|
||||
data=dict(label="NewLabel"),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# access to delete collection
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/{collection.id}/delete", follow_redirects=True
|
||||
)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# restore collection
|
||||
collection.is_deleted = False
|
||||
collection.save()
|
||||
|
||||
# access to create section
|
||||
section, response = create_section(client, book.id, collection.id)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# access to edit section
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/{section.id}/edit_section",
|
||||
data=dict(section_id=section.id, label="NewLabel"),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# access to delete section
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/{section.id}/delete_section", follow_redirects=True
|
||||
)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# restore section
|
||||
section.is_deleted = False
|
||||
section.save()
|
||||
|
||||
# access to create interpretation
|
||||
interpretation, response = create_interpretation(client, book.id, section.id)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# access to approve interpretation
|
||||
response: Response = client.post(
|
||||
f"/approve/interpretation/{interpretation.id}",
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response
|
||||
assert response.json["message"] == "success"
|
||||
assert response.json["approve"]
|
||||
assert interpretation.approved
|
||||
|
||||
# access to delete interpretation
|
||||
response: Response = client.post(
|
||||
(f"/book/{book.id}/{interpretation.id}/delete_interpretation"),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# restore interpretation
|
||||
interpretation.is_deleted = False
|
||||
interpretation.save()
|
||||
|
||||
# access to create comment
|
||||
comment, response = create_comment(client, book.id, interpretation.id)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
||||
|
||||
# access to approve comment
|
||||
response: Response = client.post(
|
||||
f"/approve/comment/{comment.id}",
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert response
|
||||
assert response.json["message"] == "success"
|
||||
assert response.json["approve"]
|
||||
assert interpretation.approved
|
||||
|
||||
# access to delete comment
|
||||
response: Response = client.post(
|
||||
f"/book/{book.id}/{interpretation.id}/comment_delete",
|
||||
data=dict(
|
||||
text=comment.text,
|
||||
interpretation_id=interpretation.id,
|
||||
comment_id=comment.id,
|
||||
),
|
||||
follow_redirects=True,
|
||||
)
|
||||
assert b"You do not have permission" not in response.data
|
||||
assert b"Success!" in response.data
|
Loading…
Reference in New Issue