mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-21 07:51:13 -05:00
[WIP] added endpoint and dao to create invites for users.
Droped token as later code to send email invite can generate timebased url to send to user. That can then be checked against configurable time threshold for expiry. Therefore no need to store a token.
This commit is contained in:
@@ -45,6 +45,7 @@ def create_app():
|
|||||||
from app.status.healthcheck import status as status_blueprint
|
from app.status.healthcheck import status as status_blueprint
|
||||||
from app.job.rest import job as job_blueprint
|
from app.job.rest import job as job_blueprint
|
||||||
from app.notifications.rest import notifications as notifications_blueprint
|
from app.notifications.rest import notifications as notifications_blueprint
|
||||||
|
from app.invite.rest import invite as invite_blueprint
|
||||||
|
|
||||||
application.register_blueprint(service_blueprint, url_prefix='/service')
|
application.register_blueprint(service_blueprint, url_prefix='/service')
|
||||||
application.register_blueprint(user_blueprint, url_prefix='/user')
|
application.register_blueprint(user_blueprint, url_prefix='/user')
|
||||||
@@ -52,6 +53,7 @@ def create_app():
|
|||||||
application.register_blueprint(status_blueprint, url_prefix='/status')
|
application.register_blueprint(status_blueprint, url_prefix='/status')
|
||||||
application.register_blueprint(notifications_blueprint, url_prefix='/notifications')
|
application.register_blueprint(notifications_blueprint, url_prefix='/notifications')
|
||||||
application.register_blueprint(job_blueprint)
|
application.register_blueprint(job_blueprint)
|
||||||
|
application.register_blueprint(invite_blueprint)
|
||||||
|
|
||||||
return application
|
return application
|
||||||
|
|
||||||
|
|||||||
6
app/dao/invited_user_dao.py
Normal file
6
app/dao/invited_user_dao.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from app import db
|
||||||
|
|
||||||
|
|
||||||
|
def save_invited_user(invited_user):
|
||||||
|
db.session.add(invited_user)
|
||||||
|
db.session.commit()
|
||||||
0
app/invite/__init__.py
Normal file
0
app/invite/__init__.py
Normal file
24
app/invite/rest.py
Normal file
24
app/invite/rest.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
from flask import (
|
||||||
|
Blueprint,
|
||||||
|
request,
|
||||||
|
jsonify
|
||||||
|
)
|
||||||
|
|
||||||
|
from app.dao.invited_user_dao import save_invited_user
|
||||||
|
from app.schemas import invited_user_schema
|
||||||
|
|
||||||
|
invite = Blueprint('invite', __name__, url_prefix='/service/<service_id>/invite')
|
||||||
|
|
||||||
|
from app.errors import register_errors
|
||||||
|
register_errors(invite)
|
||||||
|
|
||||||
|
|
||||||
|
@invite.route('', methods=['POST'])
|
||||||
|
def create_invite_user(service_id):
|
||||||
|
invited_user, errors = invited_user_schema.load(request.get_json())
|
||||||
|
if errors:
|
||||||
|
return jsonify(result="error", message=errors), 400
|
||||||
|
|
||||||
|
save_invited_user(invited_user)
|
||||||
|
|
||||||
|
return jsonify(data=invited_user_schema.dump(invited_user).data), 201
|
||||||
@@ -239,7 +239,6 @@ class InvitedUser(db.Model):
|
|||||||
from_user = db.relationship('User')
|
from_user = db.relationship('User')
|
||||||
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, unique=False)
|
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, unique=False)
|
||||||
service = db.relationship('Service')
|
service = db.relationship('Service')
|
||||||
_token = db.Column(db.String, nullable=False)
|
|
||||||
created_at = db.Column(
|
created_at = db.Column(
|
||||||
db.DateTime,
|
db.DateTime,
|
||||||
index=False,
|
index=False,
|
||||||
@@ -247,15 +246,4 @@ class InvitedUser(db.Model):
|
|||||||
nullable=False,
|
nullable=False,
|
||||||
default=datetime.datetime.now)
|
default=datetime.datetime.now)
|
||||||
status = db.Column(
|
status = db.Column(
|
||||||
db.Enum(*INVITED_USER_STATUS_TYPES, name='invited_users_status_types'), nullable=False, default='invited')
|
db.Enum(*INVITED_USER_STATUS_TYPES, name='invited_users_status_types'), nullable=False, default='pending')
|
||||||
|
|
||||||
@property
|
|
||||||
def token(self):
|
|
||||||
raise AttributeError("Token not readable")
|
|
||||||
|
|
||||||
@token.setter
|
|
||||||
def token(self, token):
|
|
||||||
self._token = hashpw(token)
|
|
||||||
|
|
||||||
def check_token(self, token):
|
|
||||||
return check_hash(token, self._token)
|
|
||||||
|
|||||||
@@ -118,6 +118,16 @@ class NotificationStatusSchema(BaseSchema):
|
|||||||
model = models.Notification
|
model = models.Notification
|
||||||
|
|
||||||
|
|
||||||
|
class InvitedUserSchema(BaseSchema):
|
||||||
|
class Meta:
|
||||||
|
model = models.InvitedUser
|
||||||
|
|
||||||
|
@validates('email_address')
|
||||||
|
def validate_to(self, value):
|
||||||
|
if not email_regex.match(value):
|
||||||
|
raise ValidationError('Invalid email')
|
||||||
|
|
||||||
|
|
||||||
user_schema = UserSchema()
|
user_schema = UserSchema()
|
||||||
user_schema_load_json = UserSchema(load_json=True)
|
user_schema_load_json = UserSchema(load_json=True)
|
||||||
users_schema = UserSchema(many=True)
|
users_schema = UserSchema(many=True)
|
||||||
@@ -142,3 +152,5 @@ email_notification_schema = EmailNotificationSchema()
|
|||||||
notification_status_schema = NotificationStatusSchema()
|
notification_status_schema = NotificationStatusSchema()
|
||||||
notifications_status_schema = NotificationStatusSchema(many=True)
|
notifications_status_schema = NotificationStatusSchema(many=True)
|
||||||
notification_status_schema_load_json = NotificationStatusSchema(load_json=True)
|
notification_status_schema_load_json = NotificationStatusSchema(load_json=True)
|
||||||
|
invited_user_schema = InvitedUserSchema()
|
||||||
|
invited_users_schema = InvitedUserSchema(many=True)
|
||||||
|
|||||||
26
migrations/versions/0023_drop_token.py
Normal file
26
migrations/versions/0023_drop_token.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 0023_drop_token
|
||||||
|
Revises: 0022_add_invite_users
|
||||||
|
Create Date: 2016-02-24 13:58:04.440296
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0023_drop_token'
|
||||||
|
down_revision = '0022_add_invite_users'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('invited_users', '_token')
|
||||||
|
### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('invited_users', sa.Column('_token', sa.VARCHAR(), autoincrement=False, nullable=False))
|
||||||
|
### end Alembic commands ###
|
||||||
23
tests/app/dao/test_invited_user_dao.py
Normal file
23
tests/app/dao/test_invited_user_dao.py
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
|
||||||
|
from app.models import InvitedUser
|
||||||
|
|
||||||
|
from app.dao.invited_user_dao import save_invited_user
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_invited_user(notify_db, notify_db_session, sample_service):
|
||||||
|
assert InvitedUser.query.count() == 0
|
||||||
|
email_address = 'invited_user@service.gov.uk'
|
||||||
|
invite_from = sample_service.users[0]
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'service': sample_service,
|
||||||
|
'email_address': email_address,
|
||||||
|
'from_user': invite_from
|
||||||
|
}
|
||||||
|
|
||||||
|
invited_user = InvitedUser(**data)
|
||||||
|
save_invited_user(invited_user)
|
||||||
|
|
||||||
|
assert InvitedUser.query.count() == 1
|
||||||
|
assert invited_user.email_address == email_address
|
||||||
|
assert invited_user.from_user == invite_from
|
||||||
0
tests/app/invite/__init__.py
Normal file
0
tests/app/invite/__init__.py
Normal file
70
tests/app/invite/test_invite_rest.py
Normal file
70
tests/app/invite/test_invite_rest.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from tests import create_authorization_header
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_invited_user(notify_api, sample_service):
|
||||||
|
with notify_api.test_request_context():
|
||||||
|
with notify_api.test_client() as client:
|
||||||
|
|
||||||
|
email_address = 'invited_user@service.gov.uk'
|
||||||
|
invite_from = sample_service.users[0]
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'service': str(sample_service.id),
|
||||||
|
'email_address': email_address,
|
||||||
|
'from_user': invite_from.id
|
||||||
|
}
|
||||||
|
|
||||||
|
data = json.dumps(data)
|
||||||
|
|
||||||
|
auth_header = create_authorization_header(
|
||||||
|
path='/service/{}/invite'.format(sample_service.id),
|
||||||
|
method='POST',
|
||||||
|
request_body=data
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
'/service/{}/invite'.format(sample_service.id),
|
||||||
|
headers=[('Content-Type', 'application/json'), auth_header],
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
assert response.status_code == 201
|
||||||
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
|
|
||||||
|
assert json_resp['data']['service'] == str(sample_service.id)
|
||||||
|
assert json_resp['data']['email_address'] == email_address
|
||||||
|
assert json_resp['data']['from_user'] == invite_from.id
|
||||||
|
assert json_resp['data']['id']
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_invited_user_invalid_email(notify_api, sample_service):
|
||||||
|
with notify_api.test_request_context():
|
||||||
|
with notify_api.test_client() as client:
|
||||||
|
|
||||||
|
email_address = 'notanemail'
|
||||||
|
invite_from = sample_service.users[0]
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'service': str(sample_service.id),
|
||||||
|
'email_address': email_address,
|
||||||
|
'from_user': invite_from.id
|
||||||
|
}
|
||||||
|
|
||||||
|
data = json.dumps(data)
|
||||||
|
|
||||||
|
auth_header = create_authorization_header(
|
||||||
|
path='/service/{}/invite'.format(sample_service.id),
|
||||||
|
method='POST',
|
||||||
|
request_body=data
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.post(
|
||||||
|
'/service/{}/invite'.format(sample_service.id),
|
||||||
|
headers=[('Content-Type', 'application/json'), auth_header],
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
assert response.status_code == 400
|
||||||
|
json_resp = json.loads(response.get_data(as_text=True))
|
||||||
|
assert json_resp['result'] == 'error'
|
||||||
|
assert json_resp['message'] == {'email_address': ['Invalid email']}
|
||||||
Reference in New Issue
Block a user