Merge pull request #676 from dod-ccpo/to-edit-sign-permissions

Break KO signing  into two steps
This commit is contained in:
George Drummond 2019-02-27 09:43:48 -05:00 committed by GitHub
commit c4383c8859
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 226 additions and 41 deletions

View File

@ -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:

View File

@ -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

View File

@ -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",

View File

@ -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

View File

@ -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">

View 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)

View File

@ -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]

View File

@ -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

View File

@ -20,6 +20,7 @@ base_public:
title_tag: JEDI Cloud
common:
back: Back
edit: Edit
manage: manage
save_and_continue: Save & Continue
sign: Sign