mirror of
https://github.com/GSA/notifications-api.git
synced 2025-12-16 02:02:13 -05:00
Created endpoints for create and delete token.
This commit is contained in:
@@ -1,17 +0,0 @@
|
|||||||
from app import db
|
|
||||||
from app.models import ApiToken
|
|
||||||
|
|
||||||
|
|
||||||
def save_token_model(token, update_dict={}):
|
|
||||||
if update_dict:
|
|
||||||
del update_dict['id']
|
|
||||||
db.session.query(ApiToken).filter_by(id=token.id).update(update_dict)
|
|
||||||
else:
|
|
||||||
db.session.add(token)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
def get_model_api_tokens(token=None):
|
|
||||||
if token:
|
|
||||||
return ApiToken.query.filter_by(token=token).one()
|
|
||||||
return ApiToken.query.filter_by().all()
|
|
||||||
22
app/dao/tokens_dao.py
Normal file
22
app/dao/tokens_dao.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
from app import db
|
||||||
|
from app.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
def save_token_model(token, update_dict={}):
|
||||||
|
if update_dict:
|
||||||
|
del update_dict['id']
|
||||||
|
db.session.query(Token).filter_by(id=token.id).update(update_dict)
|
||||||
|
else:
|
||||||
|
db.session.add(token)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
def get_model_tokens(service_id=None):
|
||||||
|
if service_id:
|
||||||
|
return Token.query.filter_by(service_id=service_id).one()
|
||||||
|
return Token.query.filter_by().all()
|
||||||
|
|
||||||
|
|
||||||
|
def delete_model_token(token):
|
||||||
|
db.session.delete(token)
|
||||||
|
db.session.commit()
|
||||||
@@ -61,8 +61,8 @@ class Service(db.Model):
|
|||||||
restricted = db.Column(db.Boolean, index=False, unique=False, nullable=False)
|
restricted = db.Column(db.Boolean, index=False, unique=False, nullable=False)
|
||||||
|
|
||||||
|
|
||||||
class ApiToken(db.Model):
|
class Token(db.Model):
|
||||||
__tablename__ = 'api_tokens'
|
__tablename__ = 'tokens'
|
||||||
|
|
||||||
id = db.Column(db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
token = db.Column(db.String, unique=True, nullable=False)
|
token = db.Column(db.String, unique=True, nullable=False)
|
||||||
|
|||||||
@@ -28,15 +28,15 @@ class TemplateSchema(ma.ModelSchema):
|
|||||||
exclude = ("updated_at", "created_at", "service_id")
|
exclude = ("updated_at", "created_at", "service_id")
|
||||||
|
|
||||||
|
|
||||||
class ApiTokenSchema(ma.ModelSchema):
|
class TokenSchema(ma.ModelSchema):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.ApiToken
|
model = models.Token
|
||||||
|
|
||||||
user_schema = UserSchema()
|
user_schema = UserSchema()
|
||||||
users_schema = UserSchema(many=True)
|
users_schema = UserSchema(many=True)
|
||||||
service_schema = ServiceSchema()
|
service_schema = ServiceSchema()
|
||||||
services_schema = ServiceSchema(many=True)
|
services_schema = ServiceSchema(many=True)
|
||||||
api_token_schema = ApiTokenSchema()
|
|
||||||
api_tokens_schema = ApiTokenSchema(many=True)
|
|
||||||
template_schema = TemplateSchema()
|
template_schema = TemplateSchema()
|
||||||
templates_schema = TemplateSchema(many=True)
|
templates_schema = TemplateSchema(many=True)
|
||||||
|
token_schema = TokenSchema()
|
||||||
|
tokens_schema = TokenSchema(many=True)
|
||||||
|
|||||||
@@ -1,13 +1,20 @@
|
|||||||
from flask import (jsonify, request)
|
import uuid
|
||||||
|
from datetime import datetime
|
||||||
|
from flask import (jsonify, request, current_app)
|
||||||
from sqlalchemy.exc import DataError
|
from sqlalchemy.exc import DataError
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
from app.dao.services_dao import (
|
from app.dao.services_dao import (
|
||||||
save_model_service, get_model_services, delete_model_service)
|
save_model_service, get_model_services, delete_model_service)
|
||||||
|
from app.dao.tokens_dao import (save_token_model, get_model_tokens, delete_model_token)
|
||||||
|
from app.dao.users_dao import get_model_users
|
||||||
from app.dao.templates_dao import (
|
from app.dao.templates_dao import (
|
||||||
save_model_template, get_model_templates)
|
save_model_template, get_model_templates)
|
||||||
from app.dao import DAOException
|
from app.dao import DAOException
|
||||||
from .. import service
|
from .. import service
|
||||||
from app import db
|
from app import db
|
||||||
|
from app.schemas import (services_schema, service_schema, token_schema)
|
||||||
|
from app.models import Token
|
||||||
|
from itsdangerous import URLSafeSerializer
|
||||||
from app.schemas import (
|
from app.schemas import (
|
||||||
services_schema, service_schema, template_schema, templates_schema)
|
services_schema, service_schema, template_schema, templates_schema)
|
||||||
|
|
||||||
@@ -73,8 +80,48 @@ def get_service(service_id=None):
|
|||||||
|
|
||||||
# TODO auth to be added
|
# TODO auth to be added
|
||||||
@service.route('/<int:service_id>/token', methods=['POST'])
|
@service.route('/<int:service_id>/token', methods=['POST'])
|
||||||
def create_token():
|
def create_token(service_id=None):
|
||||||
request.get_json()
|
try:
|
||||||
|
service = get_model_services(service_id=service_id)
|
||||||
|
except DataError:
|
||||||
|
return jsonify(result="error", message="Invalid service id"), 400
|
||||||
|
except NoResultFound:
|
||||||
|
return jsonify(result="error", message="Service not found"), 404
|
||||||
|
|
||||||
|
token = _generate_token()
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
service_token = get_model_tokens(service_id=service_id)
|
||||||
|
save_token_model(service_token, update_dict={'id': service_token.id,
|
||||||
|
'token': service_token.token,
|
||||||
|
'expiry_date': datetime.now()})
|
||||||
|
except NoResultFound:
|
||||||
|
pass
|
||||||
|
save_token_model(Token(service_id=service_id, token=token))
|
||||||
|
except DAOException as e:
|
||||||
|
return jsonify(result='error', message=str(e)), 400
|
||||||
|
return jsonify(token=str(token)), 201
|
||||||
|
|
||||||
|
|
||||||
|
@service.route('/<int:service_id>/token', methods=['DELETE'])
|
||||||
|
def delete_token(service_id):
|
||||||
|
try:
|
||||||
|
token = get_model_tokens(service_id=service_id)
|
||||||
|
delete_model_token(token)
|
||||||
|
return jsonify(data=token_schema.dump(token).data), 202
|
||||||
|
except NoResultFound:
|
||||||
|
return jsonify(result="error", message="Token not found"), 404
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_token():
|
||||||
|
token = uuid.uuid4()
|
||||||
|
serializer = URLSafeSerializer(current_app.config.get('SECRET_KEY'))
|
||||||
|
return serializer.dumps(str(token), current_app.config.get('DANGEROUS_SALT'))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_token(token):
|
||||||
|
serializer = URLSafeSerializer(current_app.config.get('SECRET_KEY'))
|
||||||
|
return serializer.loads(token, salt=current_app.config.get('DANGEROUS_SALT'))
|
||||||
|
|
||||||
|
|
||||||
# TODO auth to be added.
|
# TODO auth to be added.
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
DEBUG = False
|
DEBUG = False
|
||||||
NOTIFY_LOG_LEVEL = 'DEBUG'
|
NOTIFY_LOG_LEVEL = 'DEBUG'
|
||||||
@@ -11,11 +10,15 @@ class Config(object):
|
|||||||
|
|
||||||
class Development(Config):
|
class Development(Config):
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
SECRET_KEY = 'secret-key'
|
||||||
|
DANGEROUS_SALT = 'dangerous-salt'
|
||||||
|
|
||||||
|
|
||||||
class Test(Config):
|
class Test(Config):
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/test_notification_api'
|
SQLALCHEMY_DATABASE_URI = 'postgresql://localhost/test_notification_api'
|
||||||
|
SECRET_KEY = 'secret-key'
|
||||||
|
DANGEROUS_SALT = 'dangerous-salt'
|
||||||
|
|
||||||
|
|
||||||
class Live(Config):
|
class Live(Config):
|
||||||
|
|||||||
36
migrations/versions/0002_create_tokens.py
Normal file
36
migrations/versions/0002_create_tokens.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
"""empty message
|
||||||
|
|
||||||
|
Revision ID: 0002_create_api_token
|
||||||
|
Revises: 0001_initialise_data
|
||||||
|
Create Date: 2016-01-13 10:21:14.651691
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '0002_create_api_token'
|
||||||
|
down_revision = '0001_initialise_data'
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('tokens',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('token', sa.String(), nullable=False),
|
||||||
|
sa.Column('service_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('expiry_date', sa.DateTime(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(['service_id'], ['services.id'], ),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('token')
|
||||||
|
)
|
||||||
|
op.create_index(op.f('ix_tokens_service_id'), 'tokens', ['service_id'], unique=False)
|
||||||
|
### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f('ix_tokens_service_id'), table_name='tokens')
|
||||||
|
op.drop_table('tokens')
|
||||||
|
### end Alembic commands ###
|
||||||
@@ -9,6 +9,7 @@ PyJWT==1.4.0
|
|||||||
marshmallow==2.4.2
|
marshmallow==2.4.2
|
||||||
marshmallow-sqlalchemy==0.8.0
|
marshmallow-sqlalchemy==0.8.0
|
||||||
flask-marshmallow==0.6.2
|
flask-marshmallow==0.6.2
|
||||||
|
itsdangerous==0.24
|
||||||
|
|
||||||
git+https://github.com/alphagov/notifications-python-client.git@0.1.5#egg=notifications-python-client==0.1.5
|
git+https://github.com/alphagov/notifications-python-client.git@0.1.5#egg=notifications-python-client==0.1.5
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,45 @@
|
|||||||
import uuid
|
import uuid
|
||||||
|
from app.dao import tokens_dao
|
||||||
|
|
||||||
from app.dao import api_tokens_dao
|
from app.models import Token
|
||||||
from app.models import ApiToken
|
|
||||||
|
|
||||||
|
|
||||||
def test_should_create_token(notify_api, notify_db, notify_db_session, sample_service):
|
def test_should_create_token(notify_api, notify_db, notify_db_session, sample_service):
|
||||||
token = uuid.uuid4()
|
token = uuid.uuid4()
|
||||||
api_token = ApiToken(**{'token': token, 'service_id': sample_service.id})
|
api_token = Token(**{'token': token, 'service_id': sample_service.id})
|
||||||
|
|
||||||
api_tokens_dao.save_token_model(api_token)
|
tokens_dao.save_token_model(api_token)
|
||||||
|
|
||||||
all_tokens = api_tokens_dao.get_model_api_tokens()
|
|
||||||
|
|
||||||
|
all_tokens = tokens_dao.get_model_tokens()
|
||||||
assert len(all_tokens) == 1
|
assert len(all_tokens) == 1
|
||||||
assert all_tokens[0].token == str(token)
|
assert all_tokens[0].token == str(token)
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_delete_api_token(notify_api, notify_db, notify_db_session, sample_service):
|
||||||
|
token = uuid.uuid4()
|
||||||
|
api_token = Token(**{'token': token, 'service_id': sample_service.id})
|
||||||
|
tokens_dao.save_token_model(api_token)
|
||||||
|
all_tokens = tokens_dao.get_model_tokens()
|
||||||
|
assert len(all_tokens) == 1
|
||||||
|
|
||||||
|
tokens_dao.delete_model_token(all_tokens[0])
|
||||||
|
empty_token_list = tokens_dao.get_model_tokens()
|
||||||
|
assert len(empty_token_list) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_should_return_token_for_service(notify_api, notify_db, notify_db_session, sample_service):
|
||||||
|
the_token = str(uuid.uuid4())
|
||||||
|
api_token = Token(**{'token': the_token, 'service_id': sample_service.id})
|
||||||
|
tokens_dao.save_token_model(api_token)
|
||||||
|
token = tokens_dao.get_model_tokens(sample_service.id)
|
||||||
|
assert token.service_id == sample_service.id
|
||||||
|
assert token.token == str(the_token)
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_model_token_should_remove_token(notify_api, notify_db, notify_db_session, sample_service):
|
||||||
|
api_token = Token(**{'token': str(uuid.uuid4()), 'service_id': sample_service.id})
|
||||||
|
tokens_dao.save_token_model(api_token)
|
||||||
|
all_tokens = tokens_dao.get_model_tokens()
|
||||||
|
assert len(all_tokens) == 1
|
||||||
|
tokens_dao.delete_model_token(all_tokens[0])
|
||||||
|
assert len(tokens_dao.get_model_tokens()) == 0
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
from app.models import (Service, User, Template)
|
from app.models import (Service, User, Token, Template)
|
||||||
from app.dao.services_dao import save_model_service
|
from app.dao.services_dao import save_model_service
|
||||||
from tests.app.conftest import sample_user as create_sample_user
|
from tests.app.conftest import sample_user as create_sample_user
|
||||||
from flask import url_for
|
from flask import url_for
|
||||||
@@ -263,6 +263,61 @@ def test_delete_service_not_exists(notify_api, notify_db, notify_db_session, sam
|
|||||||
assert Service.query.count() == 1
|
assert Service.query.count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_token_should_return_token_when_successful(notify_api, notify_db, notify_db_session, sample_service):
|
||||||
|
with notify_api.test_request_context():
|
||||||
|
with notify_api.test_client() as client:
|
||||||
|
response = client.post(url_for('service.create_token', service_id=sample_service.id),
|
||||||
|
headers=[('Content-Type', 'application/json')])
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert response.get_data is not None
|
||||||
|
saved_token = Token.query.first()
|
||||||
|
assert saved_token.service_id == sample_service.id
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_token_should_expire_the_old_token_and_create_a_new_token(notify_api, notify_db, notify_db_session,
|
||||||
|
sample_service):
|
||||||
|
with notify_api.test_request_context():
|
||||||
|
with notify_api.test_client() as client:
|
||||||
|
response = client.post(url_for('service.create_token', service_id=sample_service.id),
|
||||||
|
headers=[('Content-Type', 'application/json')])
|
||||||
|
assert response.status_code == 201
|
||||||
|
assert len(Token.query.all()) == 1
|
||||||
|
saved_token = Token.query.first()
|
||||||
|
|
||||||
|
response = client.post(url_for('service.create_token', service_id=sample_service.id),
|
||||||
|
headers=[('Content-Type', 'application/json')])
|
||||||
|
assert response.status_code == 201
|
||||||
|
all_tokens = Token.query.all()
|
||||||
|
assert len(all_tokens) == 2
|
||||||
|
for x in all_tokens:
|
||||||
|
if x.id == saved_token.id:
|
||||||
|
assert x.expiry_date is not None
|
||||||
|
else:
|
||||||
|
assert x.expiry_date is None
|
||||||
|
assert x.token is not saved_token.token
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_token_should_return_error_when_service_does_not_exist(notify_api, notify_db, notify_db_session,
|
||||||
|
sample_service):
|
||||||
|
with notify_api.test_request_context():
|
||||||
|
with notify_api.test_client() as client:
|
||||||
|
response = client.post(url_for('service.create_token', service_id=123),
|
||||||
|
headers=[('Content-Type', 'application/json')])
|
||||||
|
assert response.status_code == 404
|
||||||
|
|
||||||
|
|
||||||
|
def test_delete_token(notify_api, notify_db, notify_db_session, sample_service):
|
||||||
|
with notify_api.test_request_context():
|
||||||
|
with notify_api.test_client() as client:
|
||||||
|
client.post(url_for('service.create_token', service_id=sample_service.id),
|
||||||
|
headers=[('Content-Type', 'application/json')])
|
||||||
|
all_tokens = Token.query.all()
|
||||||
|
assert len(all_tokens) == 1
|
||||||
|
response = client.delete(url_for('service.delete_token', service_id=sample_service.id))
|
||||||
|
assert response.status_code == 202
|
||||||
|
assert len(Token.query.all()) == 0
|
||||||
|
|
||||||
|
|
||||||
def test_create_template(notify_api, notify_db, notify_db_session, sample_service):
|
def test_create_template(notify_api, notify_db, notify_db_session, sample_service):
|
||||||
"""
|
"""
|
||||||
Tests POST endpoint '/<service_id>/template' a template can be created
|
Tests POST endpoint '/<service_id>/template' a template can be created
|
||||||
|
|||||||
Reference in New Issue
Block a user