Merge pull request #237 from dod-ccpo/add-workspace-member

Add a member to a workspace
This commit is contained in:
richard-dds 2018-09-04 10:23:01 -04:00 committed by GitHub
commit be715bc377
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 127 additions and 22 deletions

View File

@ -33,6 +33,8 @@ class WorkspaceUsers(object):
@classmethod @classmethod
def add(cls, user, workspace_id, role_name): def add(cls, user, workspace_id, role_name):
role = Roles.get(role_name) role = Roles.get(role_name)
new_workspace_role = None
try: try:
existing_workspace_role = ( existing_workspace_role = (
db.session.query(WorkspaceRole) db.session.query(WorkspaceRole)
@ -53,6 +55,8 @@ class WorkspaceUsers(object):
db.session.add(user) db.session.add(user)
db.session.commit() db.session.commit()
return WorkspaceUser(user, new_workspace_role)
@classmethod @classmethod
def add_many(cls, workspace_id, workspace_user_dicts): def add_many(cls, workspace_id, workspace_user_dicts):
workspace_users = [] workspace_users = []

View File

@ -7,6 +7,8 @@ from atst.domain.exceptions import NotFoundError, UnauthorizedError
from atst.domain.roles import Roles from atst.domain.roles import Roles
from atst.domain.authz import Authorization from atst.domain.authz import Authorization
from atst.models.permissions import Permissions from atst.models.permissions import Permissions
from atst.domain.users import Users
from atst.domain.workspace_users import WorkspaceUsers
class Workspaces(object): class Workspaces(object):
@ -61,6 +63,24 @@ class Workspaces(object):
) )
return workspaces return workspaces
@classmethod
def create_member(cls, user, workspace, data):
if not Authorization.has_workspace_permission(
user, workspace, Permissions.ASSIGN_AND_UNASSIGN_ATAT_ROLE
):
raise UnauthorizedError(user, "create workspace member")
new_user = Users.get_or_create_by_dod_id(
data["dod_id"],
first_name=data["first_name"],
last_name=data["last_name"],
email=data["email"],
)
workspace_user = WorkspaceUsers.add(
new_user, workspace.id, data["workspace_role"]
)
return workspace_user
@classmethod @classmethod
def _create_workspace_role(cls, user, workspace, role_name): def _create_workspace_role(cls, user, workspace, role_name):
role = Roles.get(role_name) role = Roles.get(role_name)

View File

@ -106,27 +106,37 @@ COMPLETION_DATE_RANGES = [
WORKSPACE_ROLES = [ WORKSPACE_ROLES = [
( (
"owner", "owner",
"Workspace Owner", {
"Can add, edit, deactivate access to all projects, environments, and members. Can view budget reports. Can start and edit JEDI Cloud requests.", "name": "Workspace Owner",
"description": "Can add, edit, deactivate access to all projects, environments, and members. Can view budget reports. Can start and edit JEDI Cloud requests.",
},
), ),
( (
"admin", "admin",
"Administrator", {
"Can add and edit projects, environments, members, but cannot deactivate. Cannot view budget reports or JEDI Cloud requests.", "name": "Administrator",
"description": "Can add and edit projects, environments, members, but cannot deactivate. Cannot view budget reports or JEDI Cloud requests.",
},
), ),
( (
"developer", "developer",
"Developer", {
"Can view only the projects and environments they are granted access to. Can also view members associated with each environment.", "name": "Developer",
"description": "Can view only the projects and environments they are granted access to. Can also view members associated with each environment.",
},
), ),
( (
"billing_auditor", "billing_auditor",
"Billing Auditor", {
"Can view only the projects and environments they are granted access to. Can also view budgets and reports associated with the workspace.", "name": "Billing Auditor",
"description": "Can view only the projects and environments they are granted access to. Can also view budgets and reports associated with the workspace.",
},
), ),
( (
"security_auditor", "security_auditor",
"Security Auditor", {
"Can view only the projects and environments they are granted access to. Can also view activity logs.", "name": "Security Auditor",
"description": "Can view only the projects and environments they are granted access to. Can also view activity logs.",
},
), ),
] ]

View File

@ -10,5 +10,9 @@ class WorkspaceUser(object):
) )
return set(workspace_permissions).union(atat_permissions) return set(workspace_permissions).union(atat_permissions)
@property
def workspace(self):
return self.workspace_role.workspace
def workspace_id(self): def workspace_id(self):
return self.workspace_role.workspace_id return self.workspace_role.workspace_id

View File

@ -101,3 +101,21 @@ def new_member(workspace_id):
workspace = Workspaces.get(g.current_user, workspace_id) workspace = Workspaces.get(g.current_user, workspace_id)
form = NewMemberForm() form = NewMemberForm()
return render_template("member_new.html", workspace=workspace, form=form) return render_template("member_new.html", workspace=workspace, form=form)
@bp.route("/workspaces/<workspace_id>/members/new", methods=["POST"])
def create_member(workspace_id):
workspace = Workspaces.get(g.current_user, workspace_id)
form = NewMemberForm(http_request.form)
if form.validate():
new_member = Workspaces.create_member(g.current_user, workspace, form.data)
return redirect(
url_for(
"workspaces.workspace_members",
workspace_id=workspace.id,
newMemberName=new_member.user.full_name,
)
)
else:
return render_template("member_new.html", workspace=workspace, form=form)

View File

@ -26,11 +26,14 @@ export default {
computed: { computed: {
label: function () { label: function () {
return this.value if (this.value) {
? this.choices.find((choice) => { const selectedChoice = this.choices.find((choice) => {
return this.value === choice[0] return this.value === choice[0]
})[1] })[1]
: this.defaultLabel return selectedChoice.name
} else {
return this.defaultLabel
}
} }
}, },

View File

@ -37,7 +37,8 @@ def seed_db():
Projects.create( Projects.create(
workspace=workspace, workspace=workspace,
name="First Project", name="First Project",
description="This is our first project." description="This is our first project.",
environment_names=["dev", "staging", "prod"]
) )

View File

@ -44,13 +44,13 @@
v-bind:checked='value === choice[0]' v-bind:checked='value === choice[0]'
v-on:change='change'/> v-on:change='change'/>
<label v-bind:for="'{{ field.name }}_' + choice[0]"> <label v-bind:for="'{{ field.name }}_' + choice[0]">
<template v-if='choices[2]'> <template v-if='choice[1].description'>
<dl> <dl>
<dt v-html='choice[1]'></dt> <dt v-html='choice[1].name'></dt>
<dd v-html='choice[2]'></dd> <dd v-html='choice[1].description'></dd>
</dl> </dl>
</template> </template>
<span v-else v-html='choice[1]'> <span v-else v-html='choice[1].name'>
</label> </label>
</template> </template>
</li> </li>

View File

@ -7,7 +7,8 @@
{% block content %} {% block content %}
<form method="POST" action="" autocomplete="false"> <form method="POST" action="{{ url_for('workspaces.create_member', workspace_id=workspace.id) }}" autocomplete="false">
{{ form.csrf_token }}
<div class="panel"> <div class="panel">
@ -27,9 +28,7 @@
<div class='action-group'> <div class='action-group'>
<a href='#' class='action-group__action usa-button usa-button-big'> <button class="usa-button usa-button-big usa-button-primary" tabindex="0">Add User</button>
Add User
</a>
<a href='#' class='action-group__action icon-link'> <a href='#' class='action-group__action icon-link'>
{{ Icon('x') }} {{ Icon('x') }}
<span>Cancel</span> <span>Cancel</span>

View File

@ -1,6 +1,7 @@
{% extends "base_workspace.html" %} {% extends "base_workspace.html" %}
{% from "components/empty_state.html" import EmptyState %} {% from "components/empty_state.html" import EmptyState %}
{% from "components/alert.html" import Alert %}
{% block workspace_content %} {% block workspace_content %}
@ -19,6 +20,19 @@
{% else %} {% else %}
{% set new_member_name = request.args.get("newMemberName") %}
{% if new_member_name %}
{% set message -%}
<p>{{ new_member_name }} was successfully invited via email to this workspace. They do not yet have access to any environments.</p>
<p><a href="#">Add environment access.</a></p>
{%- endset %}
{{ Alert('Member added successfully',
message=message,
level='success'
) }}
{% endif %}
<form class='search-bar'> <form class='search-bar'>
<div class='usa-input search-input'> <div class='usa-input search-input'>
<label for='members-search'>Search members by name</label> <label for='members-search'>Search members by name</label>

View File

@ -87,3 +87,35 @@ def test_get_for_update_blocks_developer():
with pytest.raises(UnauthorizedError): with pytest.raises(UnauthorizedError):
Workspaces.get_for_update(developer, workspace.id) Workspaces.get_for_update(developer, workspace.id)
def test_can_create_workspace_user():
owner = UserFactory.create()
workspace = Workspaces.create(RequestFactory.create(creator=owner))
user_data = {
"first_name": "New",
"last_name": "User",
"email": "new.user@mail.com",
"workspace_role": "developer",
"dod_id": "1234567890",
}
new_member = Workspaces.create_member(owner, workspace, user_data)
assert new_member.workspace == workspace
def test_need_permission_to_create_workspace_user():
workspace = Workspaces.create(request=RequestFactory.create())
random_user = UserFactory.create()
user_data = {
"first_name": "New",
"last_name": "User",
"email": "new.user@mail.com",
"workspace_role": "developer",
"dod_id": "1234567890",
}
with pytest.raises(UnauthorizedError):
Workspaces.create_member(random_user, workspace, user_data)