Create route for resending an app invite

Replace ApplicationInvitations._update_status() with revoke() because multiple functions used _update_status() and it was causing app roles to be disabled when they shouldn't have. Now app roles are disabled within the revoke function.
Updated Invitations.resend() to accept user details so the invite info
can be changed in the new invite
This commit is contained in:
leigh-mil 2019-09-30 13:52:39 -04:00
parent bb6d656def
commit 4d043363a7
6 changed files with 117 additions and 11 deletions

View File

@ -3,7 +3,7 @@
"files": "^.secrets.baseline$",
"lines": null
},
"generated_at": "2019-10-02T14:53:58Z",
"generated_at": "2019-09-30T16:06:53Z",
"plugins_used": [
{
"base64_limit": 4.5,
@ -194,7 +194,7 @@
"hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207",
"is_secret": false,
"is_verified": false,
"line_number": 638,
"line_number": 651,
"type": "Hex High Entropy String"
}
]

View File

@ -117,22 +117,30 @@ class BaseInvitations(object):
return cls._update_status(invite, InvitationStatus.REVOKED)
@classmethod
def resend(cls, inviter, token):
def resend(cls, inviter, token, user_info=None):
previous_invitation = cls._get(token)
cls._update_status(previous_invitation, InvitationStatus.REVOKED)
return cls.create(
inviter,
previous_invitation.role,
{
if user_info:
user_details = {
"email": user_info["email"],
"dod_id": user_info["dod_id"],
"first_name": user_info["first_name"],
"last_name": user_info["last_name"],
"phone_number": user_info["phone_number"],
"phone_ext": user_info["phone_ext"],
}
else:
user_details = {
"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,
)
"phone_number": previous_invitation.phone_number,
"phone_ext": previous_invitation.phone_ext,
}
return cls.create(inviter, previous_invitation.role, user_details, commit=True)
class PortfolioInvitations(BaseInvitations):

View File

@ -12,6 +12,7 @@ from atst.domain.invitations import ApplicationInvitations
from atst.forms.application_member import NewForm as NewMemberForm, UpdateMemberForm
from atst.forms.application import NameAndDescriptionForm, EditEnvironmentForm
from atst.forms.data import ENV_ROLE_NO_ACCESS as NO_ACCESS
from atst.forms.member import NewForm as MemberForm
from atst.domain.authz.decorator import user_can_access_decorator as user_can
from atst.models.permissions import Permissions
from atst.domain.permission_sets import PermissionSets
@ -419,3 +420,46 @@ def revoke_invite(application_id, application_role_id):
_anchor="application-members",
)
)
@applications_bp.route(
"/applications/<application_id>/members/<application_role_id>/resend_invite",
methods=["POST"],
)
@user_can(Permissions.EDIT_APPLICATION_MEMBER, message="resend application invitation")
def resend_invite(application_id, application_role_id):
app_role = ApplicationRoles.get_by_id(application_role_id)
invite = app_role.latest_invitation
form = MemberForm(http_request.form)
if form.validate():
new_invite = ApplicationInvitations.resend(
g.current_user, invite.token, form.data
)
send_application_invitation(
invitee_email=new_invite.email,
inviter_name=g.current_user.full_name,
token=new_invite.token,
)
flash(
"application_invite_resent",
user_name=new_invite.user_name,
application_name=app_role.application.name,
)
else:
flash(
"application_invite_error",
user_name=app_role.user_name,
application_name=g.application.name,
)
return redirect(
url_for(
"applications.settings",
application_id=application_id,
fragment="application-members",
_anchor="application-members",
)
)

View File

@ -39,6 +39,11 @@ MESSAGES = {
"message_template": "There was an error processing the invitation for {{ user_name }} from {{ application_name }}",
"category": "error",
},
"application_invite_resent": {
"title_template": "Application invitation revoked",
"message_template": "You have successfully resent the invite for {{ user_name }} from {{ application_name }}",
"category": "success",
},
"application_invite_revoked": {
"title_template": "Application invitation revoked",
"message_template": "You have successfully revoked the invite for {{ user_name }} from {{ application_name }}",

View File

@ -2,6 +2,7 @@ import pytest
import uuid
from flask import url_for, get_flashed_messages
from unittest.mock import Mock
import datetime
from tests.factories import *
@ -591,3 +592,33 @@ def test_filter_environment_roles():
environment_data = filter_env_roles_form_data(application_role3, [environment])
assert environment_data[0]["role"] == "No Access"
def test_resend_invite(client, user_session, session):
user = UserFactory.create()
# need to set the time created to yesterday, otherwise the original invite and resent
# invite have the same time_created and then we can't rely on time to order the invites
yesterday = datetime.date.today() - datetime.timedelta(days=1)
invite = ApplicationInvitationFactory.create(user=user, time_created=yesterday)
app_role = invite.role
application = app_role.application
user_session(application.portfolio.owner)
response = client.post(
url_for(
"applications.resend_invite",
application_id=application.id,
application_role_id=app_role.id,
),
data={
"first_name": user.first_name,
"last_name": user.last_name,
"dod_id": user.dod_id,
"email": "an_email@example.com",
},
)
session.refresh(app_role)
assert response.status_code == 302
assert invite.is_revoked
assert app_role.status == ApplicationRoleStatus.PENDING
assert app_role.latest_invitation.email == "an_email@example.com"

View File

@ -595,6 +595,24 @@ def test_applications_revoke_invite(post_url_assert_status):
post_url_assert_status(user, url, status)
# applications.resend_invite
def test_applications_resend_invite(post_url_assert_status):
ccpo = UserFactory.create_ccpo()
rando = UserFactory.create()
application = ApplicationFactory.create()
for user, status in [(ccpo, 302), (application.portfolio.owner, 302), (rando, 404)]:
app_role = ApplicationRoleFactory.create()
invite = ApplicationInvitationFactory.create(role=app_role)
url = url_for(
"applications.resend_invite",
application_id=application.id,
application_role_id=app_role.id,
)
post_url_assert_status(user, url, status)
# task_orders.download_task_order_pdf
def test_task_orders_download_task_order_pdf_access(get_url_assert_status, monkeypatch):
monkeypatch.setattr(