Created endpoints for create and delete token.

This commit is contained in:
Rebecca Law
2016-01-13 14:05:49 +00:00
parent 3a3f9becec
commit 725b976d31
10 changed files with 210 additions and 34 deletions

View File

@@ -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
View 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()

View File

@@ -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)

View File

@@ -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)

View File

@@ -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.

View File

@@ -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):

View 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 ###

View File

@@ -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

View File

@@ -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

View File

@@ -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