mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-02-05 19:03:30 -05:00
Merge pull request #96 from alphagov/api-keys-flow
Add pages for create/view/revoke API keys
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
import dateutil
|
||||
from flask import Flask, session, Markup, escape, render_template
|
||||
from flask._compat import string_types
|
||||
from flask.ext.sqlalchemy import SQLAlchemy
|
||||
@@ -8,6 +9,7 @@ from flask_login import LoginManager
|
||||
from flask_wtf import CsrfProtect
|
||||
from werkzeug.exceptions import abort
|
||||
from app.notify_client.api_client import NotificationsAdminAPIClient
|
||||
from app.notify_client.api_key_api_client import ApiKeyApiClient
|
||||
from app.notify_client.user_api_client import UserApiClient
|
||||
from app.its_dangerous_session import ItsdangerousSessionInterface
|
||||
import app.proxy_fix
|
||||
@@ -20,6 +22,7 @@ csrf = CsrfProtect()
|
||||
|
||||
notifications_api_client = NotificationsAdminAPIClient()
|
||||
user_api_client = UserApiClient()
|
||||
api_key_api_client = ApiKeyApiClient()
|
||||
|
||||
|
||||
def create_app(config_name, config_overrides=None):
|
||||
@@ -34,6 +37,7 @@ def create_app(config_name, config_overrides=None):
|
||||
|
||||
notifications_api_client.init_app(application)
|
||||
user_api_client.init_app(application)
|
||||
api_key_api_client.init_app(application)
|
||||
|
||||
login_manager.init_app(application)
|
||||
login_manager.login_view = 'main.sign_in'
|
||||
@@ -51,6 +55,7 @@ def create_app(config_name, config_overrides=None):
|
||||
application.add_template_filter(placeholders)
|
||||
application.add_template_filter(replace_placeholders)
|
||||
application.add_template_filter(nl2br)
|
||||
application.add_template_filter(format_datetime)
|
||||
|
||||
application.after_request(useful_headers_after_request)
|
||||
register_errorhandlers(application)
|
||||
@@ -131,6 +136,12 @@ def replace_placeholders(template, values):
|
||||
))
|
||||
|
||||
|
||||
def format_datetime(date):
|
||||
date = dateutil.parser.parse(date)
|
||||
native = date.replace(tzinfo=None)
|
||||
return native.strftime('%A %d %B %Y at %H:%M')
|
||||
|
||||
|
||||
# https://www.owasp.org/index.php/List_of_useful_HTTP_headers
|
||||
def useful_headers_after_request(response):
|
||||
response.headers.add('X-Frame-Options', 'deny')
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
(function(Modules) {
|
||||
"use strict";
|
||||
|
||||
if (!document.queryCommandSupported('copy')) return;
|
||||
|
||||
Modules.ApiKey = function() {
|
||||
|
||||
const states = {
|
||||
'initial': `
|
||||
<input type='button' class='api-key-button-show' value='Show API key' />
|
||||
`,
|
||||
'keyVisibleBasic': key => `
|
||||
<span class="api-key-key">${key}</span>
|
||||
`,
|
||||
'keyVisible': key => `
|
||||
<span class="api-key-key">${key}</span>
|
||||
<input type='button' class='api-key-button-copy' value='Copy API key to clipboard' />
|
||||
@@ -33,23 +29,22 @@
|
||||
|
||||
this.start = function(component) {
|
||||
|
||||
const $component = $(component).html(states.initial).attr('aria-live', 'polite'),
|
||||
const $component = $(component),
|
||||
key = $component.data('key');
|
||||
|
||||
$component
|
||||
.on(
|
||||
'click', '.api-key-button-show', () =>
|
||||
$component.html(
|
||||
document.queryCommandSupported('copy') ?
|
||||
states.keyVisible(key) : states.keyVisibleBasic(key)
|
||||
)
|
||||
)
|
||||
.html(states.keyVisible(key))
|
||||
.attr('aria-live', 'polite')
|
||||
.on(
|
||||
'click', '.api-key-button-copy', () =>
|
||||
this.copyKey(
|
||||
$('.api-key-key', component)[0], () =>
|
||||
$component.html(states.keyCopied)
|
||||
)
|
||||
)
|
||||
.on(
|
||||
'click', '.api-key-button-show', () =>
|
||||
$component.html(states.keyVisible(key))
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
.api-key {
|
||||
|
||||
&-name {
|
||||
@include bold-19;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
&-key {
|
||||
font-family: monospace;
|
||||
display: block;
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
.table {
|
||||
margin-bottom: $gutter;
|
||||
}
|
||||
|
||||
.table-heading {
|
||||
text-align: left;
|
||||
}
|
||||
@@ -16,8 +20,15 @@
|
||||
}
|
||||
|
||||
&-error {
|
||||
|
||||
color: $error-colour;
|
||||
font-weight: bold;
|
||||
|
||||
a:link,
|
||||
a:visited {
|
||||
color: $error-colour;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -235,3 +235,17 @@ class ChangeMobileNumberForm(Form):
|
||||
|
||||
class ConfirmMobileNumberForm(Form):
|
||||
sms_code = sms_code()
|
||||
|
||||
|
||||
class CreateKeyForm(Form):
|
||||
def __init__(self, existing_key_names=[], *args, **kwargs):
|
||||
self.existing_key_names = [x.lower() for x in existing_key_names]
|
||||
super(CreateKeyForm, self).__init__(*args, **kwargs)
|
||||
|
||||
key_name = StringField(u'Description of key', validators=[
|
||||
DataRequired(message='You need to give the key a name')
|
||||
])
|
||||
|
||||
def validate_key_name(self, key_name):
|
||||
if key_name.data.lower() in self.existing_key_names:
|
||||
raise ValidationError('A key with this name already exists')
|
||||
|
||||
@@ -1,9 +1,55 @@
|
||||
from flask import render_template
|
||||
from flask import request, render_template, redirect, url_for, flash
|
||||
from flask_login import login_required
|
||||
from app.main import main
|
||||
from app.main.forms import CreateKeyForm
|
||||
from app import api_key_api_client
|
||||
|
||||
|
||||
@main.route("/services/<int:service_id>/documentation")
|
||||
@login_required
|
||||
def documentation(service_id):
|
||||
return render_template('views/documentation.html', service_id=service_id)
|
||||
|
||||
|
||||
@main.route("/services/<int:service_id>/api-keys")
|
||||
@login_required
|
||||
def api_keys(service_id):
|
||||
return render_template('views/api-keys.html', service_id=service_id)
|
||||
return render_template(
|
||||
'views/api-keys.html',
|
||||
service_id=service_id,
|
||||
keys=api_key_api_client.get_api_keys(service_id=service_id)['apiKeys']
|
||||
)
|
||||
|
||||
|
||||
@main.route("/services/<int:service_id>/api-keys/create", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def create_api_key(service_id):
|
||||
key_names = [
|
||||
key['name'] for key in api_key_api_client.get_api_keys(service_id=service_id)['apiKeys']
|
||||
]
|
||||
form = CreateKeyForm(key_names)
|
||||
if form.validate_on_submit():
|
||||
secret = api_key_api_client.create_api_key(service_id=service_id, key_name=form.key_name.data)
|
||||
return render_template('views/api-keys/show.html', service_id=service_id, secret=secret,
|
||||
key_name=form.key_name.data)
|
||||
return render_template(
|
||||
'views/api-keys/create.html',
|
||||
service_id=service_id,
|
||||
key_name=form.key_name
|
||||
)
|
||||
|
||||
|
||||
@main.route("/services/<int:service_id>/api-keys/revoke/<int:key_id>", methods=['GET', 'POST'])
|
||||
@login_required
|
||||
def revoke_api_key(service_id, key_id):
|
||||
key_name = api_key_api_client.get_api_keys(service_id=service_id, key_id=key_id)['apiKeys'][0]['name']
|
||||
if request.method == 'GET':
|
||||
return render_template(
|
||||
'views/api-keys/revoke.html',
|
||||
service_id=service_id,
|
||||
key_name=key_name
|
||||
)
|
||||
elif request.method == 'POST':
|
||||
api_key_api_client.revoke_api_key(service_id=service_id, key_id=key_id)
|
||||
flash('‘{}’ was revoked'.format(key_name))
|
||||
return redirect(url_for('.api_keys', service_id=service_id))
|
||||
|
||||
27
app/notify_client/api_key_api_client.py
Normal file
27
app/notify_client/api_key_api_client.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from client.base import BaseAPIClient
|
||||
|
||||
|
||||
class ApiKeyApiClient(BaseAPIClient):
|
||||
def __init__(self, base_url=None, client_id=None, secret=None):
|
||||
super(self.__class__, self).__init__(base_url=base_url or 'base_url',
|
||||
client_id=client_id or 'client_id',
|
||||
secret=secret or 'secret')
|
||||
|
||||
def init_app(self, app):
|
||||
self.base_url = app.config['API_HOST_NAME']
|
||||
self.client_id = app.config['ADMIN_CLIENT_USER_NAME']
|
||||
self.secret = app.config['ADMIN_CLIENT_SECRET']
|
||||
|
||||
def get_api_keys(self, service_id, key_id=None, *params):
|
||||
if key_id:
|
||||
return self.get(url='/service/{}/api-keys/{}'.format(service_id, key_id))
|
||||
else:
|
||||
return self.get(url='/service/{}/api-keys'.format(service_id))
|
||||
|
||||
def create_api_key(self, service_id, key_name, *params):
|
||||
data = {"name": key_name}
|
||||
key = self.post(url='/service/{}/api-key'.format(service_id), data=data)
|
||||
return key['data']
|
||||
|
||||
def revoke_api_key(self, service_id, key_id, *params):
|
||||
return self.post(url='/service/{0}/api-key/revoke/{1}'.format(service_id, key_id), data=None)
|
||||
@@ -1,4 +1,7 @@
|
||||
{% macro api_key(key) %}
|
||||
{% macro api_key(key, name) %}
|
||||
<h2 class="api-key-name">
|
||||
{{ name }}
|
||||
</h2>
|
||||
<div data-module="api-key" data-key="{{ key }}">
|
||||
<span class="api-key-key">{{ key }}</span>
|
||||
</div>
|
||||
|
||||
@@ -55,3 +55,7 @@
|
||||
{% macro right_aligned_field_heading(text) %}
|
||||
<span class="table-field-heading-right-aligned">{{ text }}</span>
|
||||
{%- endmacro %}
|
||||
|
||||
{% macro hidden_field_heading(text) %}
|
||||
<span class="visuallyhidden">{{ text }}</span>
|
||||
{%- endmacro %}
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
<li><a href="{{ url_for('.manage_service_templates', service_id=service_id) }}">Templates</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="{{ url_for('.api_keys', service_id=service_id) }}">API keys and documentation</a></li>
|
||||
<li><a href="{{ url_for('.api_keys', service_id=123) }}">API keys</a></li>
|
||||
<li><a href="{{ url_for('.documentation', service_id=123) }}">Developer documentation</a></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><a href="{{ url_for('.manage_users', service_id=service_id) }}">Manage users</a></li>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
{% from "components/api-key.html" import api_key %}
|
||||
{% from "components/table.html" import list_table, field, hidden_field_heading %}
|
||||
|
||||
{% block page_title %}
|
||||
GOV.UK Notify | API keys and documentation
|
||||
@@ -8,49 +7,51 @@
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
<h1 class="heading-xlarge">
|
||||
API keys
|
||||
</h1>
|
||||
|
||||
<h1 class="heading-xlarge">
|
||||
API keys and documentation
|
||||
</h1>
|
||||
<p>
|
||||
To connect to the API you will need to send your service ID, encrypted with
|
||||
an API key. The API key stays secret.
|
||||
</p>
|
||||
|
||||
<h2 class="heading-medium">
|
||||
How to integrate GOV.UK Notify into your service
|
||||
</h2>
|
||||
<p>
|
||||
There are client libraries available which can do this for you. See
|
||||
<a href="{{ url_for(".documentation", service_id=service_id) }}">the
|
||||
developer documentation</a> for more information.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
blah blah blah this is where we tell you how the API works
|
||||
</p>
|
||||
<h2 class="api-key-name">
|
||||
Service ID
|
||||
</h2>
|
||||
<p class="api-key-key">
|
||||
{{ service_id }}
|
||||
</p>
|
||||
|
||||
<h2 class="heading-medium">Repositories</h2>
|
||||
{% call(item) list_table(
|
||||
keys,
|
||||
empty_message="You haven’t created any API keys yet",
|
||||
caption="API keys",
|
||||
caption_visible=False,
|
||||
field_headings=['Key name', hidden_field_heading('Action')]
|
||||
) %}
|
||||
{% call field() %}
|
||||
{{ item.name }}
|
||||
{% endcall %}
|
||||
{% if item.expiry_date %}
|
||||
{% call field(align='right', status='default') %}
|
||||
Revoked {{ item.expiry_date|format_datetime }}
|
||||
{% endcall %}
|
||||
{% else %}
|
||||
{% call field(align='right', status='error') %}
|
||||
<a href='{{ url_for('.revoke_api_key', service_id=service_id, key_id=item.id) }}'>Revoke</a>
|
||||
{% endcall %}
|
||||
{% endif %}
|
||||
{% endcall %}
|
||||
|
||||
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/alphagov/notifications-api">GOV.UK Notify API</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/alphagov/notify-api-client">GOV.UK Notify Python client</a>
|
||||
</p>
|
||||
|
||||
<h2 class="heading-medium">API key for [service name]</h2>
|
||||
|
||||
{{ api_key('d30512af92e1386d63b90e5973b49a10') }}
|
||||
|
||||
<h2 class="heading-medium">API endpoint</h2>
|
||||
|
||||
<p>
|
||||
https://www.notify.works/api/endpoint
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ page_footer(
|
||||
back_link=url_for('.service_dashboard', service_id=service_id),
|
||||
back_link_text='Back to dashboard'
|
||||
) }}
|
||||
<p>
|
||||
<a href="{{ url_for('.create_api_key', service_id=service_id) }}">Create a new API key</a>
|
||||
</p>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
24
app/templates/views/api-keys/create.html
Normal file
24
app/templates/views/api-keys/create.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
{% from "components/textbox.html" import textbox %}
|
||||
|
||||
{% block page_title %}
|
||||
GOV.UK Notify | API keys and documentation
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="heading-xlarge">
|
||||
Add a new API key
|
||||
</h1>
|
||||
|
||||
<form method="post">
|
||||
{{ textbox(key_name, hint='eg CRM application') }}
|
||||
{{ page_footer(
|
||||
'Continue',
|
||||
back_link=url_for('.api_keys', service_id=service_id),
|
||||
back_link_text='Back to API keys'
|
||||
) }}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
37
app/templates/views/api-keys/revoke.html
Normal file
37
app/templates/views/api-keys/revoke.html
Normal file
@@ -0,0 +1,37 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
{% from "components/api-key.html" import api_key %}
|
||||
|
||||
{% block page_title %}
|
||||
GOV.UK Notify | API keys and documentation
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
|
||||
<h1 class="heading-xlarge">
|
||||
Revoke API key
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
‘{{ key_name }}’ will no longer let you connect to GOV.UK Notify.
|
||||
</p>
|
||||
<p>
|
||||
You can’t undo this.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
{{ page_footer(
|
||||
'Revoke this API key',
|
||||
back_link=url_for('.api_keys', service_id=service_id),
|
||||
back_link_text='Back to API keys',
|
||||
destructive=True
|
||||
) }}
|
||||
</form>
|
||||
|
||||
{% endblock %}
|
||||
33
app/templates/views/api-keys/show.html
Normal file
33
app/templates/views/api-keys/show.html
Normal file
@@ -0,0 +1,33 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
{% from "components/api-key.html" import api_key %}
|
||||
|
||||
{% block page_title %}
|
||||
GOV.UK Notify | API keys and documentation
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
|
||||
<h1 class="heading-xlarge">
|
||||
New API key
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
Copy your key to somewhere safe. You won’t be able to see it again
|
||||
once you leave this page.
|
||||
</p>
|
||||
|
||||
{{ api_key(secret, key_name) }}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{ page_footer(
|
||||
back_link=url_for('.api_keys', service_id=service_id),
|
||||
back_link_text='Back to API keys'
|
||||
) }}
|
||||
|
||||
{% endblock %}
|
||||
49
app/templates/views/documentation.html
Normal file
49
app/templates/views/documentation.html
Normal file
@@ -0,0 +1,49 @@
|
||||
{% extends "withnav_template.html" %}
|
||||
{% from "components/page-footer.html" import page_footer %}
|
||||
{% from "components/api-key.html" import api_key %}
|
||||
|
||||
{% block page_title %}
|
||||
GOV.UK Notify | API keys and documentation
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<div class="grid-row">
|
||||
<div class="column-two-thirds">
|
||||
|
||||
<h1 class="heading-xlarge">
|
||||
Developer documentation
|
||||
</h1>
|
||||
|
||||
<h2 class="heading-medium">
|
||||
How to integrate GOV.UK Notify into your service
|
||||
</h2>
|
||||
|
||||
<p>
|
||||
blah blah blah this is where we tell you how the API works
|
||||
</p>
|
||||
|
||||
<h2 class="heading-medium">Repositories</h2>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/alphagov/notifications-api">GOV.UK Notify API</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="https://github.com/alphagov/notify-api-client">GOV.UK Notify Python client</a>
|
||||
</p>
|
||||
|
||||
<h2 class="heading-medium">API endpoint</h2>
|
||||
|
||||
<p>
|
||||
https://www.notify.works/api/endpoint
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="{{ url_for('.api_keys', service_id=service_id) }}">API keys for your service</a>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
@@ -39,6 +39,12 @@ def template_json(id_, name, type_, content, service_id):
|
||||
}
|
||||
|
||||
|
||||
def api_key_json(id_, name, expiry_date=None):
|
||||
return {'id': id_,
|
||||
'name': name,
|
||||
'expiry_date': expiry_date
|
||||
}
|
||||
|
||||
TEST_USER_EMAIL = 'test@user.gov.uk'
|
||||
|
||||
|
||||
|
||||
16
tests/app/main/test_create_api_key_form.py
Normal file
16
tests/app/main/test_create_api_key_form.py
Normal file
@@ -0,0 +1,16 @@
|
||||
from werkzeug.datastructures import MultiDict
|
||||
|
||||
from app.main.forms import CreateKeyForm
|
||||
|
||||
|
||||
def test_return_validation_error_when_key_name_exists(app_,
|
||||
db_,
|
||||
db_session):
|
||||
def _get_names():
|
||||
return ['some key', 'another key']
|
||||
|
||||
with app_.test_request_context():
|
||||
form = CreateKeyForm(_get_names(),
|
||||
formdata=MultiDict([('key_name', 'Some key')]))
|
||||
form.validate()
|
||||
assert {'key_name': ['A key with this name already exists']} == form.errors
|
||||
@@ -1,3 +1,4 @@
|
||||
from datetime import date
|
||||
from flask import url_for
|
||||
|
||||
|
||||
@@ -7,9 +8,116 @@ def test_should_show_api_keys_and_documentation_page(app_,
|
||||
mock_api_user,
|
||||
mock_user_loader,
|
||||
mock_user_dao_get_by_email):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(mock_api_user)
|
||||
response = client.get(url_for('main.documentation', service_id=123))
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_should_show_empty_api_keys_page(app_,
|
||||
db_,
|
||||
db_session,
|
||||
mock_api_user,
|
||||
mock_user_loader,
|
||||
mock_user_dao_get_by_email,
|
||||
mock_get_no_api_keys):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(mock_api_user)
|
||||
response = client.get(url_for('main.api_keys', service_id=123))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'You haven’t created any API keys yet' in response.get_data(as_text=True)
|
||||
assert 'Create a new API key' in response.get_data(as_text=True)
|
||||
mock_get_no_api_keys.assert_called_once_with(service_id=123)
|
||||
|
||||
|
||||
def test_should_show_api_keys_page(app_,
|
||||
db_,
|
||||
db_session,
|
||||
mock_api_user,
|
||||
mock_user_loader,
|
||||
mock_user_dao_get_by_email,
|
||||
mock_get_api_keys):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(mock_api_user)
|
||||
response = client.get(url_for('main.api_keys', service_id=123))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'some key name' in response.get_data(as_text=True)
|
||||
assert 'another key name' in response.get_data(as_text=True)
|
||||
assert 'Revoked Thursday 01 January 1970 at 00:00' in response.get_data(as_text=True)
|
||||
mock_get_api_keys.assert_called_once_with(service_id=123)
|
||||
|
||||
|
||||
def test_should_show_name_api_key_page(app_,
|
||||
db_,
|
||||
db_session,
|
||||
mock_api_user,
|
||||
mock_user_loader,
|
||||
mock_user_dao_get_by_email,
|
||||
mock_get_api_keys):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(mock_api_user)
|
||||
response = client.get(url_for('main.create_api_key', service_id=123))
|
||||
|
||||
assert response.status_code == 200
|
||||
|
||||
|
||||
def test_should_render_show_api_key(app_,
|
||||
db_,
|
||||
db_session,
|
||||
mock_api_user,
|
||||
mock_user_loader,
|
||||
mock_user_dao_get_by_email,
|
||||
mock_create_api_key,
|
||||
mock_get_api_keys):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(mock_api_user)
|
||||
response = client.post(url_for('main.create_api_key', service_id=123),
|
||||
data={'key_name': 'some default key name'})
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'some default key name' in response.get_data(as_text=True)
|
||||
mock_create_api_key.assert_called_once_with(service_id=123, key_name='some default key name')
|
||||
|
||||
|
||||
def test_should_show_confirm_revoke_api_key(app_,
|
||||
db_,
|
||||
db_session,
|
||||
mock_api_user,
|
||||
mock_user_loader,
|
||||
mock_user_dao_get_by_email,
|
||||
mock_get_api_keys):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(mock_api_user)
|
||||
response = client.get(url_for('main.revoke_api_key', service_id=123, key_id=321))
|
||||
|
||||
assert response.status_code == 200
|
||||
assert 'some key name' in response.get_data(as_text=True)
|
||||
mock_get_api_keys.assert_called_once_with(service_id=123, key_id=321)
|
||||
|
||||
|
||||
def test_should_redirect_after_revoking_api_key(app_,
|
||||
db_,
|
||||
db_session,
|
||||
mock_api_user,
|
||||
mock_user_loader,
|
||||
mock_user_dao_get_by_email,
|
||||
mock_revoke_api_key,
|
||||
mock_get_api_keys):
|
||||
with app_.test_request_context():
|
||||
with app_.test_client() as client:
|
||||
client.login(mock_api_user)
|
||||
response = client.post(url_for('main.revoke_api_key', service_id=123, key_id=321))
|
||||
|
||||
assert response.status_code == 302
|
||||
assert response.location == url_for('.api_keys', service_id=123, _external=True)
|
||||
mock_revoke_api_key.assert_called_once_with(service_id=123, key_id=321)
|
||||
mock_get_api_keys.assert_called_once_with(service_id=123, key_id=321)
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import os
|
||||
from datetime import date
|
||||
|
||||
import pytest
|
||||
from alembic.command import upgrade
|
||||
from alembic.config import Config
|
||||
from flask.ext.migrate import Migrate, MigrateCommand
|
||||
from flask.ext.script import Manager
|
||||
from sqlalchemy.schema import MetaData
|
||||
from . import (
|
||||
create_test_user, create_another_test_user, service_json, TestClient,
|
||||
get_test_user, template_json)
|
||||
|
||||
from app import create_app, db
|
||||
from . import (
|
||||
create_test_user, service_json, TestClient,
|
||||
get_test_user, template_json, api_key_json)
|
||||
|
||||
|
||||
@pytest.fixture(scope='session')
|
||||
@@ -89,6 +92,7 @@ def mock_get_service(mocker, mock_api_user):
|
||||
service_id, "Test Service", [mock_api_user.id], limit=1000,
|
||||
active=False, restricted=True)
|
||||
return {'data': service, 'token': 1}
|
||||
|
||||
return mocker.patch('app.notifications_api_client.get_service', side_effect=_create)
|
||||
|
||||
|
||||
@@ -99,6 +103,7 @@ def mock_create_service(mocker):
|
||||
101, service_name, [user_id], limit=limit,
|
||||
active=active, restricted=restricted)
|
||||
return {'data': service}
|
||||
|
||||
mock_class = mocker.patch(
|
||||
'app.notifications_api_client.create_service', side_effect=_create)
|
||||
return mock_class
|
||||
@@ -116,6 +121,7 @@ def mock_update_service(mocker):
|
||||
service_id, service_name, users, limit=limit,
|
||||
active=active, restricted=restricted)
|
||||
return {'data': service}
|
||||
|
||||
mock_class = mocker.patch(
|
||||
'app.notifications_api_client.update_service', side_effect=_update)
|
||||
return mock_class
|
||||
@@ -129,6 +135,7 @@ def mock_get_services(mocker, mock_api_user):
|
||||
service_two = service_json(
|
||||
2, "service_two", [mock_api_user.id], 1000, True, False)
|
||||
return {'data': [service_one, service_two]}
|
||||
|
||||
mock_class = mocker.patch(
|
||||
'app.notifications_api_client.get_services', side_effect=_create)
|
||||
return mock_class
|
||||
@@ -138,6 +145,7 @@ def mock_get_services(mocker, mock_api_user):
|
||||
def mock_delete_service(mocker, mock_get_service):
|
||||
def _delete(service_id):
|
||||
return mock_get_service.side_effect(service_id)
|
||||
|
||||
mock_class = mocker.patch(
|
||||
'app.notifications_api_client.delete_service', side_effect=_delete)
|
||||
return mock_class
|
||||
@@ -149,6 +157,7 @@ def mock_get_service_template(mocker):
|
||||
template = template_json(
|
||||
template_id, "Template Name", "sms", "template content", service_id)
|
||||
return {'data': template}
|
||||
|
||||
return mocker.patch(
|
||||
'app.notifications_api_client.get_service_template',
|
||||
side_effect=_create)
|
||||
@@ -160,6 +169,7 @@ def mock_create_service_template(mocker):
|
||||
template = template_json(
|
||||
101, name, type_, content, service)
|
||||
return {'data': template}
|
||||
|
||||
mock_class = mocker.patch(
|
||||
'app.notifications_api_client.create_service_template',
|
||||
side_effect=_create)
|
||||
@@ -172,6 +182,7 @@ def mock_update_service_template(mocker):
|
||||
template = template_json(
|
||||
id_, name, type_, content, service)
|
||||
return {'data': template}
|
||||
|
||||
mock_class = mocker.patch(
|
||||
'app.notifications_api_client.update_service_template',
|
||||
side_effect=_update)
|
||||
@@ -186,6 +197,7 @@ def mock_get_service_templates(mocker):
|
||||
template_two = template_json(
|
||||
2, "template_two", "sms", "template two content", service_id)
|
||||
return {'data': [template_one, template_two]}
|
||||
|
||||
mock_class = mocker.patch(
|
||||
'app.notifications_api_client.get_service_templates',
|
||||
side_effect=_create)
|
||||
@@ -199,6 +211,7 @@ def mock_delete_service_template(mocker):
|
||||
template_id, "Template to delete",
|
||||
"sms", "content to be deleted", service_id)
|
||||
return {'data': template}
|
||||
|
||||
return mocker.patch(
|
||||
'app.notifications_api_client.delete_service_template', side_effect=_delete)
|
||||
|
||||
@@ -314,3 +327,43 @@ def mock_user_dao_get_new_password(mocker, mock_api_user):
|
||||
mock_class = mocker.patch('app.main.dao.users_dao.get_user_by_email')
|
||||
mock_class.return_value = mock_api_user
|
||||
return mock_class
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_create_api_key(mocker):
|
||||
def _create(service_id, key_name):
|
||||
import uuid
|
||||
return {'data': str(uuid.uuid4())}
|
||||
|
||||
mock_class = mocker.patch('app.api_key_api_client.create_api_key', side_effect=_create)
|
||||
return mock_class
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_revoke_api_key(mocker):
|
||||
def _revoke(service_id, key_id):
|
||||
return {}
|
||||
|
||||
mock_class = mocker.patch('app.api_key_api_client.revoke_api_key', side_effect=_revoke)
|
||||
return mock_class
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_api_keys(mocker):
|
||||
def _get_keys(service_id, key_id=None):
|
||||
keys = {'apiKeys': [api_key_json(1, 'some key name'),
|
||||
api_key_json(2, 'another key name', expiry_date=str(date.fromtimestamp(0)))]}
|
||||
return keys
|
||||
|
||||
mock_class = mocker.patch('app.api_key_api_client.get_api_keys', side_effect=_get_keys)
|
||||
return mock_class
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def mock_get_no_api_keys(mocker):
|
||||
def _get_keys(service_id):
|
||||
keys = {'apiKeys': []}
|
||||
return keys
|
||||
|
||||
mock_class = mocker.patch('app.api_key_api_client.get_api_keys', side_effect=_get_keys)
|
||||
return mock_class
|
||||
|
||||
Reference in New Issue
Block a user