Merge pull request #676 from dod-ccpo/to-edit-sign-permissions
Break KO signing into two steps
This commit is contained in:
commit
c4383c8859
@ -36,6 +36,27 @@ class Authorization(object):
|
||||
def is_ccpo(cls, user):
|
||||
return user.atat_role.name == "ccpo"
|
||||
|
||||
@classmethod
|
||||
def is_ko(cls, user, task_order):
|
||||
return user == task_order.contracting_officer
|
||||
|
||||
@classmethod
|
||||
def is_cor(cls, user, task_order):
|
||||
return user == task_order.contracting_officer_representative
|
||||
|
||||
@classmethod
|
||||
def is_so(cls, user, task_order):
|
||||
return user == task_order.security_officer
|
||||
|
||||
@classmethod
|
||||
def check_is_ko_or_cor(cls, user, task_order):
|
||||
if Authorization.is_ko(user, task_order) or Authorization.is_cor(
|
||||
user, task_order
|
||||
):
|
||||
return True
|
||||
else:
|
||||
raise UnauthorizedError(user, "not KO or COR")
|
||||
|
||||
@classmethod
|
||||
def check_is_ko(cls, user, task_order):
|
||||
if task_order.contracting_officer != user:
|
||||
|
@ -120,6 +120,10 @@ class TaskOrders(object):
|
||||
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def is_signed_by_ko(cls, task_order):
|
||||
return task_order.signer_dod_id is not None
|
||||
|
||||
@classmethod
|
||||
def mission_owner_sections(cls):
|
||||
section_list = TaskOrders.SECTIONS
|
||||
|
@ -65,10 +65,14 @@ def view_task_order(portfolio_id, task_order_id):
|
||||
dd_254_complete = DD254s.is_complete(task_order.dd_254)
|
||||
return render_template(
|
||||
"portfolios/task_orders/show.html",
|
||||
dd_254_complete=dd_254_complete,
|
||||
is_cor=Authorization.is_cor(g.current_user, task_order),
|
||||
is_ko=Authorization.is_ko(g.current_user, task_order),
|
||||
is_so=Authorization.is_so(g.current_user, task_order),
|
||||
is_to_signed=TaskOrders.is_signed_by_ko(task_order),
|
||||
portfolio=portfolio,
|
||||
task_order=task_order,
|
||||
to_form_complete=to_form_complete,
|
||||
dd_254_complete=dd_254_complete,
|
||||
user=g.current_user,
|
||||
)
|
||||
|
||||
@ -78,7 +82,8 @@ 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(g.current_user, task_order)
|
||||
Authorization.check_is_ko_or_cor(g.current_user, task_order)
|
||||
|
||||
return render_template(
|
||||
"/portfolios/task_orders/review.html",
|
||||
portfolio=portfolio,
|
||||
@ -95,12 +100,22 @@ def submit_ko_review(portfolio_id, task_order_id, form=None):
|
||||
form_data = {**http_request.form, **http_request.files}
|
||||
form = KOReviewForm(form_data)
|
||||
|
||||
Authorization.check_is_ko(g.current_user, task_order)
|
||||
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)
|
||||
return redirect(
|
||||
url_for("task_orders.signature_requested", task_order_id=task_order_id)
|
||||
)
|
||||
if Authorization.is_ko(g.current_user, task_order):
|
||||
return redirect(
|
||||
url_for("task_orders.signature_requested", task_order_id=task_order_id)
|
||||
)
|
||||
else:
|
||||
return redirect(
|
||||
url_for(
|
||||
"portfolios.view_task_order",
|
||||
task_order_id=task_order_id,
|
||||
portfolio_id=portfolio_id,
|
||||
)
|
||||
)
|
||||
else:
|
||||
return render_template(
|
||||
"/portfolios/task_orders/review.html",
|
||||
|
@ -14,7 +14,7 @@ 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 task_order.signer_dod_id is not None:
|
||||
if TaskOrders.is_signed_by_ko(task_order):
|
||||
raise NotFoundError("task_order")
|
||||
|
||||
return task_order
|
||||
|
@ -124,27 +124,37 @@
|
||||
<div id="next-steps" class="task-order-next-steps">
|
||||
<div class="panel">
|
||||
<h3 class="task-order-next-steps__panel-head panel__content">{{ "task_orders.view.whats_next" | translate }}</h3>
|
||||
{% call Step(
|
||||
description="task_orders.view.steps.draft" | translate({
|
||||
"contact": officer_name(task_order.creator)
|
||||
})| safe,
|
||||
button_url=url_for("task_orders.new", screen=1, task_order_id=task_order.id),
|
||||
button_text='Edit',
|
||||
complete=to_form_complete) %}
|
||||
{% endcall %}
|
||||
{% set is_so = user == task_order.security_officer %}
|
||||
{{ Step(
|
||||
description="task_orders.view.steps.security" | translate({
|
||||
"security_officer": officer_name(task_order.security_officer)
|
||||
}) | safe,
|
||||
button_url=is_so and url_for("portfolios.so_review", portfolio_id=portfolio.id, task_order_id=task_order.id),
|
||||
button_text=is_so and 'Edit',
|
||||
complete=dd_254_complete) }}
|
||||
{{
|
||||
Step(
|
||||
button_text='Edit',
|
||||
button_url=url_for("task_orders.new", screen=1, task_order_id=task_order.id),
|
||||
complete=to_form_complete,
|
||||
description="task_orders.view.steps.draft" | translate({
|
||||
"contact": officer_name(task_order.creator)
|
||||
})| safe,
|
||||
)
|
||||
}}
|
||||
{{
|
||||
Step(
|
||||
button_text=is_so and ("common.edit" | translate),
|
||||
button_url=is_so and url_for("portfolios.so_review", portfolio_id=portfolio.id, task_order_id=task_order.id),
|
||||
complete=dd_254_complete,
|
||||
description="task_orders.view.steps.security" | translate({
|
||||
"security_officer": officer_name(task_order.security_officer)
|
||||
}) | safe,
|
||||
)
|
||||
}}
|
||||
{% call Step(
|
||||
description="task_orders.view.steps.record" | translate({
|
||||
"contracting_officer": officer_name(task_order.contracting_officer),
|
||||
"contracting_officer_representative": officer_name(task_order.contracting_officer_representative)
|
||||
}) | safe,
|
||||
button_url=url_for(
|
||||
"portfolios.ko_review",
|
||||
portfolio_id=portfolio.id,
|
||||
task_order_id=task_order.id,
|
||||
),
|
||||
button_text=(is_cor or is_ko) and ("common.edit" | translate),
|
||||
complete=False) %}
|
||||
<div class='alert alert--warning'>
|
||||
<div class='alert__content'>
|
||||
@ -154,14 +164,20 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% set is_ko = user == task_order.contracting_officer %}
|
||||
{{ Step(
|
||||
description="task_orders.view.steps.sign" | translate({
|
||||
"contracting_officer": officer_name(task_order.contracting_officer)
|
||||
}) | safe,
|
||||
button_url=is_ko and url_for("portfolios.ko_review", portfolio_id=portfolio.id, task_order_id=task_order.id),
|
||||
button_text=is_ko and 'Sign',
|
||||
complete=False) }}
|
||||
{{
|
||||
Step(
|
||||
button_text=is_ko and not is_to_signed and ("common.sign" | translate),
|
||||
button_url=is_ko and url_for(
|
||||
"portfolios.ko_review",
|
||||
portfolio_id=portfolio.id,
|
||||
task_order_id=task_order.id,
|
||||
),
|
||||
complete=is_to_signed,
|
||||
description="task_orders.view.steps.sign" | translate({
|
||||
"contracting_officer": officer_name(task_order.contracting_officer)
|
||||
}) | safe,
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-order-sidebar col">
|
||||
|
42
tests/domain/test_authz.py
Normal file
42
tests/domain/test_authz.py
Normal file
@ -0,0 +1,42 @@
|
||||
import pytest
|
||||
|
||||
from tests.factories import TaskOrderFactory, UserFactory
|
||||
from atst.domain.authz import Authorization
|
||||
from atst.domain.exceptions import UnauthorizedError
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def invalid_user():
|
||||
return UserFactory.create()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def task_order():
|
||||
return TaskOrderFactory.create()
|
||||
|
||||
|
||||
def test_is_ko(task_order, invalid_user):
|
||||
assert not Authorization.is_ko(invalid_user, task_order)
|
||||
assert Authorization.is_ko(task_order.contracting_officer, task_order)
|
||||
|
||||
|
||||
def test_is_cor(task_order, invalid_user):
|
||||
assert not Authorization.is_cor(invalid_user, task_order)
|
||||
assert Authorization.is_cor(
|
||||
task_order.contracting_officer_representative, task_order
|
||||
)
|
||||
|
||||
|
||||
def test_is_so(task_order, invalid_user):
|
||||
assert Authorization.is_so(task_order.security_officer, task_order)
|
||||
assert not Authorization.is_so(invalid_user, task_order)
|
||||
|
||||
|
||||
def test_check_is_ko_or_cor(task_order, invalid_user):
|
||||
assert Authorization.check_is_ko_or_cor(
|
||||
task_order.contracting_officer_representative, task_order
|
||||
)
|
||||
assert Authorization.check_is_ko_or_cor(task_order.contracting_officer, task_order)
|
||||
|
||||
with pytest.raises(UnauthorizedError):
|
||||
Authorization.check_is_ko_or_cor(invalid_user, task_order)
|
@ -13,6 +13,17 @@ from tests.factories import (
|
||||
)
|
||||
|
||||
|
||||
def test_is_signed_by_ko():
|
||||
user = UserFactory.create()
|
||||
task_order = TaskOrderFactory.create(contracting_officer=user)
|
||||
|
||||
assert not TaskOrders.is_signed_by_ko(task_order)
|
||||
|
||||
TaskOrders.update(user, task_order, signer_dod_id=user.dod_id)
|
||||
|
||||
assert TaskOrders.is_signed_by_ko(task_order)
|
||||
|
||||
|
||||
def test_section_completion_status():
|
||||
dict_keys = [k for k in TaskOrders.SECTIONS.keys()]
|
||||
section = dict_keys[0]
|
||||
|
@ -234,23 +234,50 @@ def test_cant_view_task_order_invitations_when_not_complete(client, user_session
|
||||
def test_ko_can_view_ko_review_page(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
ko = UserFactory.create()
|
||||
cor = UserFactory.create()
|
||||
|
||||
PortfolioRoleFactory.create(
|
||||
role=Roles.get("officer"),
|
||||
portfolio=portfolio,
|
||||
user=ko,
|
||||
status=PortfolioStatus.ACTIVE,
|
||||
)
|
||||
task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=ko)
|
||||
user_session(ko)
|
||||
response = client.get(
|
||||
url_for(
|
||||
"portfolios.ko_review",
|
||||
portfolio_id=portfolio.id,
|
||||
task_order_id=task_order.id,
|
||||
)
|
||||
PortfolioRoleFactory.create(
|
||||
role=Roles.get("officer"),
|
||||
portfolio=portfolio,
|
||||
user=cor,
|
||||
status=PortfolioStatus.ACTIVE,
|
||||
)
|
||||
task_order = TaskOrderFactory.create(
|
||||
portfolio=portfolio,
|
||||
contracting_officer=ko,
|
||||
contracting_officer_representative=cor,
|
||||
)
|
||||
request_url = url_for(
|
||||
"portfolios.ko_review", portfolio_id=portfolio.id, task_order_id=task_order.id
|
||||
)
|
||||
|
||||
#
|
||||
# KO returns 200
|
||||
#
|
||||
user_session(ko)
|
||||
response = client.get(request_url)
|
||||
assert response.status_code == 200
|
||||
|
||||
#
|
||||
# COR returns 200
|
||||
#
|
||||
user_session(cor)
|
||||
response = client.get(request_url)
|
||||
assert response.status_code == 200
|
||||
|
||||
#
|
||||
# Random user raises UnauthorizedError
|
||||
#
|
||||
user_session(UserFactory.create())
|
||||
response = client.get(request_url)
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_mo_redirected_to_build_page(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
@ -282,17 +309,66 @@ def test_cor_redirected_to_build_page(client, user_session):
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_submit_completed_ko_review_page(client, user_session, pdf_upload):
|
||||
def test_submit_completed_ko_review_page_as_cor(client, user_session, pdf_upload):
|
||||
portfolio = PortfolioFactory.create()
|
||||
|
||||
cor = UserFactory.create()
|
||||
|
||||
PortfolioRoleFactory.create(
|
||||
role=Roles.get("officer"),
|
||||
portfolio=portfolio,
|
||||
user=cor,
|
||||
status=PortfolioStatus.ACTIVE,
|
||||
)
|
||||
|
||||
task_order = TaskOrderFactory.create(
|
||||
portfolio=portfolio, contracting_officer_representative=cor
|
||||
)
|
||||
|
||||
form_data = {
|
||||
"start_date": "02/10/2019",
|
||||
"end_date": "03/10/2019",
|
||||
"number": "1938745981",
|
||||
"loa": "0813458013405",
|
||||
"custom_clauses": "hi im a custom clause",
|
||||
"pdf": pdf_upload,
|
||||
}
|
||||
|
||||
user_session(cor)
|
||||
|
||||
response = client.post(
|
||||
url_for(
|
||||
"portfolios.ko_review",
|
||||
portfolio_id=portfolio.id,
|
||||
task_order_id=task_order.id,
|
||||
),
|
||||
data=form_data,
|
||||
)
|
||||
|
||||
assert task_order.pdf
|
||||
assert response.headers["Location"] == url_for(
|
||||
"portfolios.view_task_order",
|
||||
task_order_id=task_order.id,
|
||||
portfolio_id=portfolio.id,
|
||||
_external=True,
|
||||
)
|
||||
|
||||
|
||||
def test_submit_completed_ko_review_page_as_ko(client, user_session, pdf_upload):
|
||||
portfolio = PortfolioFactory.create()
|
||||
|
||||
ko = UserFactory.create()
|
||||
|
||||
PortfolioRoleFactory.create(
|
||||
role=Roles.get("officer"),
|
||||
portfolio=portfolio,
|
||||
user=ko,
|
||||
status=PortfolioStatus.ACTIVE,
|
||||
)
|
||||
|
||||
task_order = TaskOrderFactory.create(portfolio=portfolio, contracting_officer=ko)
|
||||
user_session(ko)
|
||||
|
||||
form_data = {
|
||||
"start_date": "02/10/2019",
|
||||
"end_date": "03/10/2019",
|
||||
@ -310,7 +386,6 @@ def test_submit_completed_ko_review_page(client, user_session, pdf_upload):
|
||||
),
|
||||
data=form_data,
|
||||
)
|
||||
|
||||
assert task_order.pdf
|
||||
assert response.headers["Location"] == url_for(
|
||||
"task_orders.signature_requested", task_order_id=task_order.id, _external=True
|
||||
|
@ -20,6 +20,7 @@ base_public:
|
||||
title_tag: JEDI Cloud
|
||||
common:
|
||||
back: Back
|
||||
edit: Edit
|
||||
manage: manage
|
||||
save_and_continue: Save & Continue
|
||||
sign: Sign
|
||||
|
Loading…
x
Reference in New Issue
Block a user