Merge pull request #730 from dod-ccpo/archive-portfolio-member
Remove Portfolio User
This commit is contained in:
commit
48d9506f96
@ -1,6 +1,7 @@
|
||||
from atst.utils import first_or_none
|
||||
from atst.models.permissions import Permissions
|
||||
from atst.domain.exceptions import UnauthorizedError
|
||||
from atst.models.portfolio_role import Status as PortfolioRoleStatus
|
||||
|
||||
|
||||
class Authorization(object):
|
||||
@ -9,7 +10,7 @@ class Authorization(object):
|
||||
port_role = first_or_none(
|
||||
lambda pr: pr.portfolio == portfolio, user.portfolio_roles
|
||||
)
|
||||
if port_role:
|
||||
if port_role and port_role.status is not PortfolioRoleStatus.DISABLED:
|
||||
return permission in port_role.permissions
|
||||
else:
|
||||
return False
|
||||
|
@ -121,6 +121,15 @@ class PortfolioRoles(object):
|
||||
)
|
||||
return PermissionSets.get_many(perms_set_names)
|
||||
|
||||
@classmethod
|
||||
def disable(cls, portfolio_role):
|
||||
portfolio_role.status = PortfolioRoleStatus.DISABLED
|
||||
|
||||
db.session.add(portfolio_role)
|
||||
db.session.commit()
|
||||
|
||||
return portfolio_role
|
||||
|
||||
@classmethod
|
||||
def update(cls, portfolio_role, set_names):
|
||||
new_permission_sets = PortfolioRoles._permission_sets_for_names(set_names)
|
||||
|
@ -2,8 +2,6 @@ from datetime import date, timedelta
|
||||
|
||||
from flask import render_template, request as http_request, g, redirect, url_for
|
||||
|
||||
from atst.utils.flash import formatted_flash as flash
|
||||
|
||||
from . import portfolios_bp
|
||||
from atst.domain.reports import Reports
|
||||
from atst.domain.portfolios import Portfolios
|
||||
@ -15,6 +13,8 @@ import atst.forms.portfolio_member as member_forms
|
||||
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.utils.flash import formatted_flash as flash
|
||||
from atst.domain.exceptions import UnauthorizedError
|
||||
|
||||
|
||||
@portfolios_bp.route("/portfolios")
|
||||
@ -174,3 +174,28 @@ def portfolio_reports(portfolio_id):
|
||||
expiration_date=expiration_date,
|
||||
remaining_days=remaining_days,
|
||||
)
|
||||
|
||||
|
||||
@portfolios_bp.route(
|
||||
"/portfolios/<portfolio_id>/members/<user_id>/delete", methods=["POST"]
|
||||
)
|
||||
@user_can(Permissions.EDIT_PORTFOLIO_USERS, message="update portfolio members")
|
||||
def remove_member(portfolio_id, user_id):
|
||||
if str(g.current_user.id) == user_id:
|
||||
raise UnauthorizedError(
|
||||
g.current_user, "you cant remove yourself from the portfolio"
|
||||
)
|
||||
|
||||
portfolio_role = PortfolioRoles.get(portfolio_id=portfolio_id, user_id=user_id)
|
||||
PortfolioRoles.disable(portfolio_role=portfolio_role)
|
||||
|
||||
flash("portfolio_member_removed", member_name=portfolio_role.user.full_name)
|
||||
|
||||
return redirect(
|
||||
url_for(
|
||||
"portfolios.portfolio_admin",
|
||||
portfolio_id=portfolio_id,
|
||||
_anchor="portfolio-members",
|
||||
fragment="portfolio-members",
|
||||
)
|
||||
)
|
||||
|
@ -138,6 +138,11 @@ MESSAGES = {
|
||||
""",
|
||||
"category": "error",
|
||||
},
|
||||
"portfolio_member_removed": {
|
||||
"title_template": "Portfolio Member Removed",
|
||||
"message_template": "You have successfully removed {{ member_name }} from the portfolio.",
|
||||
"category": "success",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
@ -291,6 +291,10 @@
|
||||
height: 4rem;
|
||||
}
|
||||
|
||||
.usa-button-danger {
|
||||
background: $color-red;
|
||||
}
|
||||
|
||||
.members-table-footer {
|
||||
float: right;
|
||||
padding: 3 * $gap;
|
||||
|
@ -1,4 +1,8 @@
|
||||
{% from "components/confirmation_button.html" import ConfirmationButton %}
|
||||
|
||||
{% for subform in member_perms_form.members_permissions %}
|
||||
{% set modal_id = "portfolio_id_{}_user_id_{}".format(portfolio.id, subform.user_id.data) %}
|
||||
|
||||
<tr>
|
||||
<td class='name'>{{ subform.member.data }}
|
||||
{% if subform.member.data == user.full_name %}
|
||||
@ -14,7 +18,10 @@
|
||||
<td>{{ OptionsInput(subform.perms_reporting, label=False) }}</td>
|
||||
<td>{{ OptionsInput(subform.perms_portfolio_mgmt, label=False) }}</td>
|
||||
|
||||
<td><button type="button" class='{{ archive_button_class }}'>{{ "portfolios.members.archive_button" | translate }}</button>
|
||||
<td>
|
||||
<a v-on:click="openModal('{{ modal_id }}')" class='usa-button {{ archive_button_class }}'>
|
||||
{{ "portfolios.members.archive_button" | translate }}
|
||||
</a>
|
||||
</td>
|
||||
{{ subform.user_id() }}
|
||||
</tr>
|
||||
|
@ -1,6 +1,9 @@
|
||||
{% from "components/icon.html" import Icon %}
|
||||
{% from "components/options_input.html" import OptionsInput %}
|
||||
|
||||
{% from "components/modal.html" import Modal %}
|
||||
{% from "components/alert.html" import Alert %}
|
||||
|
||||
<section class="member-list" id="portfolio-members">
|
||||
<div class='responsive-table-wrapper panel'>
|
||||
{% if g.matchesPath("portfolio-members") %}
|
||||
@ -51,6 +54,34 @@
|
||||
{% endif %}
|
||||
|
||||
</form>
|
||||
|
||||
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
||||
{% for member in portfolio.members %}
|
||||
{% set modal_id = "portfolio_id_{}_user_id_{}".format(portfolio.id, member.user_id) %}
|
||||
{% call Modal(name=modal_id, dismissable=False) %}
|
||||
<h1>Are you sure you want to archive this user?</h1>
|
||||
|
||||
{{
|
||||
Alert(
|
||||
title="Warning! You are about to archive a user from the portfolio admin.",
|
||||
message="User will be removed from the portfolio, but their log history will be retained.",
|
||||
level="warning"
|
||||
)
|
||||
}}
|
||||
|
||||
<div class="action-group">
|
||||
<form method="POST" action="{{ url_for('portfolios.remove_member', portfolio_id=portfolio.id, user_id=member.user_id) }}">
|
||||
{{ member_perms_form.csrf_token }}
|
||||
<button class="usa-button usa-button-danger">
|
||||
{{ "portfolios.members.archive_button" | translate }}
|
||||
</button>
|
||||
</form>
|
||||
<a v-on:click="closeModal('{{ modal_id }}')" class="action-group__action icon-link icon-link--default">Cancel</a>
|
||||
</div>
|
||||
{% endcall %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="members-table-footer">
|
||||
<div class="action-group">
|
||||
{% if user_can(permissions.EDIT_PORTFOLIO_USERS) %}
|
||||
|
@ -11,6 +11,7 @@ from atst.domain.authz.decorator import user_can_access_decorator
|
||||
from atst.domain.permission_sets import PermissionSets
|
||||
from atst.domain.exceptions import UnauthorizedError
|
||||
from atst.models.permissions import Permissions
|
||||
from atst.domain.portfolio_roles import PortfolioRoles
|
||||
|
||||
from tests.utils import FakeLogger
|
||||
|
||||
@ -75,7 +76,7 @@ def test_user_can_access():
|
||||
|
||||
portfolio = PortfolioFactory.create(owner=edit_admin)
|
||||
# factory gives view perms by default
|
||||
PortfolioRoleFactory.create(user=view_admin, portfolio=portfolio)
|
||||
view_admin_pr = PortfolioRoleFactory.create(user=view_admin, portfolio=portfolio)
|
||||
|
||||
# check a site-wide permission
|
||||
assert user_can_access(ccpo, Permissions.VIEW_AUDIT_LOG)
|
||||
@ -101,6 +102,13 @@ def test_user_can_access():
|
||||
view_admin, Permissions.EDIT_PORTFOLIO_NAME, portfolio=portfolio
|
||||
)
|
||||
|
||||
# check when portfolio_role is disabled
|
||||
PortfolioRoles.disable(portfolio_role=view_admin_pr)
|
||||
with pytest.raises(UnauthorizedError):
|
||||
user_can_access(
|
||||
view_admin, Permissions.EDIT_PORTFOLIO_NAME, portfolio=portfolio
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def set_current_user(request_ctx):
|
||||
|
@ -29,3 +29,11 @@ def test_add_portfolio_role_with_permission_sets():
|
||||
]
|
||||
actual_names = [prms.name for prms in port_role.permission_sets]
|
||||
assert expected_names == expected_names
|
||||
|
||||
|
||||
def test_disable_portfolio_role():
|
||||
portfolio_role = PortfolioRoleFactory.create(status=PortfolioRoleStatus.ACTIVE)
|
||||
assert portfolio_role.status == PortfolioRoleStatus.ACTIVE
|
||||
|
||||
PortfolioRoles.disable(portfolio_role=portfolio_role)
|
||||
assert portfolio_role.status == PortfolioRoleStatus.DISABLED
|
||||
|
@ -2,6 +2,8 @@ from flask import url_for
|
||||
|
||||
from atst.domain.permission_sets import PermissionSets
|
||||
from atst.models.permissions import Permissions
|
||||
from atst.domain.portfolio_roles import PortfolioRoles
|
||||
from atst.models.portfolio_role import Status as PortfolioRoleStatus
|
||||
|
||||
from tests.factories import (
|
||||
random_future_date,
|
||||
@ -81,6 +83,54 @@ def test_portfolio_admin_screen_when_not_ppoc(client, user_session):
|
||||
assert translate("fragments.ppoc.update_btn").encode("utf8") not in response.data
|
||||
|
||||
|
||||
def test_remove_portfolio_member(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
|
||||
user = UserFactory.create()
|
||||
PortfolioRoleFactory.create(portfolio=portfolio, user=user)
|
||||
|
||||
user_session(portfolio.owner)
|
||||
|
||||
response = client.post(
|
||||
url_for("portfolios.remove_member", portfolio_id=portfolio.id, user_id=user.id),
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.headers["Location"] == url_for(
|
||||
"portfolios.portfolio_admin",
|
||||
portfolio_id=portfolio.id,
|
||||
_anchor="portfolio-members",
|
||||
fragment="portfolio-members",
|
||||
_external=True,
|
||||
)
|
||||
assert (
|
||||
PortfolioRoles.get(portfolio_id=portfolio.id, user_id=user.id).status
|
||||
== PortfolioRoleStatus.DISABLED
|
||||
)
|
||||
|
||||
|
||||
def test_remove_portfolio_member_self(client, user_session):
|
||||
portfolio = PortfolioFactory.create()
|
||||
|
||||
user_session(portfolio.owner)
|
||||
|
||||
response = client.post(
|
||||
url_for(
|
||||
"portfolios.remove_member",
|
||||
portfolio_id=portfolio.id,
|
||||
user_id=portfolio.owner.id,
|
||||
),
|
||||
follow_redirects=False,
|
||||
)
|
||||
|
||||
assert response.status_code == 404
|
||||
assert (
|
||||
PortfolioRoles.get(portfolio_id=portfolio.id, user_id=portfolio.owner.id).status
|
||||
== PortfolioRoleStatus.ACTIVE
|
||||
)
|
||||
|
||||
|
||||
def test_portfolio_reports(client, user_session):
|
||||
portfolio = PortfolioFactory.create(
|
||||
applications=[
|
||||
|
Loading…
x
Reference in New Issue
Block a user