Merge pull request #1146 from dod-ccpo/app-members-perms-form
Add revoke access to app members perms/env roles form
This commit is contained in:
commit
92ce3420b6
@ -29,6 +29,7 @@ class EnvironmentRoles(object):
|
|||||||
EnvironmentRole.application_role_id == application_role_id,
|
EnvironmentRole.application_role_id == application_role_id,
|
||||||
EnvironmentRole.environment_id == environment_id,
|
EnvironmentRole.environment_id == environment_id,
|
||||||
EnvironmentRole.deleted == False,
|
EnvironmentRole.deleted == False,
|
||||||
|
EnvironmentRole.status != EnvironmentRole.Status.DISABLED,
|
||||||
)
|
)
|
||||||
.one_or_none()
|
.one_or_none()
|
||||||
)
|
)
|
||||||
@ -57,6 +58,14 @@ class EnvironmentRoles(object):
|
|||||||
)
|
)
|
||||||
return existing_env_role
|
return existing_env_role
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _update_status(cls, environment_role, new_status):
|
||||||
|
environment_role.status = new_status
|
||||||
|
db.session.add(environment_role)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return environment_role
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete(cls, application_role_id, environment_id):
|
def delete(cls, application_role_id, environment_id):
|
||||||
existing_env_role = EnvironmentRoles.get(application_role_id, environment_id)
|
existing_env_role = EnvironmentRoles.get(application_role_id, environment_id)
|
||||||
@ -104,3 +113,15 @@ class EnvironmentRoles(object):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
return environment_role
|
return environment_role
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_for_update(cls, application_role_id, environment_id):
|
||||||
|
existing_env_role = (
|
||||||
|
db.session.query(EnvironmentRole)
|
||||||
|
.filter(
|
||||||
|
EnvironmentRole.application_role_id == application_role_id,
|
||||||
|
EnvironmentRole.environment_id == environment_id,
|
||||||
|
)
|
||||||
|
.one_or_none()
|
||||||
|
)
|
||||||
|
return existing_env_role
|
||||||
|
@ -4,10 +4,17 @@ from typing import List
|
|||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from atst.database import db
|
from atst.database import db
|
||||||
from atst.models import Environment, Application, Portfolio, TaskOrder, CLIN
|
from atst.models import (
|
||||||
|
Environment,
|
||||||
|
Application,
|
||||||
|
Portfolio,
|
||||||
|
TaskOrder,
|
||||||
|
CLIN,
|
||||||
|
EnvironmentRole,
|
||||||
|
)
|
||||||
from atst.domain.environment_roles import EnvironmentRoles
|
from atst.domain.environment_roles import EnvironmentRoles
|
||||||
|
|
||||||
from .exceptions import NotFoundError
|
from .exceptions import NotFoundError, DisabledError
|
||||||
|
|
||||||
|
|
||||||
class Environments(object):
|
class Environments(object):
|
||||||
@ -50,29 +57,31 @@ class Environments(object):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_env_role(cls, environment, application_role, new_role):
|
def update_env_role(cls, environment, application_role, new_role):
|
||||||
updated = False
|
env_role = EnvironmentRoles.get_for_update(application_role.id, environment.id)
|
||||||
|
if env_role and (
|
||||||
|
env_role.status == EnvironmentRole.Status.DISABLED or env_role.deleted
|
||||||
|
):
|
||||||
|
raise DisabledError("environment_role", env_role.id)
|
||||||
|
|
||||||
if new_role is None:
|
if (
|
||||||
updated = EnvironmentRoles.delete(application_role.id, environment.id)
|
env_role
|
||||||
else:
|
and env_role.role != new_role
|
||||||
env_role = EnvironmentRoles.get(application_role.id, environment.id)
|
and env_role.status != EnvironmentRole.Status.DISABLED
|
||||||
if env_role and env_role.role != new_role:
|
):
|
||||||
env_role.role = new_role
|
env_role.role = new_role
|
||||||
updated = True
|
|
||||||
db.session.add(env_role)
|
db.session.add(env_role)
|
||||||
elif not env_role:
|
elif not env_role and new_role:
|
||||||
env_role = EnvironmentRoles.create(
|
env_role = EnvironmentRoles.create(
|
||||||
application_role=application_role,
|
application_role=application_role,
|
||||||
environment=environment,
|
environment=environment,
|
||||||
role=new_role,
|
role=new_role,
|
||||||
)
|
)
|
||||||
updated = True
|
|
||||||
db.session.add(env_role)
|
db.session.add(env_role)
|
||||||
|
|
||||||
if updated:
|
if env_role and not new_role:
|
||||||
db.session.commit()
|
EnvironmentRoles.disable(env_role.id)
|
||||||
|
|
||||||
return updated
|
db.session.commit()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def revoke_access(cls, environment, target_user):
|
def revoke_access(cls, environment, target_user):
|
||||||
|
@ -53,3 +53,13 @@ class ClaimFailedException(Exception):
|
|||||||
f"Could not acquire claim for {resource.__class__.__name__} {resource.id}."
|
f"Could not acquire claim for {resource.__class__.__name__} {resource.id}."
|
||||||
)
|
)
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class DisabledError(Exception):
|
||||||
|
def __init__(self, resource_name, resource_id=None):
|
||||||
|
self.resource_name = resource_name
|
||||||
|
self.resource_id = resource_id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def message(self):
|
||||||
|
return f"Cannot update disabled {self.resource_name} {self.resource_id}."
|
||||||
|
@ -16,8 +16,9 @@ class EnvironmentForm(Form):
|
|||||||
environment_name,
|
environment_name,
|
||||||
choices=ENV_ROLES,
|
choices=ENV_ROLES,
|
||||||
default=NO_ACCESS,
|
default=NO_ACCESS,
|
||||||
filters=[lambda x: None if x == "None" else x],
|
filters=[lambda x: NO_ACCESS if x == "None" else x],
|
||||||
)
|
)
|
||||||
|
disabled = BooleanField("Revoke Access", default=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def data(self):
|
def data(self):
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
from sqlalchemy import and_, Index, ForeignKey, Column, Enum as SQLAEnum, Table
|
from sqlalchemy import Index, ForeignKey, Column, Enum as SQLAEnum, Table
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
from sqlalchemy.orm import relationship
|
from sqlalchemy.orm import relationship
|
||||||
from sqlalchemy.event import listen
|
from sqlalchemy.event import listen
|
||||||
@ -8,7 +8,6 @@ from atst.utils import first_or_none
|
|||||||
from atst.models.base import Base
|
from atst.models.base import Base
|
||||||
import atst.models.mixins as mixins
|
import atst.models.mixins as mixins
|
||||||
import atst.models.types as types
|
import atst.models.types as types
|
||||||
from atst.models.environment_role import EnvironmentRole
|
|
||||||
from atst.models.mixins.auditable import record_permission_sets_updates
|
from atst.models.mixins.auditable import record_permission_sets_updates
|
||||||
|
|
||||||
|
|
||||||
@ -55,9 +54,7 @@ class ApplicationRole(
|
|||||||
|
|
||||||
environment_roles = relationship(
|
environment_roles = relationship(
|
||||||
"EnvironmentRole",
|
"EnvironmentRole",
|
||||||
primaryjoin=and_(
|
primaryjoin="and_(EnvironmentRole.application_role_id == ApplicationRole.id, EnvironmentRole.deleted == False)",
|
||||||
EnvironmentRole.application_role_id == id, EnvironmentRole.deleted == False
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -14,6 +14,7 @@ from atst.forms.application import NameAndDescriptionForm, EditEnvironmentForm
|
|||||||
from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS
|
from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS
|
||||||
from atst.forms.member import NewForm as MemberForm
|
from atst.forms.member import NewForm as MemberForm
|
||||||
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
from atst.domain.authz.decorator import user_can_access_decorator as user_can
|
||||||
|
from atst.models.environment_role import EnvironmentRole
|
||||||
from atst.models.permissions import Permissions
|
from atst.models.permissions import Permissions
|
||||||
from atst.domain.permission_sets import PermissionSets
|
from atst.domain.permission_sets import PermissionSets
|
||||||
from atst.utils.flash import formatted_flash as flash
|
from atst.utils.flash import formatted_flash as flash
|
||||||
@ -31,7 +32,14 @@ def get_environments_obj_for_app(application):
|
|||||||
"edit_form": EditEnvironmentForm(obj=env),
|
"edit_form": EditEnvironmentForm(obj=env),
|
||||||
"member_count": len(env.roles),
|
"member_count": len(env.roles),
|
||||||
"members": sorted(
|
"members": sorted(
|
||||||
[env_role.application_role.user_name for env_role in env.roles]
|
[
|
||||||
|
{
|
||||||
|
"user_name": env_role.application_role.user_name,
|
||||||
|
"status": env_role.status.value,
|
||||||
|
}
|
||||||
|
for env_role in env.roles
|
||||||
|
],
|
||||||
|
key=lambda env_role: env_role["user_name"],
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
for env in application.environments
|
for env in application.environments
|
||||||
@ -77,11 +85,14 @@ def filter_env_roles_form_data(member, environments):
|
|||||||
"environment_id": str(env.id),
|
"environment_id": str(env.id),
|
||||||
"environment_name": env.name,
|
"environment_name": env.name,
|
||||||
"role": NO_ACCESS,
|
"role": NO_ACCESS,
|
||||||
|
"disabled": False,
|
||||||
}
|
}
|
||||||
env_roles_set = set(env.roles).intersection(set(member.environment_roles))
|
env_roles_set = set(env.roles).intersection(set(member.environment_roles))
|
||||||
|
|
||||||
if len(env_roles_set) == 1:
|
if len(env_roles_set) == 1:
|
||||||
(env_role,) = env_roles_set
|
(env_role,) = env_roles_set
|
||||||
env_data["role"] = env_role.role
|
env_data["role"] = env_role.role
|
||||||
|
env_data["disabled"] = env_role.status == EnvironmentRole.Status.DISABLED
|
||||||
|
|
||||||
env_roles_form_data.append(env_data)
|
env_roles_form_data.append(env_data)
|
||||||
|
|
||||||
@ -373,14 +384,21 @@ def remove_member(application_id, application_role_id):
|
|||||||
@user_can(Permissions.EDIT_APPLICATION_MEMBER, message="update application member")
|
@user_can(Permissions.EDIT_APPLICATION_MEMBER, message="update application member")
|
||||||
def update_member(application_id, application_role_id):
|
def update_member(application_id, application_role_id):
|
||||||
app_role = ApplicationRoles.get_by_id(application_role_id)
|
app_role = ApplicationRoles.get_by_id(application_role_id)
|
||||||
form = UpdateMemberForm(http_request.form)
|
application = Applications.get(application_id)
|
||||||
|
existing_env_roles_data = filter_env_roles_form_data(
|
||||||
|
app_role, application.environments
|
||||||
|
)
|
||||||
|
form = UpdateMemberForm(
|
||||||
|
formdata=http_request.form, environment_roles=existing_env_roles_data
|
||||||
|
)
|
||||||
|
|
||||||
if form.validate():
|
if form.validate():
|
||||||
ApplicationRoles.update_permission_sets(app_role, form.data["permission_sets"])
|
ApplicationRoles.update_permission_sets(app_role, form.data["permission_sets"])
|
||||||
|
|
||||||
for env_role in form.environment_roles:
|
for env_role in form.environment_roles:
|
||||||
environment = Environments.get(env_role.environment_id.data)
|
environment = Environments.get(env_role.environment_id.data)
|
||||||
Environments.update_env_role(environment, app_role, env_role.data["role"])
|
new_role = None if env_role.disabled.data else env_role.data["role"]
|
||||||
|
Environments.update_env_role(environment, app_role, new_role)
|
||||||
|
|
||||||
flash("application_member_updated", user_name=app_role.user_name)
|
flash("application_member_updated", user_name=app_role.user_name)
|
||||||
else:
|
else:
|
||||||
|
@ -1 +1 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M16 16h-2v-3.225l-3.919-.781C9.455 11.869 9 11.314 9 10.675V9.242c0-.477.236-.921.631-1.187C9.919 7.86 11 6.595 11 5c0-1.853-1.558-3-3-3-1.449 0-3 1.206-3 3 0 1.596 1.081 2.859 1.371 3.056.395.268.629.711.629 1.186v1.433c0 .64-.455 1.194-1.083 1.319l-3.916.783L2 16H0v-3.221c0-.951.677-1.776 1.609-1.963L5 10.139v-.623C4.235 8.839 3 7.136 3 5c0-3.088 2.595-5 5-5 2.757 0 5 2.243 5 5 0 2.134-1.234 3.837-2 4.516v.623l3.396.679c.929.187 1.604 1.01 1.604 1.957V16z"/></svg>
|
<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="user" class="svg-inline--fa fa-user fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M224 256c70.7 0 128-57.3 128-128S294.7 0 224 0 96 57.3 96 128s57.3 128 128 128zm89.6 32h-16.7c-22.2 10.2-46.9 16-72.9 16s-50.6-5.8-72.9-16h-16.7C60.2 288 0 348.2 0 422.4V464c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48v-41.6c0-74.2-60.2-134.4-134.4-134.4z"></path></svg>
|
Before Width: | Height: | Size: 540 B After Width: | Height: | Size: 484 B |
@ -91,16 +91,52 @@
|
|||||||
margin-bottom: 8 * $gap;
|
margin-bottom: 8 * $gap;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
|
|
||||||
.usa-input {
|
.form-row {
|
||||||
margin: $gap 0 $gap 0;
|
margin: $gap * 2;
|
||||||
|
|
||||||
.usa-input__title-inline {
|
|
||||||
margin-top: $gap;
|
|
||||||
margin-left: $gap;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row {
|
.usa-alert {
|
||||||
margin: 0;
|
margin: -0.5em 0;
|
||||||
|
padding: $gap;
|
||||||
|
background-image: unset;
|
||||||
|
|
||||||
|
&-body {
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.usa-input {
|
||||||
|
&__title-inline {
|
||||||
|
font-weight: $font-bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__help {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.env-role__no-access {
|
||||||
|
.usa-input__title-inline,
|
||||||
|
.usa-input__help {
|
||||||
|
color: $color-gray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__choices {
|
||||||
|
&.revoke-button label {
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.link {
|
||||||
|
color: $color-blue;
|
||||||
|
text-decoration: underline;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,15 +148,69 @@
|
|||||||
margin: 0.5em 0;
|
margin: 0.5em 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.environment-name--gray {
|
.environment-role {
|
||||||
|
padding: ($gap * 2) 0;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
margin-bottom: $gap / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__users {
|
||||||
|
background-color: $color-gray-lightest;
|
||||||
|
padding: ($gap * 1.2) ($gap * 0.6);
|
||||||
|
font-size: $small-font-size;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.environment-role__user {
|
||||||
|
background-color: $color-white;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: ($gap / 2) $gap;
|
||||||
|
border: solid 2px $color-blue;
|
||||||
|
margin: $gap;
|
||||||
|
white-space: nowrap;
|
||||||
|
width: 20rem;
|
||||||
|
position: relative;
|
||||||
|
height: 3.6rem;
|
||||||
|
|
||||||
|
&-field {
|
||||||
|
position: absolute;
|
||||||
|
background-color: $color-white;
|
||||||
|
margin-top: $gap * 2;
|
||||||
|
padding: $gap;
|
||||||
|
left: -0.1rem;
|
||||||
|
border: solid 1px $color-gray-light;
|
||||||
|
width: 20rem;
|
||||||
|
z-index: 3;
|
||||||
|
|
||||||
|
.usa-input {
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
li {
|
||||||
|
background-color: $color-white;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.unassigned {
|
||||||
|
border: solid 1px $color-gray-light;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-link {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.environment-role__no-user {
|
||||||
|
margin: $gap;
|
||||||
|
padding: ($gap / 2) $gap;
|
||||||
font-weight: $font-normal;
|
font-weight: $font-normal;
|
||||||
color: $color-gray-medium;
|
height: 3.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.application-list-item {
|
.application-list-item {
|
||||||
|
@ -64,8 +64,9 @@
|
|||||||
{% call ToggleSection(section_name="members") %}
|
{% call ToggleSection(section_name="members") %}
|
||||||
<ul>
|
<ul>
|
||||||
{% for member in env['members'] %}
|
{% for member in env['members'] %}
|
||||||
|
{% set status = ": Access Suspended" if member['status'] == 'disabled' %}
|
||||||
<li class="accordion-table__item-toggle-content__expanded">
|
<li class="accordion-table__item-toggle-content__expanded">
|
||||||
{{ member }}
|
{{ member['user_name'] }}{{ status }}
|
||||||
</li>
|
</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -1,7 +1,88 @@
|
|||||||
|
{% from "components/alert.html" import Alert %}
|
||||||
{% from "components/checkbox_input.html" import CheckboxInput %}
|
{% from "components/checkbox_input.html" import CheckboxInput %}
|
||||||
{% from "components/text_input.html" import TextInput %}
|
{% from "components/text_input.html" import TextInput %}
|
||||||
{% from "components/phone_input.html" import PhoneInput %}
|
{% from "components/phone_input.html" import PhoneInput %}
|
||||||
|
|
||||||
|
{% macro EnvRoleInput(sub_form, member_role_id=None) %}
|
||||||
|
{% set role = sub_form.role.data if not sub_form.disabled.data else "Access Suspended" %}
|
||||||
|
{% if sub_form.role.data != "No Access" and not sub_form.disabled.data -%}
|
||||||
|
<checkboxinput
|
||||||
|
name="'{{ sub_form.disabled.name | string }}-{% if member_role_id %}-{{ member_role_id }}{% endif %}'"
|
||||||
|
inline-template
|
||||||
|
key="'{{ sub_form.disabled.name | string }}-{% if member_role_id %}-{{ member_role_id }}{% endif %}'"
|
||||||
|
v-bind:initial-checked='{{ sub_form.disabled.data|string|lower }}'
|
||||||
|
v-bind:optional="true"
|
||||||
|
>
|
||||||
|
<fieldset data-ally-disabled="true" v-on:change="onInput" class="usa-input__choices revoke-button">
|
||||||
|
{% set id = "{}-{}".format(sub_form.disabled.name, member_role_id) %}
|
||||||
|
<div class="form-row" v-if="!isChecked">
|
||||||
|
<div class="form-col form-col--two-thirds">
|
||||||
|
<div class="usa-input__title-inline">
|
||||||
|
{{ sub_form.environment_name.data }}
|
||||||
|
</div>
|
||||||
|
<div class="usa-input__help">
|
||||||
|
{{ role }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-col form-col--third">
|
||||||
|
{{ sub_form.disabled(id=id, checked=True, **{"v-model": "isChecked"}) }}
|
||||||
|
{{ sub_form.disabled.label(for=id, class="usa-button button-danger-outline") | safe }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
{% call Alert(level='warning') %}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-col form-col--two-thirds">
|
||||||
|
<div class="usa-input__title-inline">
|
||||||
|
{{ sub_form.environment_name.data }}
|
||||||
|
</div>
|
||||||
|
<p class="usa-input__help">
|
||||||
|
{{ "portfolios.applications.members.form.env_access.revoke_warning" | translate | safe }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="form-col form-col--third">
|
||||||
|
{{ sub_form.disabled(id=id, checked=True, **{"v-model": "isChecked"}) }}
|
||||||
|
<label for="{{ id }}" class="link">Undo</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endcall %}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</checkboxinput>
|
||||||
|
{% else %}
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-col form-col--two-thirds">
|
||||||
|
<div class="usa-input {% if sub_form.disabled.data or sub_form.role.data == 'No Access' %}env-role__no-access{% endif %}">
|
||||||
|
<div class='usa-input__title-inline'>
|
||||||
|
{{ sub_form.environment_name.data }}
|
||||||
|
</div>
|
||||||
|
<p class="usa-input__help">
|
||||||
|
{{ role }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-col form-col--third">
|
||||||
|
{% if sub_form.role.data == "No Access" and not sub_form.disabled.data -%}
|
||||||
|
<optionsinput inline-template
|
||||||
|
v-bind:initial-value="'{{ sub_form.role.data | string }}'"
|
||||||
|
v-bind:name="'{{ sub_form.name | string }}{% if member_role_id %}-{{ member_role_id }}{% endif %}'"
|
||||||
|
v-bind:optional="true"
|
||||||
|
v-bind:watch="true">
|
||||||
|
<fieldset data-ally-disabled="true" v-on:change="onInput" class="usa-input__choices">
|
||||||
|
{{ sub_form.role(**{"v-model": "value", "id": "{}-{}".format(sub_form.role.name, member_role_id)}) }}
|
||||||
|
</fieldset>
|
||||||
|
</optionsinput>
|
||||||
|
{% elif sub_form.disabled.data -%}
|
||||||
|
<p class="usa-input__help">
|
||||||
|
{{ "portfolios.applications.members.form.env_access.suspended" | translate }}
|
||||||
|
</p>
|
||||||
|
{%- endif %}
|
||||||
|
{{ sub_form.environment_id() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
{% macro PermsFields(form, new=False, member_role_id=None) %}
|
{% macro PermsFields(form, new=False, member_role_id=None) %}
|
||||||
<h2>{{ "portfolios.applications.members.form.app_perms.title" | translate }}</h2>
|
<h2>{{ "portfolios.applications.members.form.app_perms.title" | translate }}</h2>
|
||||||
<p class='usa-input__help subtitle'>{{ "portfolios.applications.members.form.app_perms.description" | translate | safe}}</p>
|
<p class='usa-input__help subtitle'>{{ "portfolios.applications.members.form.app_perms.description" | translate | safe}}</p>
|
||||||
@ -23,32 +104,15 @@
|
|||||||
<hr>
|
<hr>
|
||||||
<div class="environment_roles environment-roles-new">
|
<div class="environment_roles environment-roles-new">
|
||||||
<h2>{{ "portfolios.applications.members.form.env_access.title" | translate }}</h2>
|
<h2>{{ "portfolios.applications.members.form.env_access.title" | translate }}</h2>
|
||||||
<p class='usa-input__help subtitle'>{{ "portfolios.applications.members.form.env_access.description" | translate | safe }}</p>
|
<p class='usa-input__help subtitle'>
|
||||||
|
{% if not new -%}
|
||||||
|
{{ "portfolios.applications.members.form.env_access.edit_description" | translate }}
|
||||||
|
{%- endif %}
|
||||||
|
{{ "portfolios.applications.members.form.env_access.description" | translate | safe }}
|
||||||
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
{% for environment_data in form.environment_roles %}
|
{% for environment_data in form.environment_roles %}
|
||||||
<optionsinput inline-template
|
{{ EnvRoleInput(environment_data, member_role_id) }}
|
||||||
v-bind:initial-value="'{{ environment_data.role.data | string }}'"
|
|
||||||
v-bind:name="'{{ environment_data.name | string }}'"
|
|
||||||
v-bind:optional="true"
|
|
||||||
v-bind:watch="true">
|
|
||||||
<div class="usa-input">
|
|
||||||
<fieldset data-ally-disabled="true" v-on:change="onInput" class="usa-input__choices">
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-col form-col--two-thirds">
|
|
||||||
<legend>
|
|
||||||
<div v-bind:class='["usa-input__title-inline", {"environment-name--gray": value === "None" }]'>
|
|
||||||
{{ environment_data.environment_name.data }}
|
|
||||||
</div>
|
|
||||||
</legend>
|
|
||||||
</div>
|
|
||||||
<div class="form-col form-col--third">
|
|
||||||
{{ environment_data.role(**{"v-model": "value"}) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
</optionsinput>
|
|
||||||
{{ environment_data.environment_id() }}
|
|
||||||
<hr>
|
<hr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
{% set modal_name = "edit_member-{}".format(loop.index) %}
|
{% set modal_name = "edit_member-{}".format(loop.index) %}
|
||||||
{% call Modal(modal_name, classes="form-content--app-mem") %}
|
{% call Modal(modal_name, classes="form-content--app-mem") %}
|
||||||
<div class="modal__form--header">
|
<div class="modal__form--header">
|
||||||
<h1>{{ Icon('avatar') }} {{ member.user_name }}</h1>
|
<h1>{{ Icon('avatar') }} {{ "portfolios.applications.members.form.edit_access_header" | translate({ "user": member.user_name }) }}</h1>
|
||||||
<hr>
|
<hr>
|
||||||
</div>
|
</div>
|
||||||
<base-form inline-template>
|
<base-form inline-template>
|
||||||
|
@ -45,6 +45,6 @@
|
|||||||
{% endset %}
|
{% endset %}
|
||||||
|
|
||||||
{% call MemberFormTemplate(next_button=next_button) %}
|
{% call MemberFormTemplate(next_button=next_button) %}
|
||||||
{{ member_fields.PermsFields(form=member_form) }}
|
{{ member_fields.PermsFields(form=member_form, new=True) }}
|
||||||
{% endcall %}
|
{% endcall %}
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from atst.domain.environment_roles import EnvironmentRoles
|
from atst.domain.environment_roles import EnvironmentRoles
|
||||||
|
from atst.models.environment_role import EnvironmentRole
|
||||||
|
|
||||||
from tests.factories import *
|
from tests.factories import *
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ def test_get_by_user_and_environment(application_role, environment):
|
|||||||
|
|
||||||
|
|
||||||
def test_delete(application_role, environment, monkeypatch):
|
def test_delete(application_role, environment, monkeypatch):
|
||||||
EnvironmentRoleFactory.create(
|
env_role = EnvironmentRoleFactory.create(
|
||||||
application_role=application_role, environment=environment
|
application_role=application_role, environment=environment
|
||||||
)
|
)
|
||||||
assert EnvironmentRoles.delete(application_role.id, environment.id)
|
assert EnvironmentRoles.delete(application_role.id, environment.id)
|
||||||
@ -88,3 +89,14 @@ def test_disable_completed(application_role, environment):
|
|||||||
environment_role = EnvironmentRoles.disable(environment_role.id)
|
environment_role = EnvironmentRoles.disable(environment_role.id)
|
||||||
|
|
||||||
assert environment_role.status == EnvironmentRole.Status.DISABLED
|
assert environment_role.status == EnvironmentRole.Status.DISABLED
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_for_update(application_role, environment):
|
||||||
|
EnvironmentRoleFactory.create(
|
||||||
|
application_role=application_role, environment=environment, deleted=True
|
||||||
|
)
|
||||||
|
role = EnvironmentRoles.get_for_update(application_role.id, environment.id)
|
||||||
|
assert role
|
||||||
|
assert role.application_role == application_role
|
||||||
|
assert role.environment == environment
|
||||||
|
assert role.deleted
|
||||||
|
@ -4,7 +4,7 @@ from uuid import uuid4
|
|||||||
|
|
||||||
from atst.domain.environments import Environments
|
from atst.domain.environments import Environments
|
||||||
from atst.domain.environment_roles import EnvironmentRoles
|
from atst.domain.environment_roles import EnvironmentRoles
|
||||||
from atst.domain.exceptions import NotFoundError
|
from atst.domain.exceptions import NotFoundError, DisabledError
|
||||||
from atst.models.environment_role import CSPRole, EnvironmentRole
|
from atst.models.environment_role import CSPRole, EnvironmentRole
|
||||||
|
|
||||||
from tests.factories import (
|
from tests.factories import (
|
||||||
@ -28,8 +28,7 @@ def test_create_environments():
|
|||||||
def test_update_env_role():
|
def test_update_env_role():
|
||||||
env_role = EnvironmentRoleFactory.create(role=CSPRole.BASIC_ACCESS.value)
|
env_role = EnvironmentRoleFactory.create(role=CSPRole.BASIC_ACCESS.value)
|
||||||
new_role = CSPRole.TECHNICAL_READ.value
|
new_role = CSPRole.TECHNICAL_READ.value
|
||||||
|
Environments.update_env_role(
|
||||||
assert Environments.update_env_role(
|
|
||||||
env_role.environment, env_role.application_role, new_role
|
env_role.environment, env_role.application_role, new_role
|
||||||
)
|
)
|
||||||
assert env_role.role == new_role
|
assert env_role.role == new_role
|
||||||
@ -37,23 +36,27 @@ def test_update_env_role():
|
|||||||
|
|
||||||
def test_update_env_role_no_access():
|
def test_update_env_role_no_access():
|
||||||
env_role = EnvironmentRoleFactory.create(role=CSPRole.BASIC_ACCESS.value)
|
env_role = EnvironmentRoleFactory.create(role=CSPRole.BASIC_ACCESS.value)
|
||||||
|
Environments.update_env_role(env_role.environment, env_role.application_role, None)
|
||||||
assert Environments.update_env_role(
|
|
||||||
env_role.environment, env_role.application_role, None
|
|
||||||
)
|
|
||||||
|
|
||||||
assert not EnvironmentRoles.get(
|
assert not EnvironmentRoles.get(
|
||||||
env_role.application_role.id, env_role.environment.id
|
env_role.application_role.id, env_role.environment.id
|
||||||
)
|
)
|
||||||
|
assert env_role.role is None
|
||||||
|
assert env_role.status == EnvironmentRole.Status.DISABLED
|
||||||
|
|
||||||
|
|
||||||
def test_update_env_role_no_change():
|
def test_update_env_role_disabled_role():
|
||||||
env_role = EnvironmentRoleFactory.create(role=CSPRole.BASIC_ACCESS.value)
|
env_role = EnvironmentRoleFactory.create(role=CSPRole.BASIC_ACCESS.value)
|
||||||
new_role = CSPRole.BASIC_ACCESS.value
|
Environments.update_env_role(env_role.environment, env_role.application_role, None)
|
||||||
|
|
||||||
assert not Environments.update_env_role(
|
with pytest.raises(DisabledError):
|
||||||
env_role.environment, env_role.application_role, new_role
|
Environments.update_env_role(
|
||||||
|
env_role.environment,
|
||||||
|
env_role.application_role,
|
||||||
|
CSPRole.TECHNICAL_READ.value,
|
||||||
)
|
)
|
||||||
|
assert env_role.role is None
|
||||||
|
assert env_role.status == EnvironmentRole.Status.DISABLED
|
||||||
|
|
||||||
|
|
||||||
def test_get_excludes_deleted():
|
def test_get_excludes_deleted():
|
||||||
|
@ -10,6 +10,7 @@ def test_environment_form():
|
|||||||
"environment_id": 123,
|
"environment_id": 123,
|
||||||
"environment_name": "testing",
|
"environment_name": "testing",
|
||||||
"role": ENV_ROLES[0][0],
|
"role": ENV_ROLES[0][0],
|
||||||
|
"disabled": True,
|
||||||
}
|
}
|
||||||
form = EnvironmentForm(data=form_data)
|
form = EnvironmentForm(data=form_data)
|
||||||
assert form.validate()
|
assert form.validate()
|
||||||
@ -24,6 +25,7 @@ def test_environment_form_default_no_access():
|
|||||||
"environment_id": 123,
|
"environment_id": 123,
|
||||||
"environment_name": "testing",
|
"environment_name": "testing",
|
||||||
"role": None,
|
"role": None,
|
||||||
|
"disabled": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -129,10 +129,14 @@ def test_edit_application_environments_obj(app, client, user_session):
|
|||||||
assert env_obj["name"] == env.name
|
assert env_obj["name"] == env.name
|
||||||
assert env_obj["id"] == env.id
|
assert env_obj["id"] == env.id
|
||||||
assert isinstance(env_obj["edit_form"], EditEnvironmentForm)
|
assert isinstance(env_obj["edit_form"], EditEnvironmentForm)
|
||||||
assert (
|
assert {
|
||||||
env_obj["members"].sort()
|
"user_name": app_role1.user_name,
|
||||||
== [app_role1.user_name, app_role2.user_name].sort()
|
"status": env_role1.status.value,
|
||||||
)
|
} in env_obj["members"]
|
||||||
|
assert {
|
||||||
|
"user_name": app_role2.user_name,
|
||||||
|
"status": env_role2.status.value,
|
||||||
|
} in env_obj["members"]
|
||||||
assert isinstance(context["audit_events"], Paginator)
|
assert isinstance(context["audit_events"], Paginator)
|
||||||
|
|
||||||
|
|
||||||
@ -503,10 +507,10 @@ def test_update_member(client, user_session, session):
|
|||||||
env_1 = EnvironmentFactory.create(application=application)
|
env_1 = EnvironmentFactory.create(application=application)
|
||||||
env_2 = EnvironmentFactory.create(application=application)
|
env_2 = EnvironmentFactory.create(application=application)
|
||||||
# add user to two of the environments: env and env_1
|
# add user to two of the environments: env and env_1
|
||||||
EnvironmentRoleFactory.create(
|
updated_role = EnvironmentRoleFactory.create(
|
||||||
environment=env, application_role=app_role, role=CSPRole.BASIC_ACCESS.value
|
environment=env, application_role=app_role, role=CSPRole.BASIC_ACCESS.value
|
||||||
)
|
)
|
||||||
EnvironmentRoleFactory.create(
|
suspended_role = EnvironmentRoleFactory.create(
|
||||||
environment=env_1, application_role=app_role, role=CSPRole.BASIC_ACCESS.value
|
environment=env_1, application_role=app_role, role=CSPRole.BASIC_ACCESS.value
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -524,8 +528,8 @@ def test_update_member(client, user_session, session):
|
|||||||
"environment_roles-0-role": CSPRole.TECHNICAL_READ.value,
|
"environment_roles-0-role": CSPRole.TECHNICAL_READ.value,
|
||||||
"environment_roles-0-environment_name": env.name,
|
"environment_roles-0-environment_name": env.name,
|
||||||
"environment_roles-1-environment_id": env_1.id,
|
"environment_roles-1-environment_id": env_1.id,
|
||||||
"environment_roles-1-role": NO_ACCESS,
|
|
||||||
"environment_roles-1-environment_name": env_1.name,
|
"environment_roles-1-environment_name": env_1.name,
|
||||||
|
"environment_roles-1-disabled": "True",
|
||||||
"environment_roles-2-environment_id": env_2.id,
|
"environment_roles-2-environment_id": env_2.id,
|
||||||
"environment_roles-2-role": CSPRole.NETWORK_ADMIN.value,
|
"environment_roles-2-role": CSPRole.NETWORK_ADMIN.value,
|
||||||
"environment_roles-2-environment_name": env_2.name,
|
"environment_roles-2-environment_name": env_2.name,
|
||||||
@ -556,11 +560,10 @@ def test_update_member(client, user_session, session):
|
|||||||
)
|
)
|
||||||
|
|
||||||
environment_roles = application.roles[0].environment_roles
|
environment_roles = application.roles[0].environment_roles
|
||||||
# make sure that old env role was deleted and there are only 2 env roles
|
|
||||||
assert len(environment_roles) == 2
|
|
||||||
# check that the user has roles in the correct envs
|
# check that the user has roles in the correct envs
|
||||||
assert environment_roles[0].environment in [env, env_2]
|
assert len(environment_roles) == 3
|
||||||
assert environment_roles[1].environment in [env, env_2]
|
assert updated_role.role == CSPRole.TECHNICAL_READ.value
|
||||||
|
assert suspended_role.status == EnvironmentRole.Status.DISABLED
|
||||||
|
|
||||||
|
|
||||||
def test_revoke_invite(client, user_session):
|
def test_revoke_invite(client, user_session):
|
||||||
|
@ -404,11 +404,17 @@ portfolios:
|
|||||||
env_access:
|
env_access:
|
||||||
title: Environment Roles
|
title: Environment Roles
|
||||||
table_header: Environment Access
|
table_header: Environment Access
|
||||||
|
edit_description: Add or revoke Environment access.
|
||||||
description: Additional role controls are available in the CSP console. <a href="#"> Learn More </a>
|
description: Additional role controls are available in the CSP console. <a href="#"> Learn More </a>
|
||||||
|
revoke_warning: Save changes to revoke access, <strong>this can not be undone.</strong>
|
||||||
|
suspended: Suspended access cannot be modified.
|
||||||
next_button: "Next: Permissions"
|
next_button: "Next: Permissions"
|
||||||
app_perms:
|
app_perms:
|
||||||
title: Application Permissions
|
title: Application Permissions
|
||||||
description: Application permissions allow users to provision and modify applications and teams. <a href="#"> Learn More </a>
|
description: Application permissions allow users to provision and modify applications and teams. <a href="#"> Learn More </a>
|
||||||
|
edit_access_header: "Manage {user}'s Access"
|
||||||
|
env_access_text: "Add or revoke Environment access. Additional controls are available in the CSP console."
|
||||||
|
step_2_title: Set Permissions and Roles
|
||||||
add_member: Add Member
|
add_member: Add Member
|
||||||
menu:
|
menu:
|
||||||
edit: Edit Roles and Permissions
|
edit: Edit Roles and Permissions
|
||||||
|
Loading…
x
Reference in New Issue
Block a user