Merge pull request #1034 from alphagov/push-inbound-sms

Push inbound sms
This commit is contained in:
Rebecca Law
2017-06-19 15:15:07 +01:00
committed by GitHub
21 changed files with 519 additions and 64 deletions
+1 -1
View File
@@ -73,7 +73,7 @@ def requires_auth():
for api_key in service.api_keys:
try:
get_decode_errors(auth_token, api_key.unsigned_secret)
get_decode_errors(auth_token, api_key.secret)
except TokenDecodeError:
continue
-12
View File
@@ -1,12 +0,0 @@
from flask import current_app
from itsdangerous import URLSafeSerializer
def get_secret(secret):
serializer = URLSafeSerializer(current_app.config.get('SECRET_KEY'))
return serializer.loads(secret, salt=current_app.config.get('DANGEROUS_SALT'))
def generate_secret(token):
serializer = URLSafeSerializer(current_app.config.get('SECRET_KEY'))
return serializer.dumps(str(token), current_app.config.get('DANGEROUS_SALT'))
+4 -5
View File
@@ -1,14 +1,13 @@
import uuid
from datetime import datetime
from app import db
from app import db, encryption
from app.models import ApiKey
from app.dao.dao_utils import (
transactional,
version_class
)
from app.authentication.utils import generate_secret
@transactional
@@ -16,7 +15,7 @@ from app.authentication.utils import generate_secret
def save_model_api_key(api_key):
if not api_key.id:
api_key.id = uuid.uuid4() # must be set now so version history model can use same id
api_key.secret = generate_secret(uuid.uuid4())
api_key.secret = uuid.uuid4()
db.session.add(api_key)
@@ -39,7 +38,7 @@ def get_unsigned_secrets(service_id):
This method can only be exposed to the Authentication of the api calls.
"""
api_keys = ApiKey.query.filter_by(service_id=service_id, expiry_date=None).all()
keys = [x.unsigned_secret for x in api_keys]
keys = [x.secret for x in api_keys]
return keys
@@ -48,4 +47,4 @@ def get_unsigned_secret(key_id):
This method can only be exposed to the Authentication of the api calls.
"""
api_key = ApiKey.query.filter_by(id=key_id, expiry_date=None).one()
return api_key.unsigned_secret
return api_key.secret
+32
View File
@@ -0,0 +1,32 @@
from datetime import datetime
from app import db, create_uuid
from app.dao.dao_utils import transactional, version_class
from app.models import ServiceInboundApi
@transactional
@version_class(ServiceInboundApi)
def save_service_inbound_api(service_inbound_api):
service_inbound_api.id = create_uuid()
service_inbound_api.created_at == datetime.utcnow()
service_inbound_api.bearer_token = service_inbound_api.bearer_token
db.session.add(service_inbound_api)
@transactional
@version_class(ServiceInboundApi)
def reset_service_inbound_api(service_inbound_api, updated_by_id, url=None, bearer_token=None):
if url:
service_inbound_api.url = url
if bearer_token:
service_inbound_api.bearer_token = bearer_token
service_inbound_api.updated_by_id = updated_by_id
service_inbound_api.updated_at = datetime.utcnow()
db.session.add(service_inbound_api)
def get_service_inbound_api(service_inbound_api_id, service_id):
return ServiceInboundApi.query.filter_by(id=service_inbound_api_id,
service_id=service_id).first()
+44 -4
View File
@@ -22,7 +22,6 @@ from app.encryption import (
hashpw,
check_hash
)
from app.authentication.utils import get_secret
from app import (
db,
encryption,
@@ -295,12 +294,46 @@ class ServiceWhitelist(db.Model):
return 'Recipient {} of type: {}'.format(self.recipient, self.recipient_type)
class ServiceInboundApi(db.Model, Versioned):
__tablename__ = 'service_inbound_api'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False, unique=True)
service = db.relationship('Service', backref='inbound_api')
url = db.Column(db.String(), nullable=False)
_bearer_token = db.Column("bearer_token", db.String(), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.datetime.utcnow, nullable=False)
updated_at = db.Column(db.DateTime, nullable=True)
updated_by = db.relationship('User')
updated_by_id = db.Column(UUID(as_uuid=True), db.ForeignKey('users.id'), index=True, nullable=False)
@property
def bearer_token(self):
if self._bearer_token:
return encryption.decrypt(self._bearer_token)
return None
@bearer_token.setter
def bearer_token(self, bearer_token):
if bearer_token:
self._bearer_token = encryption.encrypt(str(bearer_token))
def serialize(self):
return {
"id": str(self.id),
"service_id": str(self.service_id),
"url": self.url,
"updated_by_id": str(self.updated_by_id),
"created_at": self.created_at.strftime(DATETIME_FORMAT),
"updated_at": self.updated_at.strftime(DATETIME_FORMAT) if self.updated_at else None
}
class ApiKey(db.Model, Versioned):
__tablename__ = 'api_keys'
id = db.Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
name = db.Column(db.String(255), nullable=False)
secret = db.Column(db.String(255), unique=True, nullable=False)
_secret = db.Column("secret", db.String(255), unique=True, nullable=False)
service_id = db.Column(UUID(as_uuid=True), db.ForeignKey('services.id'), index=True, nullable=False)
service = db.relationship('Service', backref='api_keys')
key_type = db.Column(db.String(255), db.ForeignKey('key_types.name'), index=True, nullable=False)
@@ -325,8 +358,15 @@ class ApiKey(db.Model, Versioned):
)
@property
def unsigned_secret(self):
return get_secret(self.secret)
def secret(self):
if self._secret:
return encryption.decrypt(self._secret)
return None
@secret.setter
def secret(self, secret):
if secret:
self._secret = encryption.encrypt(str(secret))
KEY_TYPE_NORMAL = 'normal'
+9 -1
View File
@@ -53,12 +53,20 @@ def build_error_message(errors):
fields.append({"error": "ValidationError", "message": field})
message = {
"status_code": 400,
"errors": fields
"errors": unique_errors(fields)
}
return json.dumps(message)
def unique_errors(dups):
unique = []
for x in dups:
if x not in unique:
unique.append(x)
return unique
def __format_message(e):
def get_path(e):
error_path = None
+10
View File
@@ -18,3 +18,13 @@ personalisation = {
"code": "1001", # yet to be implemented
"link": "link to our error documentation not yet implemented"
}
https_url = {
"type": "string",
"format": "uri",
"pattern": "^https.*",
"validationMessage": "is not a valid https url",
"code": "1001", # yet to be implemented
"link": "link to our error documentation not yet implemented"
}
+1 -1
View File
@@ -338,7 +338,7 @@ class ApiKeySchema(BaseSchema):
class Meta:
model = models.ApiKey
exclude = ("service", "secret")
exclude = ("service", "_secret")
strict = True
+61 -1
View File
@@ -8,6 +8,7 @@ from flask import (
current_app,
Blueprint
)
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm.exc import NoResultFound
from app import redis_store
@@ -20,6 +21,11 @@ from app.dao.api_key_dao import (
expire_api_key)
from app.dao.date_util import get_financial_year
from app.dao.notification_usage_dao import get_total_billable_units_for_sent_sms_notifications_in_date_range
from app.dao.service_inbound_api_dao import (
save_service_inbound_api,
reset_service_inbound_api,
get_service_inbound_api
)
from app.dao.services_dao import (
dao_fetch_service_by_id,
dao_fetch_all_services,
@@ -49,8 +55,10 @@ from app.errors import (
InvalidRequest,
register_errors
)
from app.models import Service
from app.models import Service, ServiceInboundApi
from app.schema_validation import validate
from app.service import statistics
from app.service.service_inbound_api_schema import service_inbound_api, update_service_inbound_api_schema
from app.service.utils import get_whitelist_objects
from app.service.sender import send_notification_to_service_users
from app.schemas import (
@@ -531,3 +539,55 @@ def get_yearly_monthly_usage(service_id):
return json.dumps(json_results)
except TypeError:
return jsonify(result='error', message='No valid year provided'), 400
@service_blueprint.route('/<uuid:service_id>/inbound-api', methods=['POST'])
def create_service_inbound_api(service_id):
data = request.get_json()
validate(data, service_inbound_api)
data["service_id"] = service_id
inbound_api = ServiceInboundApi(**data)
try:
save_service_inbound_api(inbound_api)
except SQLAlchemyError as e:
return handle_sql_errror(e)
return jsonify(data=inbound_api.serialize()), 201
@service_blueprint.route('/<uuid:service_id>/inbound-api/<uuid:inbound_api_id>', methods=['POST'])
def update_service_inbound_api(service_id, inbound_api_id):
data = request.get_json()
validate(data, update_service_inbound_api_schema)
to_update = get_service_inbound_api(inbound_api_id, service_id)
reset_service_inbound_api(service_inbound_api=to_update,
updated_by_id=data["updated_by_id"],
url=data.get("url", None),
bearer_token=data.get("bearer_token", None))
return jsonify(data=to_update.serialize()), 200
@service_blueprint.route('/<uuid:service_id>/inbound-api/<uuid:inbound_api_id>', methods=["GET"])
def fetch_service_inbound_api(service_id, inbound_api_id):
inbound_api = get_service_inbound_api(inbound_api_id, service_id)
return jsonify(data=inbound_api.serialize()), 200
def handle_sql_errror(e):
if hasattr(e, 'orig') and hasattr(e.orig, 'pgerror') and e.orig.pgerror \
and ('duplicate key value violates unique constraint "ix_service_inbound_api_service_id"'
in e.orig.pgerror):
return jsonify(
result='error',
message={'name': ["You can only have one URL and bearer token for your service."]}
), 400
elif hasattr(e, 'orig') and hasattr(e.orig, 'pgerror') and e.orig.pgerror \
and ('insert or update on table "service_inbound_api" violates '
'foreign key constraint "service_inbound_api_service_id_fkey"'
in e.orig.pgerror):
return jsonify(result='error', message="No result found"), 404
else:
raise e
+27
View File
@@ -0,0 +1,27 @@
from app.schema_validation.definitions import uuid, https_url
service_inbound_api = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "POST service inbound api schema",
"type": "object",
"title": "Create service inbound api",
"properties": {
"url": https_url,
"bearer_token": {"type": "string", "minLength": 10},
"updated_by_id": uuid
},
"required": ["url", "bearer_token", "updated_by_id"]
}
update_service_inbound_api_schema = {
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "POST service inbound api schema",
"type": "object",
"title": "Create service inbound api",
"properties": {
"url": https_url,
"bearer_token": {"type": "string", "minLength": 10},
"updated_by_id": uuid
},
"required": ["updated_by_id"]
}
@@ -0,0 +1,56 @@
"""empty message
Revision ID: 0098_service_inbound_api
Revises: 0097_notnull_inbound_provider
Create Date: 2017-06-13 15:02:33.609656
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
revision = '0098_service_inbound_api'
down_revision = '0097_notnull_inbound_provider'
def upgrade():
op.create_table('service_inbound_api_history',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('service_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('url', sa.String(), nullable=False),
sa.Column('bearer_token', sa.String(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('updated_by_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('version', sa.Integer(), autoincrement=False, nullable=False),
sa.PrimaryKeyConstraint('id', 'version')
)
op.create_index(op.f('ix_service_inbound_api_history_service_id'), 'service_inbound_api_history', ['service_id'],
unique=False)
op.create_index(op.f('ix_service_inbound_api_history_updated_by_id'), 'service_inbound_api_history',
['updated_by_id'], unique=False)
op.create_table('service_inbound_api',
sa.Column('id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('service_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('url', sa.String(), nullable=False),
sa.Column('bearer_token', sa.String(), nullable=False),
sa.Column('created_at', sa.DateTime(), nullable=False),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.Column('updated_by_id', postgresql.UUID(as_uuid=True), nullable=False),
sa.Column('version', sa.Integer(), nullable=False),\
sa.ForeignKeyConstraint(['service_id'], ['services.id'], ),
sa.ForeignKeyConstraint(['updated_by_id'], ['users.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_service_inbound_api_service_id'), 'service_inbound_api', ['service_id'], unique=True)
op.create_index(op.f('ix_service_inbound_api_updated_by_id'), 'service_inbound_api', ['updated_by_id'],
unique=False)
def downgrade():
op.drop_index(op.f('ix_service_inbound_api_updated_by_id'), table_name='service_inbound_api')
op.drop_index(op.f('ix_service_inbound_api_service_id'), table_name='service_inbound_api')
op.drop_table('service_inbound_api')
op.drop_index(op.f('ix_service_inbound_api_history_updated_by_id'), table_name='service_inbound_api_history')
op.drop_index(op.f('ix_service_inbound_api_history_service_id'), table_name='service_inbound_api_history')
op.drop_table('service_inbound_api_history')
+2 -2
View File
@@ -11,7 +11,7 @@ def create_authorization_header(service_id=None, key_type=KEY_TYPE_NORMAL):
client_id = str(service_id)
secrets = ApiKey.query.filter_by(service_id=service_id, key_type=key_type).all()
if secrets:
secret = secrets[0].unsigned_secret
secret = secrets[0].secret
else:
service = dao_fetch_service_by_id(service_id)
data = {
@@ -22,7 +22,7 @@ def create_authorization_header(service_id=None, key_type=KEY_TYPE_NORMAL):
}
api_key = ApiKey(**data)
save_model_api_key(api_key)
secret = api_key.unsigned_secret
secret = api_key.secret
else:
client_id = current_app.config.get('ADMIN_CLIENT_USER_NAME')
-8
View File
@@ -1,8 +0,0 @@
from app.authentication.utils import generate_secret, get_secret
def test_secret_is_signed_and_can_be_read_again(notify_api):
with notify_api.test_request_context():
signed_secret = generate_secret('some_uuid')
assert signed_secret != 'some_uuid'
assert 'some_uuid' == get_secret(signed_secret)
+5 -5
View File
@@ -4,7 +4,7 @@ import pytest
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm.exc import NoResultFound
from app.authentication.utils import get_secret
from app import encryption
from app.dao.api_key_dao import (save_model_api_key,
get_model_api_keys,
get_unsigned_secrets,
@@ -63,14 +63,14 @@ def test_should_return_api_key_for_service(notify_api, notify_db, notify_db_sess
def test_should_return_unsigned_api_keys_for_service_id(sample_api_key):
unsigned_api_key = get_unsigned_secrets(sample_api_key.service_id)
assert len(unsigned_api_key) == 1
assert sample_api_key.secret != unsigned_api_key[0]
assert unsigned_api_key[0] == get_secret(sample_api_key.secret)
assert sample_api_key._secret != unsigned_api_key[0]
assert unsigned_api_key[0] == sample_api_key.secret
def test_get_unsigned_secret_returns_key(sample_api_key):
unsigned_api_key = get_unsigned_secret(sample_api_key.id)
assert sample_api_key.secret != unsigned_api_key
assert unsigned_api_key == get_secret(sample_api_key.secret)
assert sample_api_key._secret != unsigned_api_key
assert unsigned_api_key == sample_api_key.secret
def test_should_not_allow_duplicate_key_names_per_service(sample_api_key, fake_uuid):
@@ -0,0 +1,117 @@
import uuid
import pytest
from sqlalchemy.exc import SQLAlchemyError
from app import encryption
from app.dao.service_inbound_api_dao import (
save_service_inbound_api,
reset_service_inbound_api,
get_service_inbound_api
)
from app.models import ServiceInboundApi
def test_save_service_inbound_api(sample_service):
service_inbound_api = ServiceInboundApi(
service_id=sample_service.id,
url="https://some_service/inbound_messages",
bearer_token="some_unique_string",
updated_by_id=sample_service.users[0].id
)
save_service_inbound_api(service_inbound_api)
results = ServiceInboundApi.query.all()
assert len(results) == 1
inbound_api = results[0]
assert inbound_api.id is not None
assert inbound_api.service_id == sample_service.id
assert inbound_api.updated_by_id == sample_service.users[0].id
assert inbound_api.url == "https://some_service/inbound_messages"
assert inbound_api.bearer_token == "some_unique_string"
assert inbound_api._bearer_token != "some_unique_string"
assert inbound_api.updated_at is None
versioned = ServiceInboundApi.get_history_model().query.filter_by(id=inbound_api.id).one()
assert versioned.id == inbound_api.id
assert versioned.service_id == sample_service.id
assert versioned.updated_by_id == sample_service.users[0].id
assert versioned.url == "https://some_service/inbound_messages"
assert encryption.decrypt(versioned._bearer_token) == "some_unique_string"
assert versioned.updated_at is None
assert versioned.version == 1
def test_save_service_inbound_api_fails_if_service_does_not_exist(notify_db, notify_db_session):
service_inbound_api = ServiceInboundApi(
service_id=uuid.uuid4(),
url="https://some_service/inbound_messages",
bearer_token="some_unique_string",
updated_by_id=uuid.uuid4()
)
with pytest.raises(SQLAlchemyError):
save_service_inbound_api(service_inbound_api)
def test_update_service_inbound_api(sample_service):
service_inbound_api = ServiceInboundApi(
service_id=sample_service.id,
url="https://some_service/inbound_messages",
bearer_token="some_unique_string",
updated_by_id=sample_service.users[0].id
)
save_service_inbound_api(service_inbound_api)
results = ServiceInboundApi.query.all()
assert len(results) == 1
saved_inbound_api = results[0]
reset_service_inbound_api(saved_inbound_api, updated_by_id=sample_service.users[0].id,
url="https://some_service/changed_url")
updated_results = ServiceInboundApi.query.all()
assert len(updated_results) == 1
updated = updated_results[0]
assert updated.id is not None
assert updated.service_id == sample_service.id
assert updated.updated_by_id == sample_service.users[0].id
assert updated.url == "https://some_service/changed_url"
assert updated.bearer_token == "some_unique_string"
assert updated._bearer_token != "some_unique_string"
assert updated.updated_at is not None
versioned_results = ServiceInboundApi.get_history_model().query.filter_by(id=saved_inbound_api.id).all()
assert len(versioned_results) == 2
for x in versioned_results:
if x.version == 1:
assert x.url == "https://some_service/inbound_messages"
assert not x.updated_at
elif x.version == 2:
assert x.url == "https://some_service/changed_url"
assert x.updated_at
else:
pytest.fail("version should not exist")
assert x.id is not None
assert x.service_id == sample_service.id
assert x.updated_by_id == sample_service.users[0].id
assert encryption.decrypt(x._bearer_token) == "some_unique_string"
def test_get_service_inbound_api(sample_service):
service_inbound_api = ServiceInboundApi(
service_id=sample_service.id,
url="https://some_service/inbound_messages",
bearer_token="some_unique_string",
updated_by_id=sample_service.users[0].id
)
save_service_inbound_api(service_inbound_api)
inbound_api = get_service_inbound_api(service_inbound_api.id, sample_service.id)
assert inbound_api.id is not None
assert inbound_api.service_id == sample_service.id
assert inbound_api.updated_by_id == sample_service.users[0].id
assert inbound_api.url == "https://some_service/inbound_messages"
assert inbound_api.bearer_token == "some_unique_string"
assert inbound_api._bearer_token != "some_unique_string"
assert inbound_api.updated_at is None
+16 -3
View File
@@ -2,10 +2,9 @@ from datetime import datetime
import uuid
from app.dao.inbound_sms_dao import dao_create_inbound_sms
from app.dao.jobs_dao import dao_create_job
from app.dao.service_inbound_api_dao import save_service_inbound_api
from app.models import (
InboundSms,
Service,
User,
Template,
@@ -17,7 +16,7 @@ from app.models import (
EMAIL_TYPE,
SMS_TYPE,
KEY_TYPE_NORMAL,
)
ServiceInboundApi)
from app.dao.users_dao import save_model_user
from app.dao.notifications_dao import dao_create_notification, dao_created_scheduled_notification
from app.dao.templates_dao import dao_create_template
@@ -212,3 +211,17 @@ def create_inbound_sms(
)
dao_create_inbound_sms(inbound)
return inbound
def create_service_inbound_api(
service,
url="https://something.com",
bearer_token="some_super_secret",
):
service_inbound_api = ServiceInboundApi(service_id=service.id,
url=url,
bearer_token=bearer_token,
updated_by_id=service.users[0].id
)
save_service_inbound_api(service_inbound_api)
return service_inbound_api
@@ -321,7 +321,6 @@ def test_should_allow_valid_sms_notification(notify_api, sample_template, mocker
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocked = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
data = {
'to': '07700 900 855',
@@ -374,7 +373,6 @@ def test_should_allow_valid_email_notification(notify_api, sample_email_template
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
data = {
'to': 'ok@ok.com',
@@ -411,7 +409,6 @@ def test_should_block_api_call_if_over_day_limit_for_live_service(
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
service = create_sample_service(notify_db, notify_db_session, limit=1, restricted=False)
email_template = create_sample_email_template(notify_db, notify_db_session, service=service)
@@ -443,7 +440,6 @@ def test_should_block_api_call_if_over_day_limit_for_restricted_service(
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
service = create_sample_service(notify_db, notify_db_session, limit=1, restricted=True)
email_template = create_sample_email_template(notify_db, notify_db_session, service=service)
@@ -479,7 +475,6 @@ def test_should_allow_api_call_if_under_day_limit_regardless_of_type(
with notify_api.test_request_context():
with notify_api.test_client() as client:
mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
service = create_sample_service(notify_db, notify_db_session, limit=2, restricted=restricted)
email_template = create_sample_email_template(notify_db, notify_db_session, service=service)
@@ -586,7 +581,7 @@ def test_should_send_email_if_team_api_key_and_a_service_user(notify_api, sample
created_by=sample_email_template.created_by,
key_type=KEY_TYPE_TEAM)
save_model_api_key(api_key)
auth_header = create_jwt_token(secret=api_key.unsigned_secret, client_id=str(api_key.service_id))
auth_header = create_jwt_token(secret=api_key.secret, client_id=str(api_key.service_id))
response = client.post(
path='/notifications/email',
@@ -619,7 +614,7 @@ def test_should_send_sms_to_anyone_with_test_key(
key_type=KEY_TYPE_TEST
)
save_model_api_key(api_key)
auth_header = create_jwt_token(secret=api_key.unsigned_secret, client_id=str(api_key.service_id))
auth_header = create_jwt_token(secret=api_key.secret, client_id=str(api_key.service_id))
response = client.post(
path='/notifications/sms',
@@ -654,7 +649,7 @@ def test_should_send_email_to_anyone_with_test_key(
key_type=KEY_TYPE_TEST
)
save_model_api_key(api_key)
auth_header = create_jwt_token(secret=api_key.unsigned_secret, client_id=str(api_key.service_id))
auth_header = create_jwt_token(secret=api_key.secret, client_id=str(api_key.service_id))
response = client.post(
path='/notifications/email',
@@ -682,7 +677,7 @@ def test_should_send_sms_if_team_api_key_and_a_service_user(notify_api, sample_t
created_by=sample_template.created_by,
key_type=KEY_TYPE_TEAM)
save_model_api_key(api_key)
auth_header = create_jwt_token(secret=api_key.unsigned_secret, client_id=str(api_key.service_id))
auth_header = create_jwt_token(secret=api_key.secret, client_id=str(api_key.service_id))
response = client.post(
path='/notifications/sms',
@@ -715,7 +710,7 @@ def test_should_persist_notification(notify_api, sample_template,
created_by=template.created_by,
key_type=KEY_TYPE_TEAM)
save_model_api_key(api_key)
auth_header = create_jwt_token(secret=api_key.unsigned_secret, client_id=str(api_key.service_id))
auth_header = create_jwt_token(secret=api_key.secret, client_id=str(api_key.service_id))
response = client.post(
path='/notifications/{}'.format(template_type),
@@ -758,7 +753,7 @@ def test_should_delete_notification_and_return_error_if_sqs_fails(
created_by=template.created_by,
key_type=KEY_TYPE_TEAM)
save_model_api_key(api_key)
auth_header = create_jwt_token(secret=api_key.unsigned_secret, client_id=str(api_key.service_id))
auth_header = create_jwt_token(secret=api_key.secret, client_id=str(api_key.service_id))
response = client.post(
path='/notifications/{}'.format(template_type),
@@ -862,7 +857,7 @@ def test_should_not_send_notification_to_non_whitelist_recipient_in_trial_mode(
}
api_key = create_sample_api_key(notify_db, notify_db_session, service, key_type=key_type)
auth_header = create_jwt_token(secret=api_key.unsigned_secret, client_id=str(api_key.service_id))
auth_header = create_jwt_token(secret=api_key.secret, client_id=str(api_key.service_id))
response = client.post(
path='/notifications/{}'.format(notification_type),
@@ -923,7 +918,7 @@ def test_should_send_notification_to_whitelist_recipient(
}
sample_key = create_sample_api_key(notify_db, notify_db_session, service, key_type=key_type)
auth_header = create_jwt_token(secret=sample_key.unsigned_secret, client_id=str(sample_key.service_id))
auth_header = create_jwt_token(secret=sample_key.secret, client_id=str(sample_key.service_id))
response = client.post(
path='/notifications/{}'.format(notification_type),
@@ -1101,7 +1096,6 @@ def test_returns_a_429_limit_exceeded_if_rate_limit_exceeded(
def test_should_allow_store_original_number_on_sms_notification(client, sample_template, mocker):
mocked = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
data = {
'to': '+(44) 7700-900 855',
@@ -1128,7 +1122,6 @@ def test_should_allow_store_original_number_on_sms_notification(client, sample_t
def test_should_not_allow_international_number_on_sms_notification(client, sample_template, mocker):
mocked = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
data = {
'to': '20-12-1234-1234',
@@ -1151,7 +1144,6 @@ def test_should_not_allow_international_number_on_sms_notification(client, sampl
def test_should_allow_international_number_on_sms_notification(client, notify_db, notify_db_session, mocker):
mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
service = sample_service(notify_db, notify_db_session, can_send_international_sms=True)
template = create_sample_template(notify_db, notify_db_session, service=service)
+1 -1
View File
@@ -741,5 +741,5 @@ def test_get_notification_selects_correct_template_for_personalisation(client,
def _create_auth_header_from_key(api_key):
token = create_jwt_token(secret=api_key.unsigned_secret, client_id=str(api_key.service_id))
token = create_jwt_token(secret=api_key.secret, client_id=str(api_key.service_id))
return [('Authorization', 'Bearer {}'.format(token))]
@@ -21,7 +21,6 @@ def _post_notification(client, template, url, to):
def test_post_sms_contract(client, mocker, sample_template):
mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
response_json = return_json_from_response(_post_notification(
client, sample_template, url='/notifications/sms', to='07700 900 855'
@@ -31,7 +30,6 @@ def test_post_sms_contract(client, mocker, sample_template):
def test_post_email_contract(client, mocker, sample_email_template):
mocker.patch('app.celery.provider_tasks.deliver_email.apply_async')
mocker.patch('app.encryption.encrypt', return_value="something_encrypted")
response_json = return_json_from_response(_post_notification(
client, sample_email_template, url='/notifications/email', to='foo@bar.com'
+80 -2
View File
@@ -8,6 +8,7 @@ import pytest
from flask import url_for, current_app
from freezegun import freeze_time
from app import encryption
from app.dao.users_dao import save_model_user
from app.dao.services_dao import dao_remove_user_from_service
from app.models import (
@@ -17,7 +18,7 @@ from app.models import (
DVLA_ORG_LAND_REGISTRY
)
from tests import create_authorization_header
from tests.app.db import create_template
from tests.app.db import create_template, create_service_inbound_api
from tests.app.conftest import (
sample_service as create_service,
sample_user_service_permission as create_user_service_permission,
@@ -27,7 +28,6 @@ from tests.app.conftest import (
)
from tests.app.db import create_user
from tests.conftest import set_config_values
def test_get_service_list(client, service_factory):
@@ -2149,3 +2149,81 @@ def test_search_for_notification_by_to_field_returns_content(
assert notifications[0]['id'] == str(notification.id)
assert notifications[0]['to'] == '+447700900855'
assert notifications[0]['body'] == 'Hello Foo\nYour thing is due soon'
def test_create_service_inbound_api(client, sample_service):
data = {
"url": "https://some_service/inbound-sms",
"bearer_token": "some-unique-string",
"updated_by_id": str(sample_service.users[0].id)
}
response = client.post(
'/service/{}/inbound-api'.format(sample_service.id),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), create_authorization_header()]
)
assert response.status_code == 201
resp_json = json.loads(response.get_data(as_text=True))["data"]
assert resp_json["id"]
assert resp_json["service_id"] == str(sample_service.id)
assert resp_json["url"] == "https://some_service/inbound-sms"
assert resp_json["updated_by_id"] == str(sample_service.users[0].id)
assert resp_json["created_at"]
assert not resp_json["updated_at"]
def test_set_service_inbound_api_raises_404_when_service_does_not_exist(client):
data = {
"url": "https://some_service/inbound-sms",
"bearer_token": "some-unique-string",
"updated_by_id": str(uuid.uuid4())
}
response = client.post(
'/service/{}/inbound-api'.format(uuid.uuid4()),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), create_authorization_header()]
)
assert response.status_code == 404
assert json.loads(response.get_data(as_text=True))['message'] == 'No result found'
def test_update_service_inbound_api_updates_url(client, sample_service):
service_inbound_api = create_service_inbound_api(service=sample_service,
url="https://original_url.com")
data = {
"url": "https://another_url.com",
"updated_by_id": str(sample_service.users[0].id)
}
response = client.post("/service/{}/inbound-api/{}".format(sample_service.id, service_inbound_api.id),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), create_authorization_header()])
assert response.status_code == 200
resp_json = json.loads(response.get_data(as_text=True))["data"]
assert resp_json["url"] == "https://another_url.com"
assert service_inbound_api.url == "https://another_url.com"
def test_update_service_inbound_api_updates_bearer_token(client, sample_service):
service_inbound_api = create_service_inbound_api(service=sample_service,
bearer_token="some_super_secret")
data = {
"bearer_token": "different_token",
"updated_by_id": str(sample_service.users[0].id)
}
response = client.post("/service/{}/inbound-api/{}".format(sample_service.id, service_inbound_api.id),
data=json.dumps(data),
headers=[('Content-Type', 'application/json'), create_authorization_header()])
assert response.status_code == 200
assert service_inbound_api.bearer_token == "different_token"
def test_fetch_service_inbound_api(client, sample_service):
service_inbound_api = create_service_inbound_api(service=sample_service)
response = client.get("/service/{}/inbound-api/{}".format(sample_service.id, service_inbound_api.id),
headers=[create_authorization_header()])
assert response.status_code == 200
assert json.loads(response.get_data(as_text=True))["data"] == service_inbound_api.serialize()
+45
View File
@@ -0,0 +1,45 @@
import json
import uuid
import pytest
from jsonschema import ValidationError
from app.schema_validation import validate
from app.service.service_inbound_api_schema import service_inbound_api
def test_service_inbound_api_schema_validates():
under_test = {"url": "https://some_url.for_service",
"bearer_token": "something_ten_chars",
"updated_by_id": str(uuid.uuid4())
}
validated = validate(under_test, service_inbound_api)
assert validated == under_test
@pytest.mark.parametrize("url", ["not a url", "https not a url", "http://valid.com"])
def test_service_inbound_api_schema_errors_for_url_not_valid_url(url):
under_test = {"url": url,
"bearer_token": "something_ten_chars",
"updated_by_id": str(uuid.uuid4())
}
with pytest.raises(ValidationError) as e:
validate(under_test, service_inbound_api)
errors = json.loads(str(e.value)).get('errors')
assert len(errors) == 1
assert errors[0]['message'] == "url is not a valid https url"
def test_service_inbound_api_schema_bearer_token_under_ten_char():
under_test = {"url": "https://some_url.for_service",
"bearer_token": "shorty",
"updated_by_id": str(uuid.uuid4())
}
with pytest.raises(ValidationError) as e:
validate(under_test, service_inbound_api)
errors = json.loads(str(e.value)).get('errors')
assert len(errors) == 1
assert errors[0]['message'] == "bearer_token shorty is too short"