mirror of
https://github.com/cwinfo/powerdns-admin.git
synced 2024-11-08 14:40:27 +00:00
commit
b8ffb1dae9
@ -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
|
||||
|
168
powerdnsadmin/templates/admin_edit_key.html
Normal file
168
powerdnsadmin/templates/admin_edit_key.html
Normal 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">×</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 %}
|
131
powerdnsadmin/templates/admin_manage_keys.html
Normal file
131
powerdnsadmin/templates/admin_manage_keys.html
Normal 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 <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 <i class="fa fa-lock"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-flat btn-danger button_delete"
|
||||
id="{{ key.id }}">
|
||||
Delete <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">×</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 %}
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user