Merge pull request #1070 from dod-ccpo/edit-app-member-modal

Edit app member modal
This commit is contained in:
leigh-mil 2019-09-18 11:39:33 -04:00 committed by GitHub
commit 71befc96ef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 354 additions and 159 deletions

View File

@ -3,7 +3,7 @@
"files": "^.secrets.baseline$", "files": "^.secrets.baseline$",
"lines": null "lines": null
}, },
"generated_at": "2019-09-10T18:56:49Z", "generated_at": "2019-09-13T17:44:56Z",
"plugins_used": [ "plugins_used": [
{ {
"base64_limit": 4.5, "base64_limit": 4.5,
@ -194,7 +194,7 @@
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207", "hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
"is_secret": false, "is_secret": false,
"is_verified": false, "is_verified": false,
"line_number": 507, "line_number": 525,
"type": "Hex High Entropy String" "type": "Hex High Entropy String"
} }
] ]

View File

@ -1,15 +1,15 @@
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms.fields import FormField, FieldList, HiddenField, BooleanField from wtforms.fields import FormField, FieldList, HiddenField, BooleanField
from wtforms import Form
from .forms import BaseForm
from .member import NewForm as BaseNewMemberForm from .member import NewForm as BaseNewMemberForm
from .data import ENV_ROLES, ENV_ROLE_NO_ACCESS as NO_ACCESS from .data import ENV_ROLES, ENV_ROLE_NO_ACCESS as NO_ACCESS
from atst.domain.permission_sets import PermissionSets
from atst.forms.fields import SelectField from atst.forms.fields import SelectField
from atst.domain.permission_sets import PermissionSets
from atst.utils.localization import translate from atst.utils.localization import translate
class EnvironmentForm(FlaskForm): class EnvironmentForm(Form):
environment_id = HiddenField() environment_id = HiddenField()
environment_name = HiddenField() environment_name = HiddenField()
role = SelectField( role = SelectField(
@ -53,6 +53,7 @@ class PermissionsForm(FlaskForm):
@property @property
def data(self): def data(self):
_data = super().data _data = super().data
_data.pop("csrf_token", None)
perm_sets = [] perm_sets = []
if _data["perms_env_mgmt"]: if _data["perms_env_mgmt"]:
@ -64,10 +65,14 @@ class PermissionsForm(FlaskForm):
if _data["perms_del_env"]: if _data["perms_del_env"]:
perm_sets.append(PermissionSets.DELETE_APPLICATION_ENVIRONMENTS) perm_sets.append(PermissionSets.DELETE_APPLICATION_ENVIRONMENTS)
return perm_sets _data["permission_sets"] = perm_sets
return _data
class NewForm(BaseForm): class NewForm(PermissionsForm):
user_data = FormField(BaseNewMemberForm) user_data = FormField(BaseNewMemberForm)
permission_sets = FormField(PermissionsForm) environment_roles = FieldList(FormField(EnvironmentForm))
class UpdateMemberForm(PermissionsForm):
environment_roles = FieldList(FormField(EnvironmentForm)) environment_roles = FieldList(FormField(EnvironmentForm))

View File

@ -9,10 +9,9 @@ from atst.domain.audit_log import AuditLog
from atst.domain.common import Paginator from atst.domain.common import Paginator
from atst.domain.environment_roles import EnvironmentRoles from atst.domain.environment_roles import EnvironmentRoles
from atst.forms.application import ApplicationForm, EditEnvironmentForm from atst.forms.application import ApplicationForm, EditEnvironmentForm
from atst.forms.application_member import NewForm as NewMemberForm from atst.forms.application_member import NewForm as NewMemberForm, UpdateMemberForm
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.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 CSPRole
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
@ -35,79 +34,24 @@ def get_environments_obj_for_app(application):
return environments_obj return environments_obj
def data_for_app_env_roles_form(application): def filter_perm_sets_data(member):
csp_roles = [role.value for role in CSPRole] perm_sets_data = {
csp_roles.insert(0, NO_ACCESS) "perms_team_mgmt": bool(
# dictionary for sorting application members by environments member.has_permission_set(PermissionSets.EDIT_APPLICATION_TEAM)
# and roles within those environments
environments_dict = {
e.id: {role_name: [] for role_name in csp_roles}
for e in application.environments
}
for member in application.members:
env_ids = set(environments_dict.keys())
for env_role in member.environment_roles:
role_members_list = environments_dict[env_role.environment_id][
env_role.role
]
role_members_list.append(
{
"application_role_id": str(member.id),
"user_name": member.user_name,
"role_name": env_role.role,
}
)
env_ids.remove(env_role.environment_id)
# any leftover environment IDs are ones the app member
# does not have access to
for env_id in env_ids:
role_members_list = environments_dict[env_id][NO_ACCESS]
role_members_list.append(
{
"application_role_id": str(member.id),
"user_name": member.user_name,
"role_name": NO_ACCESS,
}
)
# transform the data into the shape the form needs
nested_data = [
{
"env_id": env_id,
"team_roles": [
{"role": role, "members": members} for role, members in roles.items()
],
}
for env_id, roles in environments_dict.items()
]
return {"envs": nested_data}
def get_form_permission_value(member, edit_perm_set):
if member.has_permission_set(edit_perm_set):
return edit_perm_set
else:
return PermissionSets.VIEW_APPLICATION
def get_members_data(application):
members_data = []
for member in application.members:
permission_sets = {
"perms_team_mgmt": get_form_permission_value(
member, PermissionSets.EDIT_APPLICATION_TEAM
), ),
"perms_env_mgmt": get_form_permission_value( "perms_env_mgmt": bool(
member, PermissionSets.EDIT_APPLICATION_ENVIRONMENTS member.has_permission_set(PermissionSets.EDIT_APPLICATION_ENVIRONMENTS)
), ),
"perms_del_env": get_form_permission_value( "perms_del_env": bool(
member, PermissionSets.DELETE_APPLICATION_ENVIRONMENTS member.has_permission_set(PermissionSets.DELETE_APPLICATION_ENVIRONMENTS)
), ),
} }
roles = EnvironmentRoles.get_for_application_member(member.id)
environment_roles = [ return perm_sets_data
def filter_env_roles_data(roles):
env_role_data = [
{ {
"environment_id": str(role.environment.id), "environment_id": str(role.environment.id),
"environment_name": role.environment.name, "environment_name": role.environment.name,
@ -115,6 +59,39 @@ def get_members_data(application):
} }
for role in roles for role in roles
] ]
return env_role_data
def filter_env_roles_form_data(member, environments):
env_roles_form_data = []
for env in environments:
env_data = {
"environment_id": str(env.id),
"environment_name": env.name,
"role": NO_ACCESS,
}
env_role = EnvironmentRoles.get_by_user_and_environment(member.user_id, env.id)
if env_role:
env_data["role"] = env_role.role
env_roles_form_data.append(env_data)
return env_roles_form_data
def get_members_data(application):
members_data = []
for member in application.members:
permission_sets = filter_perm_sets_data(member)
roles = EnvironmentRoles.get_for_application_member(member.id)
environment_roles = filter_env_roles_data(roles)
env_roles_form_data = filter_env_roles_form_data(
member, application.environments
)
form = UpdateMemberForm(
environment_roles=env_roles_form_data, **permission_sets
)
members_data.append( members_data.append(
{ {
"role_id": member.id, "role_id": member.id,
@ -122,6 +99,7 @@ def get_members_data(application):
"permission_sets": permission_sets, "permission_sets": permission_sets,
"environment_roles": environment_roles, "environment_roles": environment_roles,
"role_status": member.status.value, "role_status": member.status.value,
"form": form,
} }
) )
@ -312,7 +290,7 @@ def create_member(application_id):
application=application, application=application,
inviter=g.current_user, inviter=g.current_user,
user_data=form.user_data.data, user_data=form.user_data.data,
permission_sets_names=form.permission_sets.data, permission_sets_names=form.data["permission_sets"],
environment_roles_data=form.environment_roles.data, environment_roles_data=form.environment_roles.data,
) )
@ -365,3 +343,34 @@ def remove_member(application_id, application_role_id):
fragment="application-members", fragment="application-members",
) )
) )
@applications_bp.route(
"/applications/<application_id>/members/<application_role_id>/update",
methods=["POST"],
)
@user_can(Permissions.EDIT_APPLICATION_MEMBER, message="update application member")
def update_member(application_id, application_role_id):
app_role = ApplicationRoles.get_by_id(application_role_id)
form = UpdateMemberForm(http_request.form)
if form.validate():
ApplicationRoles.update_permission_sets(app_role, form.data["permission_sets"])
for env_role in form.environment_roles:
environment = Environments.get(env_role.environment_id.data)
Environments.update_env_role(environment, app_role, env_role.data["role"])
flash("application_member_updated", user_name=app_role.user_name)
else:
pass
# TODO: flash error message
return redirect(
url_for(
"applications.settings",
application_id=application_id,
fragment="application-members",
_anchor="application-members",
)
)

View File

@ -7,11 +7,6 @@ MESSAGES = {
"message_template": "Portfolio '{{portfolio_name}}' has been deleted", "message_template": "Portfolio '{{portfolio_name}}' has been deleted",
"category": "success", "category": "success",
}, },
"application_environment_members_updated": {
"title_template": "Application environment members updated",
"message_template": "Application environment members have been updated",
"category": "success",
},
"application_deleted": { "application_deleted": {
"title_template": translate("flash.success"), "title_template": translate("flash.success"),
"message_template": """ "message_template": """
@ -30,6 +25,11 @@ MESSAGES = {
"message_template": "You have successfully deleted {{ user_name }} from {{ application_name }}", "message_template": "You have successfully deleted {{ user_name }} from {{ application_name }}",
"category": "success", "category": "success",
}, },
"application_member_updated": {
"title_template": "Team member updated",
"message_template": "You have successfully updated the permissions for {{ user_name }}",
"category": "success",
},
"ccpo_user_added": { "ccpo_user_added": {
"title_template": translate("flash.success"), "title_template": translate("flash.success"),
"message_template": "You have successfully given {{ user_name }} CCPO permissions.", "message_template": "You have successfully given {{ user_name }} CCPO permissions.",

View File

@ -6,6 +6,7 @@ export default {
props: { props: {
name: String, name: String,
initialChecked: Boolean, initialChecked: Boolean,
optional: Boolean,
}, },
data: function() { data: function() {
@ -18,7 +19,7 @@ export default {
emitEvent('field-mount', this, { emitEvent('field-mount', this, {
optional: this.optional, optional: this.optional,
name: this.name, name: this.name,
valid: this.isChecked, valid: this.optional || this.isChecked,
}) })
}, },
@ -27,7 +28,7 @@ export default {
emitEvent('field-change', this, { emitEvent('field-change', this, {
value: e.target.checked, value: e.target.checked,
name: this.name, name: this.name,
valid: this.isChecked, valid: this.optional || this.isChecked,
}) })
}, },
}, },

View File

@ -24,7 +24,8 @@
} }
} }
#modal--add-app-mem { #modal--add-app-mem,
.form-content--app-mem {
text-align: left; text-align: left;
input[type="checkbox"] + label::before { input[type="checkbox"] + label::before {
@ -98,11 +99,19 @@
margin-top: $gap; margin-top: $gap;
margin-left: $gap; margin-left: $gap;
} }
.form-row {
margin: 0;
}
} }
&__head { &__head {
font-weight: $font-bold; font-weight: $font-bold;
} }
hr {
margin: 0.5em;
}
} }
.form-row { .form-row {

View File

@ -2,20 +2,24 @@
field, field,
label=field.label, label=field.label,
inline=False, inline=False,
classes="") -%} classes="",
key=field.name,
id=field.name,
optional=False) -%}
<checkboxinput <checkboxinput
name='{{ field.name }}' name='{{ field.name }}'
inline-template inline-template
key='{{ field.name }}' key='{{ key }}'
v-bind:initial-checked='{{ field.data|string|lower }}' v-bind:initial-checked='{{ field.data|string|lower }}'
v-bind:optional={{ optional|lower }}
> >
<div> <div>
<div class='usa-input {{ classes }} {% if field.errors %}usa-input--error{% endif %}' v-bind:class="[{ 'checked': isChecked }]"> <div class='usa-input {{ classes }} {% if field.errors %}usa-input--error{% endif %}' v-bind:class="[{ 'checked': isChecked }]">
<fieldset data-ally-disabled="true" v-on:change="onInput" class="usa-input__choices {% if inline %}usa-input__choices--inline{% endif %}"> <fieldset data-ally-disabled="true" v-on:change="onInput" class="usa-input__choices {% if inline %}usa-input__choices--inline{% endif %}">
<legend> <legend>
{{ field(**{"v-model": "isChecked"}) }} {{ field(id=id, checked=True, **{"v-model": "isChecked"}) }}
{{ label | safe }} {{ field.label(for=id) | safe }}
{% if field.description %} {% if field.description %}
<p class='usa-input__help'> <p class='usa-input__help'>

View File

@ -0,0 +1,52 @@
{% from "components/checkbox_input.html" import CheckboxInput %}
{% macro MemberPermsFields(form, new=False, member_role_id=None) %}
<div class="form-content--app-mem">
<h4>{{ "portfolios.applications.members.form.project_perms" | translate }}</h4>
<div class="application-perms">
{% if new %}
{% set team_mgmt = form.perms_team_mgmt.name %}
{% set env_mgmt = form.perms_env_mgmt.name %}
{% set del_env = form.perms_del_env.name %}
{% else %}
{% set team_mgmt = "perms_team_mgmt-{}".format(member_role_id) %}
{% set env_mgmt = "perms_env_mgmt-{}".format(member_role_id) %}
{% set del_env = "perms_del_env-{}".format(member_role_id) %}
{% endif %}
{{ CheckboxInput(form.perms_team_mgmt, classes="input__inline-fields", key=team_mgmt, id=team_mgmt, optional=True) }}
{{ CheckboxInput(form.perms_env_mgmt, classes="input__inline-fields", key=env_mgmt, id=env_mgmt, optional=True) }}
{{ CheckboxInput(form.perms_del_env, classes="input__inline-fields", key=del_env, id=del_env, optional=True) }}
</div>
<div class="environment_roles environment-roles-new">
<h4>{{ "portfolios.applications.members.form.env_access" | translate }}</h4>
<hr>
{% for environment_data in form.environment_roles %}
<optionsinput inline-template
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>
{% endfor %}
</div>
</div>
{% endmacro %}

View File

@ -2,6 +2,7 @@
{% from "components/text_input.html" import TextInput %} {% from "components/text_input.html" import TextInput %}
{% from "components/checkbox_input.html" import CheckboxInput %} {% from "components/checkbox_input.html" import CheckboxInput %}
{% from "components/phone_input.html" import PhoneInput %} {% from "components/phone_input.html" import PhoneInput %}
{% from "fragments/applications/member_perms_form_fields.html" import MemberPermsFields %}
{% macro MemberFormTemplate(title, next_button, previous=True) %} {% macro MemberFormTemplate(title, next_button, previous=True) %}
<div class="modal__form--header"> <div class="modal__form--header">
@ -24,7 +25,7 @@
</div> </div>
{% endmacro %} {% endmacro %}
{% macro MemberStepOne(new_member_form) %} {% macro MemberStepOne(member_form) %}
{% set next_button %} {% set next_button %}
<input <input
type='button' type='button'
@ -36,24 +37,24 @@
{% call MemberFormTemplate(title="portfolios.applications.members.form.add_member"|translate, next_button=next_button, previous=False) %} {% call MemberFormTemplate(title="portfolios.applications.members.form.add_member"|translate, next_button=next_button, previous=False) %}
<div class='form-row'> <div class='form-row'>
{{ TextInput(new_member_form.user_data.first_name, validation='requiredField', optional=False) }} {{ TextInput(member_form.user_data.first_name, validation='requiredField', optional=False) }}
</div> </div>
<div class='form-row'> <div class='form-row'>
{{ TextInput(new_member_form.user_data.last_name, validation='requiredField', optional=False) }} {{ TextInput(member_form.user_data.last_name, validation='requiredField', optional=False) }}
</div> </div>
<div class='form-row'> <div class='form-row'>
{{ TextInput(new_member_form.user_data.email, validation='email', optional=False) }} {{ TextInput(member_form.user_data.email, validation='email', optional=False) }}
</div> </div>
<div class="form-row"> <div class="form-row">
{{ PhoneInput(new_member_form.user_data.phone_number, new_member_form.user_data.phone_ext)}} {{ PhoneInput(member_form.user_data.phone_number, member_form.user_data.phone_ext)}}
</div> </div>
<div class='form-row'> <div class='form-row'>
{{ TextInput(new_member_form.user_data.dod_id, validation='dodId', optional=False) }} {{ TextInput(member_form.user_data.dod_id, validation='dodId', optional=False) }}
</div> </div>
<a href="#">How do I find the DoD ID?</a> <a href="#">How do I find the DoD ID?</a>
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}
{% macro MemberStepTwo(new_member_form, application) %} {% macro MemberStepTwo(member_form, application) %}
{% set next_button %} {% set next_button %}
<input <input
type="submit" type="submit"
@ -63,41 +64,6 @@
{% endset %} {% endset %}
{% call MemberFormTemplate(title="portfolios.applications.members.form.step_2_title"|translate, next_button=next_button) %} {% call MemberFormTemplate(title="portfolios.applications.members.form.step_2_title"|translate, next_button=next_button) %}
<h4>{{ "portfolios.applications.members.form.project_perms" | translate }}</h4> {{ MemberPermsFields(form=member_form) }}
<div class="application-perms">
{{ CheckboxInput(new_member_form.permission_sets.perms_team_mgmt, classes="input__inline-fields") }}
{{ CheckboxInput(new_member_form.permission_sets.perms_env_mgmt, classes="input__inline-fields") }}
{{ CheckboxInput(new_member_form.permission_sets.perms_del_env, classes="input__inline-fields") }}
</div>
<div class="environment-roles-new">
<h4>{{ "portfolios.applications.members.form.env_access" | translate }}</h4>
<hr>
{% for environment_data in new_member_form.environment_roles %}
<optionsinput inline-template
v-bind:initial-value="'{{ environment_data.role.data | string }}'"
v-bind:name="'{{ environment_data.name | string }}'"
v-bind:optional="true"
>
<div class="usa-input">
<fieldset data-ally-disabled="true" 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>
{% endfor %}
</div>
{% endcall %} {% endcall %}
{% endmacro %} {% endmacro %}

View File

@ -4,12 +4,17 @@
{% from "components/delete_confirmation.html" import DeleteConfirmation %} {% from "components/delete_confirmation.html" import DeleteConfirmation %}
{% from "components/icon.html" import Icon %} {% from "components/icon.html" import Icon %}
{% import "fragments/applications/new_member_modal_content.html" as member_steps %} {% import "fragments/applications/new_member_modal_content.html" as member_steps %}
{% from "fragments/applications/member_perms_form_fields.html" import MemberPermsFields %}
{% from "components/modal.html" import Modal %} {% from "components/modal.html" import Modal %}
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %} {% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
{% from "components/pagination.html" import Pagination %} {% from "components/pagination.html" import Pagination %}
{% from "components/save_button.html" import SaveButton %} {% from "components/save_button.html" import SaveButton %}
{% from "components/text_input.html" import TextInput %} {% from "components/text_input.html" import TextInput %}
{% from "components/toggle_list.html" import ToggleButton, ToggleSection %} {% from "components/toggle_list.html" import ToggleButton, ToggleSection %}
{% from "components/icon.html" import Icon %}
{% from "components/text_input.html" import TextInput %}
{% from "components/checkbox_input.html" import CheckboxInput %}
{% from "components/phone_input.html" import PhoneInput %}
{% set secondary_breadcrumb = 'portfolios.applications.existing_application_title' | translate({ "application_name": application.name }) %} {% set secondary_breadcrumb = 'portfolios.applications.existing_application_title' | translate({ "application_name": application.name }) %}
@ -115,6 +120,25 @@
{% if g.matchesPath("application-members") %} {% if g.matchesPath("application-members") %}
{% include "fragments/flash.html" %} {% include "fragments/flash.html" %}
{% endif %} {% endif %}
{% for member in members %}
{% set modal_name = "edit_member-{}".format(loop.index) %}
{% call Modal(modal_name) %}
<div class="modal__form--header">
<h1>{{ Icon('avatar') }} {{ member.user_name }}</h1>
<hr>
</div>
<base-form inline-template>
<form id='{{ modal_name }}' method="POST" action="{{ url_for('applications.update_member', application_id=application.id, application_role_id=member.role_id) }}">
{{ member.form.csrf_token }}
{{ MemberPermsFields(form=member.form, member_role_id=member.role_id) }}
<div class="action-group">
{{ SaveButton(text='Update', element='input', additional_classes='action-group__action') }}
<a class='action-group__action usa-button usa-button-secondary' v-on:click="closeModal('{{ modal_name }}')">{{ "common.cancel" | translate }}</a>
</div>
</form>
</base-form>
{% endcall %}
{% endfor %}
<table> <table>
<thead> <thead>
<tr> <tr>
@ -126,12 +150,18 @@
</thead> </thead>
<tbody> <tbody>
{% for member in members %} {% for member in members %}
{% set modal_name = "edit_member-{}".format(loop.index) %}
<tr> <tr>
<td> <td>
{{ member.user_name }}<br> {{ member.user_name }}
<a class="icon-link" v-on:click="openModal('{{ modal_name }}')">
{{ Icon('edit') }}
</a>
<br>
{% if member.role_status == 'pending' %} {% if member.role_status == 'pending' %}
<span class='label label--purple'>INVITE PENDING</span> <span class='label label--purple'>INVITE PENDING</span>
{% endif %} {% endif %}
</td> </td>
<td> <td>

View File

@ -1,5 +1,6 @@
from wtforms.validators import ValidationError from wtforms.validators import ValidationError
from atst.domain.permission_sets import PermissionSets
from atst.forms.data import ENV_ROLES, ENV_ROLE_NO_ACCESS as NO_ACCESS from atst.forms.data import ENV_ROLES, ENV_ROLE_NO_ACCESS as NO_ACCESS
from atst.forms.application_member import * from atst.forms.application_member import *
@ -34,3 +35,16 @@ def test_environment_form_invalid():
} }
form = EnvironmentForm(data=form_data) form = EnvironmentForm(data=form_data)
assert not form.validate() assert not form.validate()
def test_update_member_form():
form_data = {
"perms_team_mgmt": True,
"perms_env_mgmt": False,
"perms_del_env": False,
}
form = UpdateMemberForm(data=form_data)
assert form.validate()
assert form.perms_team_mgmt.data
assert not form.perms_env_mgmt.data
assert not form.perms_del_env.data

View File

@ -6,15 +6,19 @@ from unittest.mock import Mock
from tests.factories import * from tests.factories import *
from atst.domain.applications import Applications from atst.domain.applications import Applications
from atst.domain.application_roles import ApplicationRoles
from atst.domain.environment_roles import EnvironmentRoles from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.environments import Environments from atst.domain.environments import Environments
from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.common import Paginator from atst.domain.common import Paginator
from atst.domain.permission_sets import PermissionSets from atst.domain.permission_sets import PermissionSets
from atst.domain.portfolios import Portfolios from atst.domain.portfolios import Portfolios
from atst.domain.exceptions import NotFoundError from atst.domain.exceptions import NotFoundError
from atst.models.environment_role import CSPRole from atst.models.environment_role import CSPRole
from atst.models.permissions import Permissions
from atst.models.portfolio_role import Status as PortfolioRoleStatus from atst.models.portfolio_role import Status as PortfolioRoleStatus
from atst.forms.application import EditEnvironmentForm from atst.forms.application import EditEnvironmentForm
from atst.forms.application_member import UpdateMemberForm
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 tests.utils import captured_templates from tests.utils import captured_templates
@ -124,27 +128,23 @@ def test_edit_application_environments_obj(app, client, user_session):
assert isinstance(context["audit_events"], Paginator) assert isinstance(context["audit_events"], Paginator)
def test_data_for_app_env_roles_form(app, client, user_session): def test_get_members_data(app, client, user_session):
portfolio = PortfolioFactory.create() user = UserFactory.create()
application = Applications.create( application = ApplicationFactory.create(
portfolio.owner, environments=[
portfolio, {
"Snazzy Application", "name": "testing",
"A new application for me and my friends", "members": [{"user": user, "role_name": CSPRole.BASIC_ACCESS.value}],
{"env"}, }
]
) )
env = application.environments[0] environment = application.environments[0]
app_role0 = ApplicationRoleFactory.create(application=application) app_role = ApplicationRoles.get(user_id=user.id, application_id=application.id)
app_role1 = ApplicationRoleFactory.create(application=application) env_role = EnvironmentRoles.get(
env_role1 = EnvironmentRoleFactory.create( application_role_id=app_role.id, environment_id=environment.id
application_role=app_role1, environment=env, role=CSPRole.BASIC_ACCESS.value
)
app_role2 = ApplicationRoleFactory.create(application=application)
env_role2 = EnvironmentRoleFactory.create(
application_role=app_role2, environment=env, role=CSPRole.NETWORK_ADMIN.value
) )
user_session(portfolio.owner) user_session(application.portfolio.owner)
with captured_templates(app) as templates: with captured_templates(app) as templates:
response = app.test_client().get( response = app.test_client().get(
@ -154,6 +154,24 @@ def test_data_for_app_env_roles_form(app, client, user_session):
assert response.status_code == 200 assert response.status_code == 200
_, context = templates[-1] _, context = templates[-1]
member = context["members"][0]
assert member["role_id"] == app_role.id
assert member["user_name"] == user.full_name
assert member["permission_sets"] == {
"perms_team_mgmt": False,
"perms_env_mgmt": False,
"perms_del_env": False,
}
assert member["environment_roles"] == [
{
"environment_id": str(environment.id),
"environment_name": environment.name,
"role": env_role.role,
}
]
assert member["role_status"]
assert isinstance(member["form"], UpdateMemberForm)
def test_user_with_permission_can_update_application(client, user_session): def test_user_with_permission_can_update_application(client, user_session):
owner = UserFactory.create() owner = UserFactory.create()
@ -361,9 +379,9 @@ def test_create_member(monkeypatch, client, user_session, session):
"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-role": NO_ACCESS,
"environment_roles-1-environment_name": env_1.name, "environment_roles-1-environment_name": env_1.name,
"permission_sets-perms_env_mgmt": True, "perms_env_mgmt": True,
"permission_sets-perms_team_mgmt": True, "perms_team_mgmt": True,
"permission_sets-perms_del_env": True, "perms_del_env": True,
}, },
) )
@ -453,3 +471,72 @@ def test_remove_member_failure(client, user_session):
) )
assert response.status_code == 404 assert response.status_code == 404
def test_update_member(client, user_session):
role = PermissionSets.get(PermissionSets.EDIT_APPLICATION_TEAM)
# create an app role with only edit team perms
app_role = ApplicationRoleFactory.create(permission_sets=[role])
application = app_role.application
env = EnvironmentFactory.create(application=application)
env_1 = EnvironmentFactory.create(application=application)
env_2 = EnvironmentFactory.create(application=application)
# add user to two of the environments: env and env_1
EnvironmentRoleFactory.create(
environment=env, application_role=app_role, role=CSPRole.BASIC_ACCESS.value
)
EnvironmentRoleFactory.create(
environment=env_1, application_role=app_role, role=CSPRole.BASIC_ACCESS.value
)
user_session(application.portfolio.owner)
# update the user's app permissions to have edit team and env perms
# update user's role in env, remove user from env_1, and add user to env_2
response = client.post(
url_for(
"applications.update_member",
application_id=application.id,
application_role_id=app_role.id,
),
data={
"environment_roles-0-environment_id": env.id,
"environment_roles-0-role": CSPRole.TECHNICAL_READ.value,
"environment_roles-0-environment_name": env.name,
"environment_roles-1-environment_id": env_1.id,
"environment_roles-1-role": NO_ACCESS,
"environment_roles-1-environment_name": env_1.name,
"environment_roles-2-environment_id": env_2.id,
"environment_roles-2-role": CSPRole.NETWORK_ADMIN.value,
"environment_roles-2-environment_name": env_2.name,
"perms_env_mgmt": True,
"perms_team_mgmt": True,
"perms_del_env": True,
},
)
assert response.status_code == 302
expected_url = url_for(
"applications.settings",
application_id=application.id,
fragment="application-members",
_anchor="application-members",
_external=True,
)
assert response.location == expected_url
# make sure new application role was not created
assert len(application.roles) == 1
# check that new app perms were added
assert bool(app_role.has_permission_set(PermissionSets.EDIT_APPLICATION_TEAM))
assert bool(
app_role.has_permission_set(PermissionSets.EDIT_APPLICATION_ENVIRONMENTS)
)
assert bool(
app_role.has_permission_set(PermissionSets.DELETE_APPLICATION_ENVIRONMENTS)
)
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
assert environment_roles[0].environment in [env, env_2]
assert environment_roles[1].environment in [env, env_2]

View File

@ -464,6 +464,24 @@ def test_applications_update_environments(post_url_assert_status):
post_url_assert_status(rando, url, 404) post_url_assert_status(rando, url, 404)
# applications.update_member
def test_applications_update_member(post_url_assert_status):
ccpo = UserFactory.create_ccpo()
rando = UserFactory.create()
application_role = ApplicationRoleFactory.create()
application = application_role.application
url = url_for(
"applications.update_member",
application_id=application.id,
application_role_id=application_role.id,
)
post_url_assert_status(application.portfolio.owner, url, 302)
post_url_assert_status(ccpo, url, 302)
post_url_assert_status(rando, url, 404)
# task_orders.download_task_order_pdf # task_orders.download_task_order_pdf
def test_task_orders_download_task_order_pdf_access(get_url_assert_status, monkeypatch): def test_task_orders_download_task_order_pdf_access(get_url_assert_status, monkeypatch):
monkeypatch.setattr( monkeypatch.setattr(

View File

@ -366,14 +366,14 @@ portfolios:
delete_envs: 'Allow member to <strong>delete environments</strong> within the application.' delete_envs: 'Allow member to <strong>delete environments</strong> within the application.'
manage_team: 'Allow member to <strong>add, update,</strong> and <strong>remove members</strong> from the application team.' manage_team: 'Allow member to <strong>add, update,</strong> and <strong>remove members</strong> from the application team.'
perms_team_mgmt: perms_team_mgmt:
view_application: View Team 'False': View Team
edit_application_team: Edit Team 'True': Edit Team
perms_env_mgmt: perms_env_mgmt:
view_application: View Environments 'False': View Environments
edit_application_environments: Edit Environments 'True': Edit Environments
perms_del_env: perms_del_env:
view_application: "" 'False': ""
delete_application_environments: Delete Application 'True': Delete Application
index: index:
empty: empty:
start_button: Start a new JEDI portfolio start_button: Start a new JEDI portfolio