New invitation backend for portfolio invitations.

Portfolio invitations do not associate a user entity until the
invitation has been accepted. User info, including DOD ID, is held on
the invitation itself. When a user accepts and invitation, their user
entry is associated with the corresponding `portfolio_role` entry.

The same change will be applied to `application_role` and application
invitations. For now, small changes have been made to
application-related methods so that that flow works as-is.
This commit is contained in:
dandds 2019-05-31 12:58:23 -04:00
parent 755fabd725
commit c085db23d7
17 changed files with 163 additions and 165 deletions

View File

@ -1,7 +1,7 @@
"""add user data fields to invitations """add user data fields to invitations
Revision ID: 8467440c4ae6 Revision ID: 8467440c4ae6
Revises: d2390c547dca Revises: 24700d113ea9
Create Date: 2019-05-31 12:40:10.457529 Create Date: 2019-05-31 12:40:10.457529
""" """
@ -11,7 +11,7 @@ from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic. # revision identifiers, used by Alembic.
revision = '8467440c4ae6' revision = '8467440c4ae6'
down_revision = 'd2390c547dca' down_revision = '24700d113ea9'
branch_labels = None branch_labels = None
depends_on = None depends_on = None

View File

@ -28,8 +28,9 @@ class ApplicationRoles(object):
return application_role return application_role
@classmethod @classmethod
def enable(cls, role): def enable(cls, role, user):
role.status = ApplicationRoleStatus.ACTIVE role.status = ApplicationRoleStatus.ACTIVE
role.user = user
db.session.add(role) db.session.add(role)
db.session.commit() db.session.commit()

View File

@ -55,7 +55,7 @@ class BaseInvitations(object):
return invite return invite
@classmethod @classmethod
def create(cls, inviter, role, email): def create(cls, inviter, role, member_data, commit=False):
# pylint: disable=not-callable # pylint: disable=not-callable
invite = cls.model( invite = cls.model(
role=role, role=role,
@ -63,9 +63,15 @@ class BaseInvitations(object):
user=role.user, user=role.user,
status=InvitationStatus.PENDING, status=InvitationStatus.PENDING,
expiration_time=cls.current_expiration_time(), expiration_time=cls.current_expiration_time(),
email=email, email=member_data.get("email"),
dod_id=member_data.get("dod_id"),
first_name=member_data.get("first_name"),
phone_number=member_data.get("phone_number"),
last_name=member_data.get("last_name"),
) )
db.session.add(invite) db.session.add(invite)
if commit:
db.session.commit() db.session.commit()
return invite return invite
@ -74,7 +80,7 @@ class BaseInvitations(object):
def accept(cls, user, token): def accept(cls, user, token):
invite = cls._get(token) invite = cls._get(token)
if invite.user.dod_id != user.dod_id: if invite.dod_id != user.dod_id:
if invite.is_pending: if invite.is_pending:
cls._update_status(invite, InvitationStatus.REJECTED_WRONG_USER) cls._update_status(invite, InvitationStatus.REJECTED_WRONG_USER)
raise WrongUserError(user, invite) raise WrongUserError(user, invite)
@ -88,7 +94,7 @@ class BaseInvitations(object):
elif invite.is_pending: # pragma: no branch elif invite.is_pending: # pragma: no branch
cls._update_status(invite, InvitationStatus.ACCEPTED) cls._update_status(invite, InvitationStatus.ACCEPTED)
cls.role_domain_class.enable(invite.role) cls.role_domain_class.enable(invite.role, user)
return invite return invite
@classmethod @classmethod
@ -111,20 +117,22 @@ class BaseInvitations(object):
return cls._update_status(invite, InvitationStatus.REVOKED) return cls._update_status(invite, InvitationStatus.REVOKED)
@classmethod @classmethod
def lookup_by_resource_and_user(cls, resource, user): def resend(cls, inviter, token):
role = cls.role_domain_class.get(resource.id, user.id)
if role.latest_invitation is None:
raise NotFoundError(cls.model.__tablename__)
return role.latest_invitation
@classmethod
def resend(cls, user, token):
previous_invitation = cls._get(token) previous_invitation = cls._get(token)
cls._update_status(previous_invitation, InvitationStatus.REVOKED) cls._update_status(previous_invitation, InvitationStatus.REVOKED)
return cls.create(user, previous_invitation.role, previous_invitation.email) return cls.create(
inviter,
previous_invitation.role,
{
"email": previous_invitation.email,
"dod_id": previous_invitation.dod_id,
"first_name": previous_invitation.first_name,
"last_name": previous_invitation.last_name,
"phone_number": previous_invitation.last_name,
},
commit=True,
)
class PortfolioInvitations(BaseInvitations): class PortfolioInvitations(BaseInvitations):

View File

@ -163,8 +163,9 @@ class PortfolioRoles(object):
return portfolio_role return portfolio_role
@classmethod @classmethod
def enable(cls, portfolio_role): def enable(cls, portfolio_role, user):
portfolio_role.status = PortfolioRoleStatus.ACTIVE portfolio_role.status = PortfolioRoleStatus.ACTIVE
portfolio_role.user = user
db.session.add(portfolio_role) db.session.add(portfolio_role)
db.session.commit() db.session.commit()

View File

@ -1,10 +1,9 @@
from atst.domain.permission_sets import PermissionSets from atst.domain.permission_sets import PermissionSets
from atst.domain.authz import Authorization from atst.domain.authz import Authorization
from atst.models.permissions import Permissions
from atst.domain.users import Users
from atst.domain.portfolio_roles import PortfolioRoles from atst.domain.portfolio_roles import PortfolioRoles
from atst.domain.invitations import PortfolioInvitations
from atst.domain.environments import Environments from atst.domain.environments import Environments
from atst.models.portfolio_role import Status as PortfolioRoleStatus from atst.models import Permissions, PortfolioRole, PortfolioRoleStatus
from .query import PortfoliosQuery from .query import PortfoliosQuery
from .scopes import ScopedPortfolio from .scopes import ScopedPortfolio
@ -49,25 +48,26 @@ class Portfolios(object):
portfolios = PortfoliosQuery.get_for_user(user) portfolios = PortfoliosQuery.get_for_user(user)
return portfolios return portfolios
@classmethod
def create_member(cls, portfolio, data):
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"],
provisional=True,
)
permission_sets = data.get("permission_sets", [])
return Portfolios.add_member(
portfolio, new_user, permission_sets=permission_sets
)
@classmethod @classmethod
def add_member(cls, portfolio, member, permission_sets=None): def add_member(cls, portfolio, member, permission_sets=None):
portfolio_role = PortfolioRoles.add(member, portfolio.id, permission_sets) portfolio_role = PortfolioRoles.add(member, portfolio.id, permission_sets)
return portfolio_role return portfolio_role
@classmethod
def invite(cls, portfolio, inviter, member_data):
permission_sets = PortfolioRoles._permission_sets_for_names(
member_data.get("permission_sets", [])
)
role = PortfolioRole(portfolio_id=portfolio.id, permission_sets=permission_sets)
invitation = PortfolioInvitations.create(
inviter=inviter, role=role, member_data=member_data
)
PortfoliosQuery.add_and_commit(role)
return invitation
@classmethod @classmethod
def update_member(cls, member, permission_sets): def update_member(cls, member, permission_sets):
return PortfolioRoles.update(member, permission_sets) return PortfolioRoles.update(member, permission_sets)

View File

@ -97,7 +97,7 @@ class InvitesMixin(object):
@property @property
def user_name(self): def user_name(self):
return self.role.user.full_name return "{} {}".format(self.first_name, self.last_name)
@property @property
def is_revokable(self): def is_revokable(self):

View File

@ -116,7 +116,14 @@ class PortfolioRole(
@property @property
def user_name(self): def user_name(self):
if self.user:
return self.user.full_name return self.user.full_name
else:
return self.latest_invitation.user_name
@property
def full_name(self):
return self.user_name
@property @property
def is_active(self): def is_active(self):
@ -128,10 +135,6 @@ class PortfolioRole(
self.latest_invitation and self.latest_invitation.is_inactive self.latest_invitation and self.latest_invitation.is_inactive
) )
@property
def full_name(self):
return self.user.full_name
@property @property
def application_id(self): def application_id(self):
return None return None

View File

@ -3,12 +3,23 @@ from flask import render_template, request as http_request, g, redirect, url_for
from . import portfolios_bp from . import portfolios_bp
from atst.domain.exceptions import AlreadyExistsError from atst.domain.exceptions import AlreadyExistsError
from atst.domain.portfolios import Portfolios from atst.domain.portfolios import Portfolios
from atst.services.invitation import Invitation as InvitationService
import atst.forms.portfolio_member as member_forms import atst.forms.portfolio_member as member_forms
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.permissions import Permissions from atst.models.permissions import Permissions
from atst.utils.flash import formatted_flash as flash from atst.utils.flash import formatted_flash as flash
from atst.queue import queue
def send_portfolio_invitation(invitee_email, inviter_name, token):
body = render_template(
"emails/portfolio/invitation.txt", owner=inviter_name, token=token
)
queue.send_mail(
[invitee_email],
"{} has invited you to a JEDI cloud portfolio".format(inviter_name),
body,
)
@portfolios_bp.route("/portfolios/<portfolio_id>/members/new", methods=["POST"]) @portfolios_bp.route("/portfolios/<portfolio_id>/members/new", methods=["POST"])
@ -19,13 +30,14 @@ def create_member(portfolio_id):
if form.validate(): if form.validate():
try: try:
member = Portfolios.create_member(portfolio, form.update_data) invite = Portfolios.invite(portfolio, g.current_user, form.update_data)
invite_service = InvitationService( send_portfolio_invitation(
g.current_user, member, form.update_data.get("email") invite.email, g.current_user.full_name, invite.token
) )
invite_service.invite()
flash("new_portfolio_member", new_member=member, portfolio=portfolio) flash(
"new_portfolio_member", user_name=invite.user_name, portfolio=portfolio
)
except AlreadyExistsError: except AlreadyExistsError:
return render_template( return render_template(

View File

@ -79,7 +79,13 @@ class Invitation:
return invite return invite
def _create_invite(self): def _create_invite(self):
return self.domain_class.create(self.inviter, self.member, self.email) user = self.member.user
return self.domain_class.create(
self.inviter,
self.member,
{"email": self.email, "dod_id": user.dod_id},
commit=True,
)
def _send_invite_email(self, token): def _send_invite_email(self, token):
body = render_template( body = render_template(

View File

@ -61,7 +61,7 @@ MESSAGES = {
"new_portfolio_member": { "new_portfolio_member": {
"title_template": translate("flash.success"), "title_template": translate("flash.success"),
"message_template": """ "message_template": """
<p>{{ "flash.new_portfolio_member" | translate({ "user_name": new_member.user_name }) }}</p> <p>{{ "flash.new_portfolio_member" | translate({ "user_name": user_name }) }}</p>
""", """,
"category": "success", "category": "success",
}, },

View File

@ -33,7 +33,7 @@ def test_enabled_application_role():
) )
assert app_role.status == ApplicationRoleStatus.DISABLED assert app_role.status == ApplicationRoleStatus.DISABLED
ApplicationRoles.enable(app_role) ApplicationRoles.enable(app_role, app_role.user)
assert app_role.status == ApplicationRoleStatus.ACTIVE assert app_role.status == ApplicationRoleStatus.ACTIVE

View File

@ -23,10 +23,11 @@ from tests.factories import (
def test_create_invitation(): def test_create_invitation():
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
user = UserFactory.create() user = UserFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(user=user, portfolio=portfolio)
invite = PortfolioInvitations.create(portfolio.owner, ws_role, user.email) invite = PortfolioInvitations.create(
assert invite.user == user portfolio.owner, role, user.to_dictionary(), commit=True
assert invite.role == ws_role )
assert invite.role == role
assert invite.inviter == portfolio.owner assert invite.inviter == portfolio.owner
assert invite.status == InvitationStatus.PENDING assert invite.status == InvitationStatus.PENDING
assert re.match(r"^[\w\-_]+$", invite.token) assert re.match(r"^[\w\-_]+$", invite.token)
@ -35,8 +36,10 @@ def test_create_invitation():
def test_accept_invitation(): def test_accept_invitation():
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
user = UserFactory.create() user = UserFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(user=user, portfolio=portfolio)
invite = PortfolioInvitations.create(portfolio.owner, ws_role, user.email) invite = PortfolioInvitations.create(
portfolio.owner, role, user.to_dictionary(), commit=True
)
assert invite.is_pending assert invite.is_pending
accepted_invite = PortfolioInvitations.accept(user, invite.token) accepted_invite = PortfolioInvitations.accept(user, invite.token)
assert accepted_invite.is_accepted assert accepted_invite.is_accepted
@ -45,14 +48,14 @@ def test_accept_invitation():
def test_accept_expired_invitation(): def test_accept_expired_invitation():
user = UserFactory.create() user = UserFactory.create()
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(portfolio=portfolio)
increment = PortfolioInvitations.EXPIRATION_LIMIT_MINUTES + 1 increment = PortfolioInvitations.EXPIRATION_LIMIT_MINUTES + 1
expiration_time = datetime.datetime.now() - datetime.timedelta(minutes=increment) expiration_time = datetime.datetime.now() - datetime.timedelta(minutes=increment)
invite = PortfolioInvitationFactory.create( invite = PortfolioInvitationFactory.create(
user=user,
expiration_time=expiration_time, expiration_time=expiration_time,
status=InvitationStatus.PENDING, status=InvitationStatus.PENDING,
role=ws_role, role=role,
dod_id=user.dod_id,
) )
with pytest.raises(ExpiredError): with pytest.raises(ExpiredError):
PortfolioInvitations.accept(user, invite.token) PortfolioInvitations.accept(user, invite.token)
@ -63,9 +66,9 @@ def test_accept_expired_invitation():
def test_accept_rejected_invite(): def test_accept_rejected_invite():
user = UserFactory.create() user = UserFactory.create()
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(portfolio=portfolio)
invite = PortfolioInvitationFactory.create( invite = PortfolioInvitationFactory.create(
user=user, status=InvitationStatus.REJECTED_EXPIRED, role=ws_role status=InvitationStatus.REJECTED_EXPIRED, role=role, dod_id=user.dod_id
) )
with pytest.raises(InvitationError): with pytest.raises(InvitationError):
PortfolioInvitations.accept(user, invite.token) PortfolioInvitations.accept(user, invite.token)
@ -74,9 +77,9 @@ def test_accept_rejected_invite():
def test_accept_revoked_invite(): def test_accept_revoked_invite():
user = UserFactory.create() user = UserFactory.create()
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(portfolio=portfolio)
invite = PortfolioInvitationFactory.create( invite = PortfolioInvitationFactory.create(
user=user, status=InvitationStatus.REVOKED, role=ws_role status=InvitationStatus.REVOKED, role=role, dod_id=user.dod_id
) )
with pytest.raises(InvitationError): with pytest.raises(InvitationError):
PortfolioInvitations.accept(user, invite.token) PortfolioInvitations.accept(user, invite.token)
@ -85,9 +88,9 @@ def test_accept_revoked_invite():
def test_wrong_user_accepts_invitation(): def test_wrong_user_accepts_invitation():
user = UserFactory.create() user = UserFactory.create()
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(portfolio=portfolio)
wrong_user = UserFactory.create() wrong_user = UserFactory.create()
invite = PortfolioInvitationFactory.create(user=user, role=ws_role) invite = PortfolioInvitationFactory.create(role=role, dod_id=user.dod_id)
with pytest.raises(WrongUserError): with pytest.raises(WrongUserError):
PortfolioInvitations.accept(wrong_user, invite.token) PortfolioInvitations.accept(wrong_user, invite.token)
@ -95,9 +98,9 @@ def test_wrong_user_accepts_invitation():
def test_user_cannot_accept_invitation_accepted_by_wrong_user(): def test_user_cannot_accept_invitation_accepted_by_wrong_user():
user = UserFactory.create() user = UserFactory.create()
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(portfolio=portfolio)
wrong_user = UserFactory.create() wrong_user = UserFactory.create()
invite = PortfolioInvitationFactory.create(user=user, role=ws_role) invite = PortfolioInvitationFactory.create(role=role, dod_id=user.dod_id)
with pytest.raises(WrongUserError): with pytest.raises(WrongUserError):
PortfolioInvitations.accept(wrong_user, invite.token) PortfolioInvitations.accept(wrong_user, invite.token)
with pytest.raises(InvitationError): with pytest.raises(InvitationError):
@ -107,8 +110,8 @@ def test_user_cannot_accept_invitation_accepted_by_wrong_user():
def test_accept_invitation_twice(): def test_accept_invitation_twice():
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
user = UserFactory.create() user = UserFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(portfolio=portfolio)
invite = PortfolioInvitations.create(portfolio.owner, ws_role, user.email) invite = PortfolioInvitationFactory.create(role=role, dod_id=user.dod_id)
PortfolioInvitations.accept(user, invite.token) PortfolioInvitations.accept(user, invite.token)
with pytest.raises(InvitationError): with pytest.raises(InvitationError):
PortfolioInvitations.accept(user, invite.token) PortfolioInvitations.accept(user, invite.token)
@ -117,44 +120,31 @@ def test_accept_invitation_twice():
def test_revoke_invitation(): def test_revoke_invitation():
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
user = UserFactory.create() user = UserFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(user=user, portfolio=portfolio)
invite = PortfolioInvitations.create(portfolio.owner, ws_role, user.email) invite = PortfolioInvitationFactory.create(role=role, dod_id=user.dod_id)
assert invite.is_pending assert invite.is_pending
PortfolioInvitations.revoke(invite.token) PortfolioInvitations.revoke(invite.token)
assert invite.is_revoked assert invite.is_revoked
def test_resend_invitation(): def test_resend_invitation(session):
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
user = UserFactory.create() user = UserFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(portfolio=portfolio)
invite = PortfolioInvitations.create(portfolio.owner, ws_role, user.email) first_invite = PortfolioInvitationFactory.create(role=role, dod_id=user.dod_id)
PortfolioInvitations.resend(user, invite.token) assert first_invite.is_pending
assert ws_role.invitations[0].is_revoked second_invite = PortfolioInvitations.resend(user, first_invite.token)
assert ws_role.invitations[1].is_pending assert first_invite.is_revoked
assert second_invite.is_pending
def test_audit_event_for_accepted_invite(): def test_audit_event_for_accepted_invite():
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
user = UserFactory.create() user = UserFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio) role = PortfolioRoleFactory.create(portfolio=portfolio)
invite = PortfolioInvitations.create(portfolio.owner, ws_role, user.email) invite = PortfolioInvitationFactory.create(role=role, dod_id=user.dod_id)
invite = PortfolioInvitations.accept(user, invite.token) invite = PortfolioInvitations.accept(user, invite.token)
accepted_event = AuditLog.get_by_resource(invite.id)[0] accepted_event = AuditLog.get_by_resource(invite.id)[0]
assert "email" in accepted_event.event_details assert "email" in accepted_event.event_details
assert "dod_id" in accepted_event.event_details assert "dod_id" in accepted_event.event_details
def test_lookup_by_user_and_portfolio():
portfolio = PortfolioFactory.create()
user = UserFactory.create()
ws_role = PortfolioRoleFactory.create(user=user, portfolio=portfolio)
invite = PortfolioInvitations.create(portfolio.owner, ws_role, user.email)
assert PortfolioInvitations.lookup_by_resource_and_user(portfolio, user) == invite
with pytest.raises(NotFoundError):
PortfolioInvitations.lookup_by_resource_and_user(
portfolio, UserFactory.create()
)

View File

@ -49,36 +49,6 @@ def test_portfolio_has_timestamps(portfolio):
assert portfolio.time_created == portfolio.time_updated assert portfolio.time_created == portfolio.time_updated
def test_can_create_portfolio_role(portfolio, portfolio_owner):
user_data = {
"first_name": "New",
"last_name": "User",
"email": "new.user@mail.com",
"portfolio_role": "developer",
"dod_id": "1234567890",
}
new_member = Portfolios.create_member(portfolio, user_data)
assert new_member.portfolio == portfolio
assert new_member.user.provisional
def test_can_add_existing_user_to_portfolio(portfolio, portfolio_owner):
user = UserFactory.create()
user_data = {
"first_name": "New",
"last_name": "User",
"email": "new.user@mail.com",
"portfolio_role": "developer",
"dod_id": user.dod_id,
}
new_member = Portfolios.create_member(portfolio, user_data)
assert new_member.portfolio == portfolio
assert new_member.user.email == user.email
assert not new_member.user.provisional
def test_update_portfolio_role_role(portfolio, portfolio_owner): def test_update_portfolio_role_role(portfolio, portfolio_owner):
user_data = { user_data = {
"first_name": "New", "first_name": "New",
@ -238,3 +208,16 @@ def test_does_not_count_disabled_members(session):
) )
assert portfolio.user_count == 3 assert portfolio.user_count == 3
def test_invite():
portfolio = PortfolioFactory.create()
inviter = UserFactory.create()
member_data = UserFactory.dictionary()
invitation = Portfolios.invite(portfolio, inviter, member_data)
assert invitation.role
assert invitation.role.portfolio == portfolio
assert invitation.role.user is None
assert invitation.dod_id == member_data["dod_id"]

View File

@ -65,7 +65,10 @@ def test_has_portfolio_status_history(session):
# to commit after create() # to commit after create()
PortfolioRoleFactory._meta.sqlalchemy_session_persistence = "flush" PortfolioRoleFactory._meta.sqlalchemy_session_persistence = "flush"
portfolio_role = PortfolioRoleFactory.create(portfolio=portfolio, user=user) portfolio_role = PortfolioRoleFactory.create(portfolio=portfolio, user=user)
PortfolioRoles.enable(portfolio_role) portfolio_role.status = PortfolioRoleStatus.ACTIVE
session.add(portfolio_role)
session.commit()
changed_events = ( changed_events = (
session.query(AuditEvent) session.query(AuditEvent)
.filter( .filter(

View File

@ -8,7 +8,7 @@ def test_accept_application_invitation(client, user_session):
application = ApplicationFactory.create() application = ApplicationFactory.create()
app_role = ApplicationRoleFactory.create(application=application, user=user) app_role = ApplicationRoleFactory.create(application=application, user=user)
invite = ApplicationInvitationFactory.create( invite = ApplicationInvitationFactory.create(
role=app_role, user=user, inviter=application.portfolio.owner role=app_role, inviter=application.portfolio.owner, dod_id=user.dod_id
) )
user_session(user) user_session(user)
@ -28,7 +28,7 @@ def test_accept_application_invitation_end_to_end(client, user_session):
application = ApplicationFactory.create(name="Millenium Falcon") application = ApplicationFactory.create(name="Millenium Falcon")
app_role = ApplicationRoleFactory.create(application=application, user=user) app_role = ApplicationRoleFactory.create(application=application, user=user)
invite = ApplicationInvitationFactory.create( invite = ApplicationInvitationFactory.create(
role=app_role, user=user, inviter=application.portfolio.owner role=app_role, dod_id=user.dod_id, inviter=application.portfolio.owner
) )
user_session(user) user_session(user)

View File

@ -18,10 +18,10 @@ from atst.domain.permission_sets import PermissionSets
def test_existing_member_accepts_valid_invite(client, user_session): def test_existing_member_accepts_valid_invite(client, user_session):
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
user = UserFactory.create() user = UserFactory.create()
ws_role = PortfolioRoleFactory.create( role = PortfolioRoleFactory.create(
portfolio=portfolio, user=user, status=PortfolioRoleStatus.PENDING portfolio=portfolio, user=user, status=PortfolioRoleStatus.PENDING
) )
invite = PortfolioInvitationFactory.create(user_id=user.id, role=ws_role) invite = PortfolioInvitationFactory.create(dod_id=user.dod_id, role=role)
# the user does not have access to the portfolio before accepting the invite # the user does not have access to the portfolio before accepting the invite
assert len(Portfolios.for_user(user)) == 0 assert len(Portfolios.for_user(user)) == 0
@ -46,32 +46,15 @@ def test_existing_member_accepts_valid_invite(client, user_session):
def test_new_member_accepts_valid_invite(monkeypatch, client, user_session): def test_new_member_accepts_valid_invite(monkeypatch, client, user_session):
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
user_info = UserFactory.dictionary() user_info = UserFactory.dictionary()
role = PortfolioRoleFactory.create(portfolio=portfolio)
user_session(portfolio.owner) invite = PortfolioInvitationFactory.create(role=role, dod_id=user_info["dod_id"])
response = client.post(
url_for("portfolios.create_member", portfolio_id=portfolio.id),
data={
"permission_sets-perms_app_mgmt": PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT,
"permission_sets-perms_funding": PermissionSets.VIEW_PORTFOLIO_FUNDING,
"permission_sets-perms_reporting": PermissionSets.VIEW_PORTFOLIO_REPORTS,
"permission_sets-perms_portfolio_mgmt": PermissionSets.VIEW_PORTFOLIO_ADMIN,
"user_data-first_name": user_info["first_name"],
"user_data-last_name": user_info["last_name"],
"user_data-dod_id": user_info["dod_id"],
"user_data-email": user_info["email"],
},
)
assert response.status_code == 302
user = Users.get_by_dod_id(user_info["dod_id"])
token = user.portfolio_invitations[0].token
monkeypatch.setattr( monkeypatch.setattr(
"atst.domain.auth.should_redirect_to_user_profile", lambda *args: False "atst.domain.auth.should_redirect_to_user_profile", lambda *args: False
) )
user_session(user) user_session(UserFactory.create(dod_id=user_info["dod_id"]))
response = client.get( response = client.get(
url_for("portfolios.accept_invitation", portfolio_token=token) url_for("portfolios.accept_invitation", portfolio_token=invite.token)
) )
# user is redirected to the portfolio view # user is redirected to the portfolio view
@ -81,7 +64,8 @@ def test_new_member_accepts_valid_invite(monkeypatch, client, user_session):
in response.headers["Location"] in response.headers["Location"]
) )
# the user has access to the portfolio # the user has access to the portfolio
assert len(Portfolios.for_user(user)) == 1 assert role.user.dod_id == user_info["dod_id"]
assert len(role.user.portfolio_roles) == 1
def test_member_accepts_invalid_invite(client, user_session): def test_member_accepts_invalid_invite(client, user_session):

View File

@ -1,9 +1,11 @@
from flask import url_for from flask import url_for
from tests.factories import UserFactory, PortfolioFactory
from atst.domain.permission_sets import PermissionSets from atst.domain.permission_sets import PermissionSets
from atst.models import PortfolioInvitation
from atst.queue import queue from atst.queue import queue
from tests.factories import UserFactory, PortfolioFactory
_DEFAULT_PERMS_FORM_DATA = { _DEFAULT_PERMS_FORM_DATA = {
"permission_sets-perms_app_mgmt": PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT, "permission_sets-perms_app_mgmt": PermissionSets.VIEW_PORTFOLIO_APPLICATION_MANAGEMENT,
"permission_sets-perms_funding": PermissionSets.VIEW_PORTFOLIO_FUNDING, "permission_sets-perms_funding": PermissionSets.VIEW_PORTFOLIO_FUNDING,
@ -23,8 +25,8 @@ def test_user_with_permission_has_add_member_link(client, user_session):
) )
def test_create_member(client, user_session): def test_create_member(client, user_session, session):
user = UserFactory.create() user_data = UserFactory.dictionary()
portfolio = PortfolioFactory.create() portfolio = PortfolioFactory.create()
user_session(portfolio.owner) user_session(portfolio.owner)
queue_length = len(queue.get_queue()) queue_length = len(queue.get_queue())
@ -32,20 +34,25 @@ def test_create_member(client, user_session):
response = client.post( response = client.post(
url_for("portfolios.create_member", portfolio_id=portfolio.id), url_for("portfolios.create_member", portfolio_id=portfolio.id),
data={ data={
"user_data-dod_id": user.dod_id, "user_data-dod_id": user_data.get("dod_id"),
"user_data-first_name": "user_data-Wilbur", "user_data-first_name": user_data.get("first_name"),
"user_data-last_name": "user_data-Zuckerman", "user_data-last_name": user_data.get("last_name"),
"user_data-email": "user_data-some_pig@zuckermans.com", "user_data-email": user_data.get("email"),
"user_data-portfolio_role": "user_data-developer",
**_DEFAULT_PERMS_FORM_DATA, **_DEFAULT_PERMS_FORM_DATA,
}, },
follow_redirects=True, follow_redirects=True,
) )
assert response.status_code == 200 assert response.status_code == 200
assert user.full_name in response.data.decode() full_name = "{} {}".format(user_data.get("first_name"), user_data.get("last_name"))
assert user.has_portfolios assert full_name in response.data.decode()
assert user.portfolio_invitations
invitation = (
session.query(PortfolioInvitation)
.filter_by(dod_id=user_data.get("dod_id"))
.one()
)
assert invitation.role.portfolio == portfolio
assert len(queue.get_queue()) == queue_length + 1 assert len(queue.get_queue()) == queue_length + 1
portfolio_role = user.portfolio_roles[0] assert len(invitation.role.permission_sets) == 5
assert len(portfolio_role.permission_sets) == 5