Merge pull request #804 from RoeiGanor/api_key

Add API Key to the UI
This commit is contained in:
Khanh Ngo 2020-10-10 14:11:09 +02:00 committed by GitHub
commit b8ffb1dae9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 425 additions and 0 deletions

View file

@ -1,6 +1,7 @@
import json
import datetime
import traceback
from base64 import b64encode
from ast import literal_eval
from flask import Blueprint, render_template, make_response, url_for, current_app, request, redirect, jsonify, abort, flash, session
from flask_login import login_required, current_user
@ -17,6 +18,11 @@ from ..models.domain import Domain
from ..models.record import Record
from ..models.domain_template import DomainTemplate
from ..models.domain_template_record import DomainTemplateRecord
from ..models.api_key import ApiKey
from ..lib.schema import ApiPlainKeySchema
apikey_plain_schema = ApiPlainKeySchema(many=True)
admin_bp = Blueprint('admin',
__name__,
@ -127,6 +133,123 @@ def edit_user(user_username=None):
create=create,
error=result['msg'])
@admin_bp.route('/key/edit/<key_id>', methods=['GET', 'POST'])
@admin_bp.route('/key/edit', methods=['GET', 'POST'])
@login_required
@operator_role_required
def edit_key(key_id=None):
domains = Domain.query.all()
roles = Role.query.all()
apikey = None
create = True
plain_key = None
if key_id:
apikey = ApiKey.query.filter(ApiKey.id == key_id).first()
create = False
if not apikey:
return render_template('errors/404.html'), 404
if request.method == 'GET':
return render_template('admin_edit_key.html',
key=apikey,
domains=domains,
roles=roles,
create=create)
if request.method == 'POST':
fdata = request.form
description = fdata['description']
role = fdata.getlist('key_role')[0]
doamin_list = fdata.getlist('key_multi_domain')
# Create new apikey
if create:
domain_obj_list = Domain.query.filter(Domain.name.in_(doamin_list)).all()
apikey = ApiKey(desc=description,
role_name=role,
domains=domain_obj_list)
try:
apikey.create()
except Exception as e:
current_app.logger.error('Error: {0}'.format(e))
raise ApiKeyCreateFail(message='Api key create failed')
plain_key = apikey_plain_schema.dump([apikey])[0]["plain_key"]
plain_key = b64encode(plain_key.encode('utf-8')).decode('utf-8')
history_message = "Created API key {0}".format(apikey.id)
# Update existing apikey
else:
try:
apikey.update(role,description,doamin_list)
history_message = "Updated API key {0}".format(apikey.id)
except Exception as e:
current_app.logger.error('Error: {0}'.format(e))
history = History(msg=history_message,
detail=str({
'key': apikey.id,
'role': apikey.role.name,
'description': apikey.description,
'domain_acl': [domain.name for domain in apikey.domains]
}),
created_by=current_user.username)
history.add()
return render_template('admin_edit_key.html',
key=apikey,
domains=domains,
roles=roles,
create=create,
plain_key=plain_key)
@admin_bp.route('/manage-keys', methods=['GET', 'POST'])
@login_required
@operator_role_required
def manage_keys():
if request.method == 'GET':
try:
apikeys = ApiKey.query.all()
except Exception as e:
current_app.logger.error('Error: {0}'.format(e))
abort(500)
return render_template('admin_manage_keys.html',
keys=apikeys)
elif request.method == 'POST':
jdata = request.json
if jdata['action'] == 'delete_key':
apikey = ApiKey.query.get(jdata['data'])
try:
history_apikey_id = apikey.id
history_apikey_role = apikey.role.name
history_apikey_description = apikey.description
history_apikey_domains = [ domain.name for domain in apikey.domains]
apikey.delete()
except Exception as e:
current_app.logger.error('Error: {0}'.format(e))
current_app.logger.info('Delete API key {0}'.format(apikey.id))
history = History(msg='Delete API key {0}'.format(apikey.id),
detail=str({
'key': history_apikey_id,
'role': history_apikey_role,
'description': history_apikey_description,
'domains': history_apikey_domains
}),
created_by=current_user.username)
history.add()
return make_response(
jsonify({
'status': 'ok',
'msg': 'Key has been removed.'
}), 200)
@admin_bp.route('/manage-user', methods=['GET', 'POST'])
@login_required

View file

@ -0,0 +1,168 @@
{% extends "base.html" %}
{% set active_page = "admin_keys" %}
{% block title %}
<title>Edit Key - {{ SITE_NAME }}</title>
{% endblock %}
{% block dashboard_stat %}
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
Key
<small>{% if create %}New key{% else %}{{ key.id }}{% endif %}</small>
</h1>
<ol class="breadcrumb">
<li><a href="{{ url_for('dashboard.dashboard') }}"><i class="fa fa-dashboard"></i>Home</a></li>
<li><a href="{{ url_for('admin.manage_keys') }}">Key</a></li>
<li class="active">{% if create %}Add{% else %}Edit{% endif %} key</li>
</ol>
</section>
{% endblock %}
{% block content %}
<section class="content">
<div class="row">
<div class="col-md-4">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{% if create %}Add{% else %}Edit{% endif %} key</h3>
</div>
<!-- /.box-header -->
<!-- form start -->
<form role="form" method="post"
action="{% if create %}{{ url_for('admin.edit_key') }}{% else %}{{ url_for('admin.edit_key', key_id=key.id) }}{% endif %}">
<input type="hidden" name="_csrf_token" value="{{ csrf_token() }}">
<input type="hidden" name="create" value="{{ create }}">
<div class="box-body">
<div class="form-group has-feedback">
<label class="control-label" for="role">Role</label>
<select class="key_role form-control" id="key_role" name="key_role">
{% for role in roles %}
<option value="{{ role.name }}"
{% if (key is not none) and (role.id==key.role.id) %}selected{% endif %}>{{ role.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group has-feedback">
<label class="control-label" for="description">Description</label>
<input type="text" class="form-control" placeholder="Description" name="description"
{% if key is not none %} value="{{ key.description }}" {% endif %}> <span
class="glyphicon glyphicon-pencil form-control-feedback"></span>
</div>
</div>
<div class="box-header with-border">
<h3 class="box-title">Access Control</h3>
</div>
<div class="box-body">
<p>This key will have acess to the domains on the right.</p>
<p>Click on domains to move between the columns.</p>
<div class="form-group col-xs-2">
<select multiple="multiple" class="form-control" id="key_multi_domain"
name="key_multi_domain">
{% for domain in domains %}
<option {% if domain in key.domains %}selected{% endif %} value="{{ domain.name }}">{{ domain.name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="box-footer">
<button type="submit"
class="btn btn-flat btn-primary">{% if create %}Create{% else %}Update{% endif %}
Key</button>
</div>
</form>
</div>
</div>
<div class="col-md-8">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Help with {% if create %}creating a new{% else%}updating a{% endif %} key
</h3>
</div>
<div class="box-body">
<p>Fill in all the fields in the form to the left.</p>
<p><strong>Role</strong> The role of the key.</p>
<p><strong>Description</strong> The key description.</p>
<p><strong>Access Control</strong> The domains which the key has access to.</p>
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block extrascripts %}
<script>
$("#key_multi_domain").multiSelect({
selectableHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Domain Name'>",
selectionHeader: "<input type='text' class='search-input' autocomplete='off' placeholder='Domain Name'>",
afterInit: function (ms) {
var that = this,
$selectableSearch = that.$selectableUl.prev(),
$selectionSearch = that.$selectionUl.prev(),
selectableSearchString = '#' + that.$container.attr('id') + ' .ms-elem-selectable:not(.ms-selected)',
selectionSearchString = '#' + that.$container.attr('id') + ' .ms-elem-selection.ms-selected';
that.qs1 = $selectableSearch.quicksearch(selectableSearchString)
.on('keydown', function (e) {
if (e.which === 40) {
that.$selectableUl.focus();
return false;
}
});
that.qs2 = $selectionSearch.quicksearch(selectionSearchString)
.on('keydown', function (e) {
if (e.which == 40) {
that.$selectionUl.focus();
return false;
}
});
},
afterSelect: function () {
this.qs1.cache();
this.qs2.cache();
},
afterDeselect: function () {
this.qs1.cache();
this.qs2.cache();
}
});
{% if plain_key %}
$(document.body).ready(function () {
var modal = $("#modal_show_key");
var info = "{{ plain_key }}";
modal.find('.modal-body p').text(info);
modal.find('#button_key_confirm').click(redirect_modal);
modal.find('#button_close_modal').click(redirect_modal);
modal.modal('show');
});
function redirect_modal() {
window.location.href = '{{ url_for('admin.manage_keys') }}';
modal.modal('hide');
}
{% endif %}
</script>
{% endblock %}
{% block modals %}
<div class="modal fade" id="modal_show_key">
<div class="modal-dialog">
<div class="modal-content modal-sm">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" id="button_close_modal">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Your API key</h4>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-flat btn-primary center-block" id="button_key_confirm">
Confirm</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
{% endblock %}

View file

@ -0,0 +1,131 @@
{% extends "base.html" %}
{% set active_page = "admin_keys" %}
{% block title %}
<title>Key Management - {{ SITE_NAME }}</title>
{% endblock %} {% block dashboard_stat %}
<section class="content-header">
<h1>
Key <small>Manage API keys</small>
</h1>
<ol class="breadcrumb">
<li><a href="{{ url_for('dashboard.dashboard') }}"><i class="fa fa-dashboard"></i> Home</a></li>
<li class="active">Key</li>
</ol>
</section>
{% endblock %} {% block content %}
<section class="content">
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header">
<h3 class="box-title">Key Management</h3>
</div>
<div class="box-body">
<a href="{{ url_for('admin.edit_key') }}">
<button type="button" class="btn btn-flat btn-primary pull-left button_add_key">
Add Key&nbsp;<i class="fa fa-plus"></i>
</button>
</a>
</div>
<div class="box-body">
<table id="tbl_keys" class="table table-bordered table-striped">
<thead>
<tr>
<th>Id</th>
<th>Role</th>
<th>Description</th>
<th>Domains</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for key in keys %}
<tr class="odd gradeX">
<td>{{ key.id }}</td>
<td>{{ key.role.name }}</td>
<td>{{ key.description }}</td>
<td>{% for domain in key.domains %}{{ domain.name }}{% if not loop.last %}, {% endif %}{% endfor %}</td>
<td width="15%">
<button type="button" class="btn btn-flat btn-success button_edit"
onclick="window.location.href='{{ url_for('admin.edit_key', key_id=key.id) }}'">
Edit&nbsp;<i class="fa fa-lock"></i>
</button>
<button type="button" class="btn btn-flat btn-danger button_delete"
id="{{ key.id }}">
Delete&nbsp;<i class="fa fa-trash"></i>
</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<!-- /.box-body -->
</div>
<!-- /.box -->
</div>
<!-- /.col -->
</div>
<!-- /.row -->
</section>
{% endblock %}
{% block extrascripts %}
<script>
// set up key data table
$("#tbl_keys").DataTable({
"paging": true,
"lengthChange": true,
"searching": true,
"ordering": true,
"info": false,
"autoWidth": false,
"lengthMenu": [
[10, 25, 50, 100, -1],
[10, 25, 50, 100, "All"]
],
"pageLength": 10
});
// handle deletion of keys
$(document.body).on('click', '.button_delete', function () {
var modal = $("#modal_delete");
var key_id = $(this).prop('id');
var info = "Are you sure you want to delete key #" + key_id + "?";
modal.find('.modal-body p').text(info);
modal.find('#button_delete_confirm').click(function () {
var postdata = {
'action': 'delete_key',
'data': key_id,
'_csrf_token': '{{ csrf_token() }}'
}
applyChanges(postdata, $SCRIPT_ROOT + '/admin/manage-keys', false, true);
modal.modal('hide');
})
modal.modal('show');
});
</script>
{% endblock %}
{% block modals %}
<div class="modal fade modal-warning" id="modal_delete">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">Confirmation</h4>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-flat btn-default pull-left" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-flat btn-danger" id="button_delete_confirm">Delete</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
{% endblock %}

View file

@ -132,6 +132,9 @@
<li class="{{ 'active' if active_page == 'admin_users' else '' }}">
<a href="{{ url_for('admin.manage_user') }}"><i class="fa fa-users"></i> <span>Users</span></a>
</li>
<li class="{{ 'active' if active_page == 'admin_keys' else '' }}">
<a href="{{ url_for('admin.manage_keys') }}"><i class="fa fa-key"></i> <span>API Keys</span></a>
</li>
<li class="{{ 'treeview active' if active_page == 'admin_settings' else 'treeview' }}">
<a href="#">
<i class="fa fa-cog"></i> <span>Settings</span>