apply access decorator to routes

This commit is contained in:
dandds
2019-03-20 10:47:13 -04:00
parent 0ea21fbb9b
commit de7c69bde7
25 changed files with 198 additions and 59 deletions

View File

@@ -1,7 +1,6 @@
from atst.database import db
from atst.domain.environments import Environments
from atst.domain.exceptions import NotFoundError
from atst.models.permissions import Permissions
from atst.models.application import Application
from atst.models.environment import Environment
from atst.models.environment_role import EnvironmentRole

View File

@@ -4,6 +4,15 @@ from flask import g
from . import user_can_access
from atst.domain.portfolios import Portfolios
from atst.domain.task_orders import TaskOrders
def evaluate_exceptions(user, permission, exceptions, **kwargs):
return (
True
if True in [exc(g.current_user, permission, **kwargs) for exc in exceptions]
else False
)
def user_can_access_decorator(permission, message=None, exceptions=None):
@@ -16,13 +25,14 @@ def user_can_access_decorator(permission, message=None, exceptions=None):
access_args["portfolio"] = Portfolios.get(
g.current_user, kwargs["portfolio_id"]
)
elif "task_order_id" in kwargs:
task_order = TaskOrders.get(g.current_user, kwargs["task_order_id"])
access_args["portfolio"] = task_order.portfolio
if exceptions:
evaluated = [
exc(g.current_user, permission, **access_args) for exc in exceptions
]
if True in evaluated:
return True
if exceptions and evaluate_exceptions(
g.current_user, permission, exceptions, **access_args, **kwargs
):
return f(*args, **kwargs)
user_can_access(g.current_user, permission, **access_args)

View File

@@ -4,7 +4,6 @@ from sqlalchemy.orm.exc import NoResultFound
from atst.database import db
from atst.models.invitation import Invitation, Status as InvitationStatus
from atst.domain.portfolio_roles import PortfolioRoles
from atst.domain.portfolios import Portfolios
from .exceptions import NotFoundError
@@ -118,7 +117,6 @@ class Invitations(object):
@classmethod
def resend(cls, user, portfolio_id, token):
portfolio = Portfolios.get(user, portfolio_id)
previous_invitation = Invitations._get(token)
Invitations._update_status(previous_invitation, InvitationStatus.REVOKED)

View File

@@ -22,6 +22,8 @@ from atst.domain.audit_log import AuditLog
from atst.domain.auth import logout as _logout
from atst.domain.common import Paginator
from atst.domain.portfolios import Portfolios
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash
@@ -143,6 +145,7 @@ def logout():
@bp.route("/activity-history")
@user_can(Permissions.VIEW_AUDIT_LOG, message="view activity log")
def activity_history():
pagination_opts = Paginator.get_pagination_opts(request)
audit_events = AuditLog.get_all_events(g.current_user, pagination_opts)

View File

@@ -13,15 +13,19 @@ from atst.domain.exceptions import UnauthorizedError
from atst.domain.applications import Applications
from atst.domain.portfolios import Portfolios
from atst.forms.application import NewApplicationForm, ApplicationForm
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
@portfolios_bp.route("/portfolios/<portfolio_id>/applications")
@user_can(Permissions.VIEW_APPLICATION)
def portfolio_applications(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
return render_template("portfolios/applications/index.html", portfolio=portfolio)
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/new")
@user_can(Permissions.CREATE_APPLICATION)
def new_application(portfolio_id):
portfolio = Portfolios.get_for_update_applications(g.current_user, portfolio_id)
form = NewApplicationForm()
@@ -31,6 +35,7 @@ def new_application(portfolio_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/new", methods=["POST"])
@user_can(Permissions.CREATE_APPLICATION)
def create_application(portfolio_id):
portfolio = Portfolios.get_for_update_applications(g.current_user, portfolio_id)
form = NewApplicationForm(http_request.form)
@@ -54,6 +59,7 @@ def create_application(portfolio_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/<application_id>/edit")
@user_can(Permissions.EDIT_APPLICATION)
def edit_application(portfolio_id, application_id):
portfolio = Portfolios.get_for_update_applications(g.current_user, portfolio_id)
application = Applications.get(g.current_user, portfolio, application_id)
@@ -70,6 +76,7 @@ def edit_application(portfolio_id, application_id):
@portfolios_bp.route(
"/portfolios/<portfolio_id>/applications/<application_id>/edit", methods=["POST"]
)
@user_can(Permissions.EDIT_APPLICATION)
def update_application(portfolio_id, application_id):
portfolio = Portfolios.get_for_update_applications(g.current_user, portfolio_id)
application = Applications.get(g.current_user, portfolio, application_id)
@@ -91,6 +98,8 @@ def update_application(portfolio_id, application_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/environments/<environment_id>/access")
# TODO: we probably need a different permission for this
@user_can(Permissions.VIEW_ENVIRONMENT)
def access_environment(portfolio_id, environment_id):
env_role = EnvironmentRoles.get(g.current_user.id, environment_id)
if not env_role:

View File

@@ -8,8 +8,9 @@ from atst.domain.portfolios import Portfolios
from atst.domain.audit_log import AuditLog
from atst.domain.common import Paginator
from atst.forms.portfolio import PortfolioForm
from atst.models.permissions import Permissions
from atst.domain.permission_sets import PermissionSets
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
@portfolios_bp.route("/portfolios")
@@ -37,6 +38,7 @@ def serialize_member(member):
@portfolios_bp.route("/portfolios/<portfolio_id>/admin")
@user_can(Permissions.VIEW_PORTFOLIO_ADMIN)
def portfolio_admin(portfolio_id):
portfolio = Portfolios.get_for_update_information(g.current_user, portfolio_id)
form = PortfolioForm(data={"name": portfolio.name})
@@ -56,6 +58,7 @@ def portfolio_admin(portfolio_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/edit", methods=["POST"])
@user_can(Permissions.EDIT_PORTFOLIO_NAME)
def edit_portfolio(portfolio_id):
portfolio = Portfolios.get_for_update_information(g.current_user, portfolio_id)
form = PortfolioForm(http_request.form)
@@ -69,6 +72,7 @@ def edit_portfolio(portfolio_id):
@portfolios_bp.route("/portfolios/<portfolio_id>")
@user_can(Permissions.VIEW_PORTFOLIO)
def show_portfolio(portfolio_id):
return redirect(
url_for("portfolios.portfolio_applications", portfolio_id=portfolio_id)
@@ -76,6 +80,7 @@ def show_portfolio(portfolio_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/reports")
@user_can(Permissions.VIEW_PORTFOLIO_REPORTS)
def portfolio_reports(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
today = date.today()

View File

@@ -5,6 +5,8 @@ from atst.domain.portfolios import Portfolios
from atst.domain.invitations import Invitations
from atst.queue import queue
from atst.utils.flash import formatted_flash as flash
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
def send_invite_email(owner_name, token, new_member_email):
@@ -43,6 +45,7 @@ def accept_invitation(token):
@portfolios_bp.route(
"/portfolios/<portfolio_id>/invitations/<token>/revoke", methods=["POST"]
)
@user_can(Permissions.EDIT_PORTFOLIO_USERS)
def revoke_invitation(portfolio_id, token):
portfolio = Portfolios.get_for_update_member(g.current_user, portfolio_id)
Invitations.revoke(token)
@@ -53,6 +56,7 @@ def revoke_invitation(portfolio_id, token):
@portfolios_bp.route(
"/portfolios/<portfolio_id>/invitations/<token>/resend", methods=["POST"]
)
@user_can(Permissions.EDIT_PORTFOLIO_USERS)
def resend_invitation(portfolio_id, token):
invite = Invitations.resend(g.current_user, portfolio_id, token)
send_invite_email(g.current_user.full_name, invite.token, invite.email)

View File

@@ -12,6 +12,8 @@ from atst.domain.environment_roles import EnvironmentRoles
from atst.services.invitation import Invitation as InvitationService
import atst.forms.portfolio_member as member_forms
from atst.forms.data import ENVIRONMENT_ROLES, ENV_ROLE_MODAL_DESCRIPTION
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash
@@ -32,6 +34,7 @@ def serialize_portfolio_role(portfolio_role):
@portfolios_bp.route("/portfolios/<portfolio_id>/members")
@user_can(Permissions.VIEW_PORTFOLIO_USERS)
def portfolio_members(portfolio_id):
portfolio = Portfolios.get_with_members(g.current_user, portfolio_id)
members_list = [serialize_portfolio_role(k) for k in portfolio.members]
@@ -45,6 +48,7 @@ def portfolio_members(portfolio_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/applications/<application_id>/members")
@user_can(Permissions.VIEW_APPLICATION_MEMBER)
def application_members(portfolio_id, application_id):
portfolio = Portfolios.get_with_members(g.current_user, portfolio_id)
application = Applications.get(g.current_user, portfolio, application_id)
@@ -60,6 +64,7 @@ def application_members(portfolio_id, application_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/members/new")
@user_can(Permissions.CREATE_PORTFOLIO_USERS)
def new_member(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
form = member_forms.NewForm()
@@ -69,6 +74,7 @@ def new_member(portfolio_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/members/new", methods=["POST"])
@user_can(Permissions.CREATE_PORTFOLIO_USERS)
def create_member(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
form = member_forms.NewForm(http_request.form)
@@ -97,6 +103,7 @@ def create_member(portfolio_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/members/<member_id>/member_edit")
@user_can(Permissions.VIEW_PORTFOLIO_USERS)
def view_member(portfolio_id, member_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
member = PortfolioRoles.get(portfolio_id, member_id)
@@ -125,6 +132,7 @@ def view_member(portfolio_id, member_id):
@portfolios_bp.route(
"/portfolios/<portfolio_id>/members/<member_id>/member_edit", methods=["POST"]
)
@user_can(Permissions.EDIT_PORTFOLIO_USERS)
def update_member(portfolio_id, member_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
member = PortfolioRoles.get(portfolio_id, member_id)
@@ -163,6 +171,7 @@ def update_member(portfolio_id, member_id):
@portfolios_bp.route(
"/portfolios/<portfolio_id>/members/<member_id>/revoke_access", methods=["POST"]
)
@user_can(Permissions.EDIT_PORTFOLIO_USERS)
def revoke_access(portfolio_id, member_id):
revoked_role = Portfolios.revoke_access(g.current_user, portfolio_id, member_id)
flash("revoked_portfolio_access", member_name=revoked_role.user.full_name)

View File

@@ -20,9 +20,12 @@ from atst.services.invitation import (
OFFICER_INVITATIONS,
Invitation as InvitationService,
)
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
@portfolios_bp.route("/portfolios/<portfolio_id>/task_orders")
@user_can(Permissions.VIEW_PORTFOLIO_FUNDING)
def portfolio_funding(portfolio_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
task_orders_by_status = defaultdict(list)
@@ -66,6 +69,7 @@ def portfolio_funding(portfolio_id):
@portfolios_bp.route("/portfolios/<portfolio_id>/task_order/<task_order_id>")
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS)
def view_task_order(portfolio_id, task_order_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
task_order = TaskOrders.get(g.current_user, task_order_id)
@@ -85,13 +89,19 @@ def view_task_order(portfolio_id, task_order_id):
)
def wrap_check_is_ko_or_cor(user, _perm, task_order_id=None, **_kwargs):
task_order = TaskOrders.get(user, task_order_id)
Authorization.check_is_ko_or_cor(user, task_order)
return True
@portfolios_bp.route("/portfolios/<portfolio_id>/task_order/<task_order_id>/review")
@user_can(None, exceptions=[wrap_check_is_ko_or_cor])
def ko_review(portfolio_id, task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
portfolio = Portfolios.get(g.current_user, portfolio_id)
Authorization.check_is_ko_or_cor(g.current_user, task_order)
if TaskOrders.all_sections_complete(task_order):
return render_template(
"/portfolios/task_orders/review.html",
@@ -107,6 +117,7 @@ def ko_review(portfolio_id, task_order_id):
"/portfolios/<portfolio_id>/task_order/<task_order_id>/resend_invite",
methods=["POST"],
)
@user_can(Permissions.EDIT_TASK_ORDER_DETAILS)
def resend_invite(portfolio_id, task_order_id, form=None):
invite_type = http_request.args.get("invite_type")
@@ -164,13 +175,12 @@ def resend_invite(portfolio_id, task_order_id, form=None):
@portfolios_bp.route(
"/portfolios/<portfolio_id>/task_order/<task_order_id>/review", methods=["POST"]
)
@user_can(None, exceptions=[wrap_check_is_ko_or_cor])
def submit_ko_review(portfolio_id, task_order_id, form=None):
task_order = TaskOrders.get(g.current_user, task_order_id)
form_data = {**http_request.form, **http_request.files}
form = KOReviewForm(form_data)
Authorization.check_is_ko_or_cor(g.current_user, task_order)
if form.validate():
TaskOrders.update(user=g.current_user, task_order=task_order, **form.data)
if Authorization.is_ko(g.current_user, task_order) and TaskOrders.can_ko_sign(
@@ -199,6 +209,7 @@ def submit_ko_review(portfolio_id, task_order_id, form=None):
@portfolios_bp.route(
"/portfolios/<portfolio_id>/task_order/<task_order_id>/invitations"
)
@user_can(Permissions.EDIT_TASK_ORDER_DETAILS)
def task_order_invitations(portfolio_id, task_order_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
task_order = TaskOrders.get(g.current_user, task_order_id)
@@ -219,6 +230,7 @@ def task_order_invitations(portfolio_id, task_order_id):
"/portfolios/<portfolio_id>/task_order/<task_order_id>/invitations",
methods=["POST"],
)
@user_can(Permissions.EDIT_TASK_ORDER_DETAILS)
def edit_task_order_invitations(portfolio_id, task_order_id):
portfolio = Portfolios.get(g.current_user, portfolio_id)
task_order = TaskOrders.get(g.current_user, task_order_id)
@@ -266,11 +278,17 @@ def so_review_form(task_order):
return DD254Form(data=form_data)
def wrap_check_is_so(user, _perm, task_order_id=None, **_kwargs):
task_order = TaskOrders.get(user, task_order_id)
Authorization.check_is_so(user, task_order)
return True
@portfolios_bp.route("/portfolios/<portfolio_id>/task_order/<task_order_id>/dd254")
@user_can(None, exceptions=[wrap_check_is_so])
def so_review(portfolio_id, task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
Authorization.check_is_so(g.current_user, task_order)
form = so_review_form(task_order)
return render_template(
@@ -284,10 +302,9 @@ def so_review(portfolio_id, task_order_id):
@portfolios_bp.route(
"/portfolios/<portfolio_id>/task_order/<task_order_id>/dd254", methods=["POST"]
)
@user_can(None, exceptions=[wrap_check_is_so])
def submit_so_review(portfolio_id, task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
Authorization.check_is_so(g.current_user, task_order)
form = DD254Form(http_request.form)
if form.validate():

View File

@@ -5,9 +5,12 @@ from . import task_orders_bp
from atst.domain.task_orders import TaskOrders
from atst.domain.exceptions import NotFoundError
from atst.utils.docx import Docx
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
@task_orders_bp.route("/task_orders/download_summary/<task_order_id>")
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS)
def download_summary(task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
byte_str = BytesIO()
@@ -31,6 +34,7 @@ def send_file(attachment):
@task_orders_bp.route("/task_orders/csp_estimate/<task_order_id>")
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS)
def download_csp_estimate(task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
if task_order.csp_estimate:
@@ -40,6 +44,7 @@ def download_csp_estimate(task_order_id):
@task_orders_bp.route("/task_orders/pdf/<task_order_id>")
@user_can(Permissions.VIEW_TASK_ORDER_DETAILS)
def download_task_order_pdf(task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
if task_order.pdf:

View File

@@ -4,9 +4,12 @@ from . import task_orders_bp
from atst.domain.task_orders import TaskOrders
from atst.utils.flash import formatted_flash as flash
from atst.services.invitation import update_officer_invitations
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
@task_orders_bp.route("/task_orders/invite/<task_order_id>", methods=["POST"])
@user_can(Permissions.EDIT_TASK_ORDER_DETAILS)
def invite(task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
if TaskOrders.all_sections_complete(task_order):

View File

@@ -14,6 +14,8 @@ from atst.domain.task_orders import TaskOrders
from atst.domain.portfolios import Portfolios
from atst.utils.flash import formatted_flash as flash
import atst.forms.task_order as task_order_form
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
TASK_ORDER_SECTIONS = [
@@ -249,9 +251,19 @@ def get_started():
return render_template("task_orders/new/get_started.html") # pragma: no cover
def is_new_task_order(*args, **kwargs):
return (
"screen" in kwargs
and kwargs["screen"] == 1
and "task_order_id" not in kwargs
and "portfolio_id" not in kwargs
)
@task_orders_bp.route("/task_orders/new/<int:screen>")
@task_orders_bp.route("/task_orders/new/<int:screen>/<task_order_id>")
@task_orders_bp.route("/portfolios/<portfolio_id>/task_orders/new/<int:screen>")
@user_can(Permissions.CREATE_TASK_ORDER, exceptions=[is_new_task_order])
def new(screen, task_order_id=None, portfolio_id=None):
workflow = ShowTaskOrderWorkflow(
g.current_user, screen, task_order_id, portfolio_id
@@ -298,6 +310,7 @@ def new(screen, task_order_id=None, portfolio_id=None):
@task_orders_bp.route(
"/portfolios/<portfolio_id>/task_orders/new/<int:screen>", methods=["POST"]
)
@user_can(Permissions.CREATE_TASK_ORDER, exceptions=[is_new_task_order])
def update(screen, task_order_id=None, portfolio_id=None):
form_data = {**http_request.form, **http_request.files}
workflow = UpdateTaskOrderWorkflow(

View File

@@ -8,11 +8,11 @@ from atst.domain.exceptions import NoAccessError
from atst.domain.task_orders import TaskOrders
from atst.forms.task_order import SignatureForm
from atst.utils.flash import formatted_flash as flash
from atst.domain.authz.decorator import user_can_access_decorator as user_can
def find_unsigned_ko_to(task_order_id):
task_order = TaskOrders.get(g.current_user, task_order_id)
Authorization.check_is_ko(g.current_user, task_order)
if not TaskOrders.can_ko_sign(task_order):
raise NoAccessError("task_order")
@@ -20,7 +20,15 @@ def find_unsigned_ko_to(task_order_id):
return task_order
def wrap_check_is_ko(user, _perm, task_order_id=None, **_kwargs):
task_order = TaskOrders.get(user, task_order_id)
Authorization.check_is_ko(user, task_order)
return True
@task_orders_bp.route("/task_orders/<task_order_id>/digital_signature", methods=["GET"])
@user_can(None, exceptions=[wrap_check_is_ko])
def signature_requested(task_order_id):
task_order = find_unsigned_ko_to(task_order_id)
@@ -35,6 +43,7 @@ def signature_requested(task_order_id):
@task_orders_bp.route(
"/task_orders/<task_order_id>/digital_signature", methods=["POST"]
)
@user_can(None, exceptions=[wrap_check_is_ko])
def record_signature(task_order_id):
task_order = find_unsigned_ko_to(task_order_id)