Delete an application member

This commit is contained in:
George Drummond 2019-05-13 10:14:48 -04:00
parent 547d813970
commit 27a4ef12c6
No known key found for this signature in database
GPG Key ID: 296DD6077123BF17
9 changed files with 167 additions and 9 deletions

View File

@ -3,6 +3,7 @@ from sqlalchemy.orm.exc import NoResultFound
from atst.database import db
from . import BaseDomainClass
from atst.domain.application_roles import ApplicationRoles
from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.environments import Environments
from atst.domain.exceptions import NotFoundError
from atst.domain.users import Users
@ -100,3 +101,15 @@ class Applications(BaseDomainClass):
Environments.add_member(environment, user, env_role_data.get("role"))
return application_role
@classmethod
def remove_member(cls, application, user):
application_role = ApplicationRoles.get(
user_id=user.id, application_id=application.id
)
db.session.delete(application_role)
db.session.commit()
for env in application.environments:
EnvironmentRoles.delete(user_id=user.id, environment_id=env.id)

View File

@ -158,3 +158,31 @@ def create_member(application_id):
_anchor="application-members",
)
)
@applications_bp.route(
"/applications/<application_id>/members/<user_id>/delete", methods=["POST"]
)
# TODO: Is this correct??
@user_can(Permissions.EDIT_APPLICATION_MEMBER, message="remove application member")
def remove_member(application_id, user_id):
application_role = ApplicationRoles.get(
application_id=application_id, user_id=user_id
)
Applications.remove_member(application=g.application, user=application_role.user)
flash(
"application_member_removed",
user_name=application_role.user.full_name,
application_name=g.application.name,
)
return redirect(
url_for(
"applications.team",
_anchor="application-members",
application_id=g.application.id,
fragment="application-members",
)
)

View File

@ -2,6 +2,11 @@ from flask import flash, render_template_string
from atst.utils.localization import translate
MESSAGES = {
"application_member_removed": {
"title_template": "Team member removed from application",
"message_template": "You have successfully deleted {{ user_name }} from {{ application_name }}",
"category": "success",
},
"environment_deleted": {
"title_template": "{{ environment_name }} deleted",
"message_template": 'The environment "{{ environment_name }}" has been deleted',

View File

@ -1,12 +1,12 @@
import ally from 'ally.js'
import checkboxinput from '../checkbox_input'
import DateSelector from '../date_selector'
import FormMixin from '../../mixins/form'
import levelofwarrant from '../levelofwarrant'
import Modal from '../../mixins/modal'
import multicheckboxinput from '../multi_checkbox_input'
import MultiStepModalForm from './multi_step_modal_form'
import checkboxinput from '../checkbox_input'
import levelofwarrant from '../levelofwarrant'
import multicheckboxinput from '../multi_checkbox_input'
import optionsinput from '../options_input'
import textinput from '../text_input'
import toggler from '../toggler'
@ -14,12 +14,12 @@ import toggler from '../toggler'
export default {
name: 'base-form',
components: {
checkboxinput,
DateSelector,
levelofwarrant,
Modal,
multicheckboxinput,
MultiStepModalForm,
checkboxinput,
levelofwarrant,
multicheckboxinput,
optionsinput,
textinput,
toggler,

View File

@ -3,6 +3,7 @@
{{ team_form.csrf_token }}
{% for member_form in team_form.members %}
{% set delete_modal_id = "delete-user-{}".format(member_form.id) %}
{% set environment_roles_form = member_form.environment_roles %}
{% set permissions_form = member_form.permission_sets %}
@ -38,6 +39,13 @@
{{ environment_form.environment_name.data }}
</li>
{% endfor %}
{% if user_can(permissions.EDIT_APPLICATION_MEMBER) %}
<li class="accordion-table__item__expanded action-group">
<span class="usa-button button-danger" v-on:click="openModal('{{ delete_modal_id }}')">
{{ "portfolios.members.archive_button" | translate }}
</span>
</li>
{% endif %}
</ul>
{% endcall %}
{{ member_form.user_id() }}

View File

@ -6,6 +6,9 @@
{% from "components/toggle_list.html" import ToggleButton, ToggleSection %}
{% from "components/multi_step_modal_form.html" import MultiStepModalForm %}
{% import "fragments/applications/new_member_modal_content.html" as member_steps %}
{% from "components/alert.html" import Alert %}
{% from "components/delete_confirmation.html" import DeleteConfirmation %}
{% from "components/modal.html" import Modal %}
{% set secondary_breadcrumb = 'portfolios.applications.team_settings.title' | translate({ "application_name": application.name }) %}
@ -109,6 +112,35 @@
</div>
</form>
</base-form>
{% if user_can(permissions.EDIT_APPLICATION_MEMBER) %}
{% for member_form in team_form.members %}
{% set delete_modal_id = "delete-user-{}".format(member_form.id) %}
{% call Modal(name=delete_modal_id, dismissable=True) %}
<h1>
{{ "portfolios.applications.remove_member.header" | translate }}
</h1>
{{
Alert(
title=("portfolios.applications.remove_member.alert.title" | translate),
message=("portfolios.applications.remove_member.alert.message" | translate({"user_name": member_form.user_name.data})),
level="warning"
)
}}
{{
DeleteConfirmation(
modal_id=delete_modal_id,
delete_text=('portfolios.applications.remove_member.button' | translate),
delete_action=url_for('applications.remove_member', application_id=application.id, user_id=member_form.data.user_id),
form=member_form
)
}}
{% endcall %}
{% endfor %}
{% endif %}
{% if user_can(permissions.CREATE_APPLICATION_MEMBER) %}
{% import "fragments/applications/new_member_modal_content.html" as member_steps %}
{{ MultiStepModalForm(

View File

@ -2,16 +2,19 @@ import pytest
from uuid import uuid4
from atst.models import CSPRole, ApplicationRoleStatus
from atst.domain.application_roles import ApplicationRoles
from atst.domain.applications import Applications
from atst.domain.permission_sets import PermissionSets
from atst.domain.environment_roles import EnvironmentRoles
from atst.domain.exceptions import NotFoundError
from atst.domain.permission_sets import PermissionSets
from tests.factories import (
ApplicationFactory,
ApplicationRoleFactory,
UserFactory,
PortfolioFactory,
EnvironmentFactory,
EnvironmentRoleFactory,
PortfolioFactory,
UserFactory,
)
@ -155,3 +158,25 @@ def test_for_user():
assert len(portfolio.applications) == 4
user_applications = Applications.for_user(user, portfolio)
assert len(user_applications) == 2
def test_remove_member():
application = ApplicationFactory.create()
user = UserFactory.create()
member_role = ApplicationRoleFactory.create(application=application, user=user)
environment = EnvironmentFactory.create(application=application)
environment_role = EnvironmentRoleFactory.create(user=user, environment=environment)
assert member_role == ApplicationRoles.get(
user_id=user.id, application_id=application.id
)
Applications.remove_member(application=application, user=member_role.user)
with pytest.raises(NotFoundError):
ApplicationRoles.get(user_id=user.id, application_id=application.id)
#
# TODO: Why does above raise NotFoundError and this returns None
#
assert EnvironmentRoles.get(user_id=user.id, environment_id=environment.id) == None

View File

@ -1,4 +1,5 @@
import pytest
import uuid
from flask import url_for
from atst.domain.permission_sets import PermissionSets
@ -128,3 +129,43 @@ def test_create_member(client, user_session):
assert user.application_roles[0].application == application
assert len(user.environment_roles) == 1
assert user.environment_roles[0].environment == env
def test_remove_member_success(client, user_session):
user = UserFactory.create()
application = ApplicationFactory.create()
application_role = ApplicationRoleFactory.create(application=application, user=user)
user_session(application.portfolio.owner)
response = client.post(
url_for(
"applications.remove_member", application_id=application.id, user_id=user.id
)
)
assert response.status_code == 302
assert response.location == url_for(
"applications.team",
_anchor="application-members",
_external=True,
application_id=application.id,
fragment="application-members",
)
def test_remove_member_failure(client, user_session):
user = UserFactory.create()
application = ApplicationFactory.create()
user_session(application.portfolio.owner)
response = client.post(
url_for(
"applications.remove_member",
application_id=application.id,
user_id=uuid.uuid4(),
)
)
assert response.status_code == 404

View File

@ -416,6 +416,12 @@ portfolios:
app_settings_text: App settings
create_button_text: Create
csp_console_text: CSP console
remove_member:
alert:
message: '{user_name} will no longer be able to access this application'
title: Warning! This action is permanent.
button: Remove member
header: Are you sure you want to remove this team member?
delete:
alert:
message: You will lose access to this application and all of its reporting and metrics tools. The activity log will be retained.