Merge pull request #450 from dod-ccpo/send-invite-to-email-provided-##161961059
Send invite to email provided ##161961059
This commit is contained in:
commit
8c9e74112c
50
alembic/versions/02d11579a581_add_email_to_invite.py
Normal file
50
alembic/versions/02d11579a581_add_email_to_invite.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
"""Add email to invite
|
||||||
|
|
||||||
|
Revision ID: 02d11579a581
|
||||||
|
Revises: 4f46aecb337f
|
||||||
|
Create Date: 2018-11-19 14:51:33.178358
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.sql import text
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '02d11579a581'
|
||||||
|
down_revision = '4f46aecb337f'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('invitations', sa.Column('email', sa.String()))
|
||||||
|
conn = op.get_bind()
|
||||||
|
# Add non-null value to email column
|
||||||
|
conn.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
insert into invitations (email)
|
||||||
|
select u.email
|
||||||
|
from invitations i
|
||||||
|
inner join workspace_roles wr on i.workspace_role_id = wr.id
|
||||||
|
inner join users u on wr.user_id = u.id
|
||||||
|
where i.email is null;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
conn.execute(
|
||||||
|
text(
|
||||||
|
"""
|
||||||
|
update invitations
|
||||||
|
set email = 'example@example.com'
|
||||||
|
where email is null;
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
)
|
||||||
|
op.alter_column('invitations', 'email', nullable=False)
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('invitations', 'email')
|
||||||
|
# ### end Alembic commands ###
|
@ -54,13 +54,14 @@ class Invitations(object):
|
|||||||
return invite
|
return invite
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, inviter, workspace_role):
|
def create(cls, inviter, workspace_role, email):
|
||||||
invite = Invitation(
|
invite = Invitation(
|
||||||
workspace_role=workspace_role,
|
workspace_role=workspace_role,
|
||||||
inviter=inviter,
|
inviter=inviter,
|
||||||
user=workspace_role.user,
|
user=workspace_role.user,
|
||||||
status=InvitationStatus.PENDING,
|
status=InvitationStatus.PENDING,
|
||||||
expiration_time=Invitations.current_expiration_time(),
|
expiration_time=Invitations.current_expiration_time(),
|
||||||
|
email=email,
|
||||||
)
|
)
|
||||||
db.session.add(invite)
|
db.session.add(invite)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@ -120,4 +121,6 @@ class Invitations(object):
|
|||||||
previous_invitation = Invitations._get(token)
|
previous_invitation = Invitations._get(token)
|
||||||
Invitations._update_status(previous_invitation, InvitationStatus.REVOKED)
|
Invitations._update_status(previous_invitation, InvitationStatus.REVOKED)
|
||||||
|
|
||||||
return Invitations.create(user, previous_invitation.workspace_role)
|
return Invitations.create(
|
||||||
|
user, previous_invitation.workspace_role, previous_invitation.email
|
||||||
|
)
|
||||||
|
@ -42,11 +42,13 @@ class Invitation(Base, TimestampsMixin, AuditableMixin):
|
|||||||
|
|
||||||
expiration_time = Column(TIMESTAMP(timezone=True))
|
expiration_time = Column(TIMESTAMP(timezone=True))
|
||||||
|
|
||||||
token = Column(String(), index=True, default=lambda: secrets.token_urlsafe())
|
token = Column(String, index=True, default=lambda: secrets.token_urlsafe())
|
||||||
|
|
||||||
|
email = Column(String, nullable=False)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return "<Invitation(user='{}', workspace_role='{}', id='{}')>".format(
|
return "<Invitation(user='{}', workspace_role='{}', id='{}', email='{}')>".format(
|
||||||
self.user_id, self.workspace_role_id, self.id
|
self.user_id, self.workspace_role_id, self.id, self.email
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -82,10 +84,6 @@ class Invitation(Base, TimestampsMixin, AuditableMixin):
|
|||||||
if self.workspace_role:
|
if self.workspace_role:
|
||||||
return self.workspace_role.workspace
|
return self.workspace_role.workspace
|
||||||
|
|
||||||
@property
|
|
||||||
def user_email(self):
|
|
||||||
return self.workspace_role.user.email
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def user_name(self):
|
def user_name(self):
|
||||||
return self.workspace_role.user.full_name
|
return self.workspace_role.user.full_name
|
||||||
|
@ -262,10 +262,8 @@ def create_member(workspace_id):
|
|||||||
if form.validate():
|
if form.validate():
|
||||||
try:
|
try:
|
||||||
new_member = Workspaces.create_member(user, workspace, form.data)
|
new_member = Workspaces.create_member(user, workspace, form.data)
|
||||||
invite = Invitations.create(user, new_member)
|
invite = Invitations.create(user, new_member, form.data["email"])
|
||||||
send_invite_email(
|
send_invite_email(g.current_user.full_name, invite.token, invite.email)
|
||||||
g.current_user.full_name, invite.token, new_member.user.email
|
|
||||||
)
|
|
||||||
|
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
@ -381,7 +379,7 @@ def revoke_invitation(workspace_id, token):
|
|||||||
@bp.route("/workspaces/<workspace_id>/invitations/<token>/resend", methods=["POST"])
|
@bp.route("/workspaces/<workspace_id>/invitations/<token>/resend", methods=["POST"])
|
||||||
def resend_invitation(workspace_id, token):
|
def resend_invitation(workspace_id, token):
|
||||||
invite = Invitations.resend(g.current_user, workspace_id, token)
|
invite = Invitations.resend(g.current_user, workspace_id, token)
|
||||||
send_invite_email(g.current_user.full_name, invite.token, invite.user_email)
|
send_invite_email(g.current_user.full_name, invite.token, invite.email)
|
||||||
return redirect(
|
return redirect(
|
||||||
url_for(
|
url_for(
|
||||||
"workspaces.workspace_members",
|
"workspaces.workspace_members",
|
||||||
|
@ -22,7 +22,7 @@ def test_create_invitation():
|
|||||||
workspace = WorkspaceFactory.create()
|
workspace = WorkspaceFactory.create()
|
||||||
user = UserFactory.create()
|
user = UserFactory.create()
|
||||||
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
||||||
invite = Invitations.create(workspace.owner, ws_role)
|
invite = Invitations.create(workspace.owner, ws_role, user.email)
|
||||||
assert invite.user == user
|
assert invite.user == user
|
||||||
assert invite.workspace_role == ws_role
|
assert invite.workspace_role == ws_role
|
||||||
assert invite.inviter == workspace.owner
|
assert invite.inviter == workspace.owner
|
||||||
@ -34,7 +34,7 @@ def test_accept_invitation():
|
|||||||
workspace = WorkspaceFactory.create()
|
workspace = WorkspaceFactory.create()
|
||||||
user = UserFactory.create()
|
user = UserFactory.create()
|
||||||
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
||||||
invite = Invitations.create(workspace.owner, ws_role)
|
invite = Invitations.create(workspace.owner, ws_role, user.email)
|
||||||
assert invite.is_pending
|
assert invite.is_pending
|
||||||
accepted_invite = Invitations.accept(user, invite.token)
|
accepted_invite = Invitations.accept(user, invite.token)
|
||||||
assert accepted_invite.is_accepted
|
assert accepted_invite.is_accepted
|
||||||
@ -89,7 +89,7 @@ def test_accept_invitation_twice():
|
|||||||
workspace = WorkspaceFactory.create()
|
workspace = WorkspaceFactory.create()
|
||||||
user = UserFactory.create()
|
user = UserFactory.create()
|
||||||
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
||||||
invite = Invitations.create(workspace.owner, ws_role)
|
invite = Invitations.create(workspace.owner, ws_role, user.email)
|
||||||
Invitations.accept(user, invite.token)
|
Invitations.accept(user, invite.token)
|
||||||
with pytest.raises(InvitationError):
|
with pytest.raises(InvitationError):
|
||||||
Invitations.accept(user, invite.token)
|
Invitations.accept(user, invite.token)
|
||||||
@ -99,7 +99,7 @@ def test_revoke_invitation():
|
|||||||
workspace = WorkspaceFactory.create()
|
workspace = WorkspaceFactory.create()
|
||||||
user = UserFactory.create()
|
user = UserFactory.create()
|
||||||
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
||||||
invite = Invitations.create(workspace.owner, ws_role)
|
invite = Invitations.create(workspace.owner, ws_role, user.email)
|
||||||
assert invite.is_pending
|
assert invite.is_pending
|
||||||
Invitations.revoke(invite.token)
|
Invitations.revoke(invite.token)
|
||||||
assert invite.is_revoked
|
assert invite.is_revoked
|
||||||
@ -109,7 +109,7 @@ def test_resend_invitation():
|
|||||||
workspace = WorkspaceFactory.create()
|
workspace = WorkspaceFactory.create()
|
||||||
user = UserFactory.create()
|
user = UserFactory.create()
|
||||||
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
ws_role = WorkspaceRoleFactory.create(user=user, workspace=workspace)
|
||||||
invite = Invitations.create(workspace.owner, ws_role)
|
invite = Invitations.create(workspace.owner, ws_role, user.email)
|
||||||
Invitations.resend(workspace.owner, workspace.id, invite.token)
|
Invitations.resend(workspace.owner, workspace.id, invite.token)
|
||||||
assert ws_role.invitations[0].is_revoked
|
assert ws_role.invitations[0].is_revoked
|
||||||
assert ws_role.invitations[1].is_pending
|
assert ws_role.invitations[1].is_pending
|
||||||
|
@ -342,5 +342,6 @@ class InvitationFactory(Base):
|
|||||||
class Meta:
|
class Meta:
|
||||||
model = Invitation
|
model = Invitation
|
||||||
|
|
||||||
|
email = factory.Faker("email")
|
||||||
status = InvitationStatus.PENDING
|
status = InvitationStatus.PENDING
|
||||||
expiration_time = Invitations.current_expiration_time()
|
expiration_time = Invitations.current_expiration_time()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import datetime
|
import datetime
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
|
import pytest
|
||||||
|
|
||||||
from tests.factories import (
|
from tests.factories import (
|
||||||
UserFactory,
|
UserFactory,
|
||||||
@ -339,6 +340,29 @@ def test_existing_member_accepts_valid_invite(client, user_session):
|
|||||||
assert len(Workspaces.for_user(user)) == 1
|
assert len(Workspaces.for_user(user)) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_existing_member_invite_sent_to_email_submitted_in_form(
|
||||||
|
client, user_session, queue
|
||||||
|
):
|
||||||
|
workspace = WorkspaceFactory.create()
|
||||||
|
user = UserFactory.create()
|
||||||
|
member_form_data = {
|
||||||
|
"dod_id": user.dod_id,
|
||||||
|
"first_name": user.first_name,
|
||||||
|
"last_name": user.last_name,
|
||||||
|
"workspace_role": "developer",
|
||||||
|
"email": "example@example.com",
|
||||||
|
}
|
||||||
|
user_session(workspace.owner)
|
||||||
|
client.post(
|
||||||
|
url_for("workspaces.create_member", workspace_id=workspace.id),
|
||||||
|
data={**member_form_data},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert user.email != "example@example.com"
|
||||||
|
assert len(queue.get_queue().jobs[0].args[0]) == 1
|
||||||
|
assert queue.get_queue().jobs[0].args[0][0] == "example@example.com"
|
||||||
|
|
||||||
|
|
||||||
def test_new_member_accepts_valid_invite(monkeypatch, client, user_session):
|
def test_new_member_accepts_valid_invite(monkeypatch, client, user_session):
|
||||||
workspace = WorkspaceFactory.create()
|
workspace = WorkspaceFactory.create()
|
||||||
user_info = UserFactory.dictionary()
|
user_info = UserFactory.dictionary()
|
||||||
@ -478,3 +502,32 @@ def test_resend_invitation_sends_email(client, user_session, queue):
|
|||||||
)
|
)
|
||||||
|
|
||||||
assert len(queue.get_queue()) == 1
|
assert len(queue.get_queue()) == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_existing_member_invite_resent_to_email_submitted_in_form(
|
||||||
|
client, user_session, queue
|
||||||
|
):
|
||||||
|
workspace = WorkspaceFactory.create()
|
||||||
|
user = UserFactory.create()
|
||||||
|
ws_role = WorkspaceRoleFactory.create(
|
||||||
|
user=user, workspace=workspace, status=WorkspaceRoleStatus.PENDING
|
||||||
|
)
|
||||||
|
invite = InvitationFactory.create(
|
||||||
|
user_id=user.id,
|
||||||
|
workspace_role_id=ws_role.id,
|
||||||
|
status=InvitationStatus.PENDING,
|
||||||
|
email="example@example.com",
|
||||||
|
)
|
||||||
|
user_session(workspace.owner)
|
||||||
|
client.post(
|
||||||
|
url_for(
|
||||||
|
"workspaces.resend_invitation",
|
||||||
|
workspace_id=workspace.id,
|
||||||
|
token=invite.token,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
send_mail_job = queue.get_queue().jobs[0]
|
||||||
|
assert user.email != "example@example.com"
|
||||||
|
assert send_mail_job.func.__func__.__name__ == "_send_mail"
|
||||||
|
assert send_mail_job.args[0] == ["example@example.com"]
|
||||||
|
Loading…
x
Reference in New Issue
Block a user