Resolve the conflicts for #228

This commit is contained in:
Khanh Ngo
2018-04-02 13:38:53 +07:00
32 changed files with 1103 additions and 130 deletions

View File

@ -11,7 +11,6 @@ login_manager = LoginManager()
login_manager.init_app(app)
db = SQLAlchemy(app)
def enable_github_oauth(GITHUB_ENABLE):
if not GITHUB_ENABLE:
return None, None
@ -89,5 +88,9 @@ def enable_google_oauth(GOOGLE_ENABLE):
google = enable_google_oauth(app.config.get('GOOGLE_OAUTH_ENABLE'))
from app import views, models
if app.config.get('SAML_ENABLED') and app.config.get('SAML_ENCRYPT'):
from app.lib import certutil
if not certutil.check_certificate():
certutil.create_self_signed_cert()

48
app/lib/certutil.py Normal file
View File

@ -0,0 +1,48 @@
from OpenSSL import crypto
from datetime import datetime
import pytz
import os
CRYPT_PATH = os.path.abspath(os.path.dirname(os.path.realpath(__file__)) + "/../../")
CERT_FILE = CRYPT_PATH + "/saml_cert.crt"
KEY_FILE = CRYPT_PATH + "/saml_cert.key"
def check_certificate():
if not os.path.isfile(CERT_FILE):
return False
st_cert = open(CERT_FILE, 'rt').read()
cert = crypto.load_certificate(crypto.FILETYPE_PEM, st_cert)
now = datetime.now(pytz.utc)
begin = datetime.strptime(cert.get_notBefore(), "%Y%m%d%H%M%SZ").replace(tzinfo=pytz.UTC)
begin_ok = begin < now
end = datetime.strptime(cert.get_notAfter(), "%Y%m%d%H%M%SZ").replace(tzinfo=pytz.UTC)
end_ok = end > now
if begin_ok and end_ok:
return True
return False
def create_self_signed_cert():
# create a key pair
k = crypto.PKey()
k.generate_key(crypto.TYPE_RSA, 2048)
# create a self-signed cert
cert = crypto.X509()
cert.get_subject().C = "DE"
cert.get_subject().ST = "NRW"
cert.get_subject().L = "Dortmund"
cert.get_subject().O = "Dummy Company Ltd"
cert.get_subject().OU = "Dummy Company Ltd"
cert.get_subject().CN = "PowerDNS-Admin"
cert.set_serial_number(1000)
cert.gmtime_adj_notBefore(0)
cert.gmtime_adj_notAfter(10*365*24*60*60)
cert.set_issuer(cert.get_subject())
cert.set_pubkey(k)
cert.sign(k, 'sha256')
open(CERT_FILE, "wt").write(
crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
open(KEY_FILE, "wt").write(
crypto.dump_privatekey(crypto.FILETYPE_PEM, k))

View File

@ -7,6 +7,44 @@ import hashlib
from app import app
from distutils.version import StrictVersion
from urllib.parse import urlparse
from datetime import datetime, timedelta
from threading import Thread
from .certutil import *
if app.config['SAML_ENABLED']:
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.utils import OneLogin_Saml2_Utils
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser
idp_timestamp = datetime(1970, 1, 1)
idp_data = None
idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(app.config['SAML_METADATA_URL'])
if idp_data is None:
print('SAML: IDP Metadata initial load failed')
exit(-1)
idp_timestamp = datetime.now()
def get_idp_data():
global idp_data, idp_timestamp
lifetime = timedelta(minutes=app.config['SAML_METADATA_CACHE_LIFETIME'])
if idp_timestamp+lifetime < datetime.now():
background_thread = Thread(target=retreive_idp_data)
background_thread.start()
return idp_data
def retreive_idp_data():
global idp_data, idp_timestamp
new_idp_data = OneLogin_Saml2_IdPMetadataParser.parse_remote(app.config['SAML_METADATA_URL'])
if new_idp_data is not None:
idp_data = new_idp_data
idp_timestamp = datetime.now()
print("SAML: IDP Metadata successfully retreived from: " + app.config['SAML_METADATA_URL'])
else:
print("SAML: IDP Metadata could not be retreived")
if 'TIMEOUT' in app.config.keys():
TIMEOUT = app.config['TIMEOUT']
@ -62,7 +100,8 @@ def fetch_remote(remote_url, method='GET', data=None, accept=None, params=None,
def fetch_json(remote_url, method='GET', data=None, params=None, headers=None):
r = fetch_remote(remote_url, method=method, data=data, params=params, headers=headers, accept='application/json; q=1')
r = fetch_remote(remote_url, method=method, data=data, params=params, headers=headers,
accept='application/json; q=1')
if method == "DELETE":
return True
@ -159,3 +198,78 @@ def email_to_gravatar_url(email="", size=100):
"""
hash_string = hashlib.md5(email.encode('utf-8')).hexdigest()
return "https://s.gravatar.com/avatar/{0}?s={1}".format(hash_string, size)
def prepare_flask_request(request):
# If server is behind proxys or balancers use the HTTP_X_FORWARDED fields
url_data = urlparse.urlparse(request.url)
return {
'https': 'on' if request.scheme == 'https' else 'off',
'http_host': request.host,
'server_port': url_data.port,
'script_name': request.path,
'get_data': request.args.copy(),
'post_data': request.form.copy(),
# Uncomment if using ADFS as IdP, https://github.com/onelogin/python-saml/pull/144
'lowercase_urlencoding': True,
'query_string': request.query_string
}
def init_saml_auth(req):
own_url = ''
if req['https'] is 'on':
own_url = 'https://'
else:
own_url = 'http://'
own_url += req['http_host']
metadata = get_idp_data()
settings = {}
settings['sp'] = {}
settings['sp']['NameIDFormat'] = idp_data['sp']['NameIDFormat']
settings['sp']['entityId'] = app.config['SAML_SP_ENTITY_ID']
cert = open(CERT_FILE, "r").readlines()
key = open(KEY_FILE, "r").readlines()
settings['sp']['privateKey'] = "".join(key)
settings['sp']['x509cert'] = "".join(cert)
settings['sp']['assertionConsumerService'] = {}
settings['sp']['assertionConsumerService']['binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
settings['sp']['assertionConsumerService']['url'] = own_url+'/saml/authorized'
settings['sp']['attributeConsumingService'] = {}
settings['sp']['singleLogoutService'] = {}
settings['sp']['singleLogoutService']['binding'] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
settings['sp']['singleLogoutService']['url'] = own_url+'/saml/sls'
settings['idp'] = metadata['idp']
settings['strict'] = True
settings['debug'] = app.config['SAML_DEBUG']
settings['security'] = {}
settings['security']['digestAlgorithm'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
settings['security']['metadataCacheDuration'] = None
settings['security']['metadataValidUntil'] = None
settings['security']['requestedAuthnContext'] = True
settings['security']['signatureAlgorithm'] = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
settings['security']['wantAssertionsEncrypted'] = False
settings['security']['wantAttributeStatement'] = True
settings['security']['wantNameId'] = True
settings['security']['authnRequestsSigned'] = app.config['SAML_SIGN_REQUEST']
settings['security']['logoutRequestSigned'] = app.config['SAML_SIGN_REQUEST']
settings['security']['logoutResponseSigned'] = app.config['SAML_SIGN_REQUEST']
settings['security']['nameIdEncrypted'] = False
settings['security']['signMetadata'] = True
settings['security']['wantAssertionsSigned'] = True
settings['security']['wantMessagesSigned'] = True
settings['security']['wantNameIdEncrypted'] = False
settings['contactPerson'] = {}
settings['contactPerson']['support'] = {}
settings['contactPerson']['support']['emailAddress'] = app.config['SAML_SP_CONTACT_NAME']
settings['contactPerson']['support']['givenName'] = app.config['SAML_SP_CONTACT_MAIL']
settings['contactPerson']['technical'] = {}
settings['contactPerson']['technical']['emailAddress'] = app.config['SAML_SP_CONTACT_NAME']
settings['contactPerson']['technical']['givenName'] = app.config['SAML_SP_CONTACT_MAIL']
settings['organization'] = {}
settings['organization']['en-US'] = {}
settings['organization']['en-US']['displayname'] = 'PowerDNS-Admin'
settings['organization']['en-US']['name'] = 'PowerDNS-Admin'
settings['organization']['en-US']['url'] = own_url
auth = OneLogin_Saml2_Auth(req, settings)
return auth

View File

@ -140,7 +140,9 @@ class User(db.Model):
def check_password(self, hashed_password):
# Check hased password. Useing bcrypt, the salt is saved into the hash itself
return bcrypt.checkpw(self.plain_text_password.encode('utf-8'), hashed_password.encode('utf-8'))
if (self.plain_text_password):
return bcrypt.checkpw(self.plain_text_password.encode('utf-8'), hashed_password.encode('utf-8'))
return False
def get_user_info_by_id(self):
user_info = User.query.get(int(self.id))
@ -276,9 +278,9 @@ class User(db.Model):
self.set_admin(isadmin)
self.update_profile()
return True
logging.error('Unsupported authentication method')
return False
else:
logging.error('Unsupported authentication method')
return False
def create_user(self):
"""
@ -286,10 +288,10 @@ class User(db.Model):
We will create a local user (in DB) in order to manage user
profile such as name, roles,...
"""
# Set an invalid password hash for non local users
self.password = '*'
db.session.add(self)
db.session.commit()
@ -653,9 +655,41 @@ class Domain(db.Model):
logging.debug(traceback.print_exc())
return {'status': 'error', 'msg': 'Cannot add this domain.'}
def update_soa_setting(self, domain_name, soa_edit_api):
domain = Domain.query.filter(Domain.name == domain_name).first()
if not domain:
return {'status': 'error', 'msg': 'Domain doesnt exist.'}
headers = {}
headers['X-API-Key'] = PDNS_API_KEY
if soa_edit_api == 'OFF':
post_data = {
"soa_edit_api": None,
"kind": domain.type
}
else:
post_data = {
"soa_edit_api": soa_edit_api,
"kind": domain.type
}
try:
jdata = utils.fetch_json(
urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain.name)), headers=headers,
method='PUT', data=post_data)
if 'error' in jdata.keys():
logging.error(jdata['error'])
return {'status': 'error', 'msg': jdata['error']}
else:
logging.info('soa-edit-api changed for domain {0} successfully'.format(domain_name))
return {'status': 'ok', 'msg': 'soa-edit-api changed successfully'}
except Exception as e:
logging.debug(e)
logging.debug(traceback.format_exc())
logging.error('Cannot change soa-edit-api for domain {0}'.format(domain_name))
return {'status': 'error', 'msg': 'Cannot change soa-edit-api this domain.'}
def create_reverse_domain(self, domain_name, domain_reverse_name):
"""
Check the existing reverse lookup domain,
Check the existing reverse lookup domain,
if not exists create a new one automatically
"""
domain_obj = Domain.query.filter(Domain.name == domain_name).first()
@ -799,6 +833,50 @@ class Domain(db.Model):
else:
return {'status': 'error', 'msg': 'This domain doesnot exist'}
def enable_domain_dnssec(self, domain_name):
"""
Enable domain DNSSEC
"""
domain = Domain.query.filter(Domain.name == domain_name).first()
if domain:
headers = {}
headers['X-API-Key'] = PDNS_API_KEY
post_data = {
"keytype": "ksk",
"active": True
}
try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys'.format(domain.name)), headers=headers, method='POST',data=post_data)
if 'error' in jdata:
return {'status': 'error', 'msg': 'DNSSEC is not enabled for this domain', 'jdata' : jdata}
else:
return {'status': 'ok'}
except:
logging.error(traceback.print_exc())
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
else:
return {'status': 'error', 'msg': 'This domain does not exist'}
def delete_dnssec_key(self, domain_name, key_id):
"""
Remove keys DNSSEC
"""
domain = Domain.query.filter(Domain.name == domain_name).first()
if domain:
headers = {}
headers['X-API-Key'] = PDNS_API_KEY
url = '/servers/localhost/zones/{0}/cryptokeys/{1}'.format(domain.name, key_id)
try:
jdata = utils.fetch_json(urljoin(PDNS_STATS_URL, API_EXTENDED_URL + url), headers=headers, method='DELETE')
if 'error' in jdata:
return {'status': 'error', 'msg': 'DNSSEC is not disabled for this domain', 'jdata' : jdata}
else:
return {'status': 'ok'}
except:
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator','id': key_id, 'url': url}
else:
return {'status': 'error', 'msg': 'This domain doesnot exist'}
class DomainUser(db.Model):
__tablename__ = 'domain_user'
@ -953,7 +1031,7 @@ class Record(object):
if r_type == 'PTR': # only ptr
if ':' in r['record_name']: # dirty ipv6 check
r_name = r['record_name']
record = {
"name": r_name,
"type": r_type,
@ -962,7 +1040,7 @@ class Record(object):
"ttl": int(r['record_ttl']) if r['record_ttl'] else 3600,
}
records.append(record)
deleted_records, new_records = self.compare(domain, records)
records = []
@ -974,7 +1052,7 @@ class Record(object):
if r_type == 'PTR': # only ptr
if ':' in r['name']: # dirty ipv6 check
r_name = dns.reversename.from_address(r['name']).to_text()
record = {
"name": r_name,
"type": r_type,
@ -1035,12 +1113,12 @@ class Record(object):
r_name = key[0]
r_type = key[1]
r_changetype = key[2]
if PRETTY_IPV6_PTR: # only if activated
if r_type == 'PTR': # only ptr
if ':' in r_name: # dirty ipv6 check
r_name = dns.reversename.from_address(r_name).to_text()
new_record = {
"name": r_name,
"type": r_type,

View File

@ -1,3 +1,5 @@
var dnssecKeyList = []
function applyChanges(data, url, showResult, refreshPage) {
var success = false;
$.ajax({
@ -116,7 +118,22 @@ function SelectElement(elementID, valueToSelect)
element.value = valueToSelect;
}
function getdnssec(url){
function enable_dns_sec(url) {
$.getJSON(url, function(data) {
var modal = $("#modal_dnssec_info");
if (data['status'] == 'error'){
modal.find('.modal-body p').text(data['msg']);
}
else {
modal.modal('hide');
//location.reload();
window.location.reload(true);
}
})
}
function getdnssec(url, domain){
$.getJSON(url, function(data) {
var modal = $("#modal_dnssec_info");
@ -127,23 +144,36 @@ function getdnssec(url){
else {
dnssec_msg = '';
var dnssec = data['dnssec'];
for (var i = 0; i < dnssec.length; i++) {
if (dnssec[i]['active']){
dnssec_msg += '<form>'+
'<h3><strong>'+dnssec[i]['keytype']+'</strong></h3>'+
'<strong>DNSKEY</strong>'+
'<input class="form-control" autocomplete="off" type="text" readonly="true" value="'+dnssec[i]['dnskey']+'">'+
'</form>'+
'<br/>';
if(dnssec[i]['ds']){
var dsList = dnssec[i]['ds'];
dnssec_msg += '<strong>DS</strong>';
for (var j = 0; j < dsList.length; j++){
dnssec_msg += '<input class="form-control" autocomplete="off" type="text" readonly="true" value="'+dsList[j]+'">';
}
}
dnssec_msg += '</form>';
if (dnssec.length == 0 && parseFloat(PDNS_VERSION) >= 4.1) {
dnssec_msg = '<h3>DNSSEC is disabled. Click on Enable to activate it.';
modal.find('.modal-body p').html(dnssec_msg);
dnssec_footer = '<button type="button" class="btn btn-flat btn-success button_dnssec_enable pull-left" id="'+domain+'">Enable</button><button type="button" class="btn btn-flat btn-default pull-right" data-dismiss="modal">Cancel</button>';
modal.find('.modal-footer ').html(dnssec_footer);
}
else {
if (parseFloat(PDNS_VERSION) >= 4.1) {
dnssec_footer = '<button type="button" class="btn btn-flat btn-danger button_dnssec_disable pull-left" id="'+domain+'">Disable DNSSEC</button><button type="button" class="btn btn-flat btn-default pull-right" data-dismiss="modal">Close</button>';
modal.find('.modal-footer ').html(dnssec_footer);
}
for (var i = 0; i < dnssec.length; i++) {
if (dnssec[i]['active']){
dnssec_msg += '<form>'+
'<h3><strong>'+dnssec[i]['keytype']+'</strong></h3>'+
'<strong>DNSKEY</strong>'+
'<input class="form-control" autocomplete="off" type="text" readonly="true" value="'+dnssec[i]['dnskey']+'">'+
'</form>'+
'<br/>';
if(dnssec[i]['ds']){
var dsList = dnssec[i]['ds'];
dnssec_msg += '<strong>DS</strong>';
for (var j = 0; j < dsList.length; j++){
dnssec_msg += '<input class="form-control" autocomplete="off" type="text" readonly="true" value="'+dsList[j]+'">';
}
}
dnssec_msg += '</form>';
}
}
}
modal.find('.modal-body p').html(dnssec_msg);
}

View File

@ -83,7 +83,7 @@
<small>{{ current_user.role.name }}</small>
</p>
</li>
<!-- Menu Footer-->
<li class="user-footer">
<div class="pull-left">

View File

@ -154,6 +154,7 @@
{% endblock %}
{% block extrascripts %}
<script>
PDNS_VERSION = '{{ pdns_version }}'
// set up history data table
$("#tbl_history").DataTable({
"paging" : false,
@ -214,6 +215,23 @@
modal.modal('show');
});
$(document.body).on("click", ".button_dnssec", function() {
var domain = $(this).prop('id');
getdnssec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec', domain);
});
$(document.body).on("click", ".button_dnssec_enable", function() {
var domain = $(this).prop('id');
enable_dns_sec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec/enable');
});
$(document.body).on("click", ".button_dnssec_disable", function() {
var domain = $(this).prop('id');
enable_dns_sec($SCRIPT_ROOT + '/domain/' + domain + '/dnssec/disable');
});
</script>
{% endblock %}
{% block modals %}
@ -262,4 +280,28 @@
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
<div class="modal fade" id="modal_dnssec_info">
<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">DNSSEC</h4>
</div>
<div class="modal-body">
<p></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-flat btn-default pull-right"
data-dismiss="modal">Close</button>
</div>
</div>
<!-- /.modal-content -->
</div>
<!-- /.modal-dialog -->
</div>
<!-- /.modal -->
{% endblock %}

View File

@ -4,9 +4,9 @@
{% macro dnssec(domain) %}
{% if domain.dnssec %}
<td><span class="label label-success"><i class="fa fa-lock-alt"></i>&nbsp;Enabled</span></td>
<td><span style="cursor:pointer" class="label label-success button_dnssec" id="{{ domain.name }}"><i class="fa fa-lock"></i>&nbsp;Enabled</span></td>
{% else %}
<td><span class="label label-primary"><i class="fa fa-unlock-alt"></i>&nbsp;Disabled</span></td>
<td><span style="cursor:pointer" class="label label-primary button_dnssec" id="{{ domain.name }}"><i class="fa fa-unlock-alt"></i>&nbsp;Disabled</span></td>
{% endif %}
{% endmacro %}

View File

@ -82,7 +82,7 @@
<button type="button" class="btn btn-flat btn-warning">&nbsp;&nbsp;<i class="fa fa-exclamation-circle"></i>&nbsp;&nbsp;</button>
</td>
<td width="6%">
<button type="button" class="btn btn-flat btn-warning">&nbsp;&nbsp;<i class="fa fa-exclamation-circle"></i>&nbsp;&nbsp;</button>
<button type="button" class="btn btn-flat btn-warning"">&nbsp;&nbsp;<i class="fa fa-exclamation-circle"></i>&nbsp;&nbsp;</button>
</td>
{% endif %}
</td>
@ -104,6 +104,7 @@
{% endblock %}
{% block extrascripts %}
<script>
PDNS_VERSION = '{{ pdns_version }}'
// superglobals
window.records_allow_edit = {{ editable_records|tojson }};
window.nEditing = null;
@ -143,7 +144,7 @@
],
"orderFixed": [[7, 'asc']]
});
// handle delete button
$(document.body).on("click", ".button_delete", function(e) {
e.stopPropagation();
@ -151,25 +152,25 @@
var table = $("#tbl_records").DataTable();
var record = $(this).prop('id');
var nRow = $(this).parents('tr')[0];
var info = "Are you sure you want to delete " + record + "?";
var info = "Are you sure you want to delete " + record + "?";
modal.find('.modal-body p').text(info);
modal.find('#button_delete_confirm').click(function() {
table.row(nRow).remove().draw();
modal.modal('hide');
})
modal.modal('show');
});
// handle edit button
$(document.body).on("click", ".button_edit, .row_record", function(e) {
e.stopPropagation();
if ($(this).is('tr')) {
var nRow = $(this)[0];
var nRow = $(this)[0];
} else {
var nRow = $(this).parents('tr')[0];
}
var table = $("#tbl_records").DataTable();
if (nEditing == nRow) {
/* click on row already being edited, do nothing */
} else if (nEditing !== null && nEditing != nRow && nNew == false) {
@ -189,13 +190,13 @@
nEditing = nRow;
}
});
// handle apply changes button
$(document.body).on("click",".button_apply_changes", function() {
var modal = $("#modal_apply_changes");
var table = $("#tbl_records").DataTable();
var domain = $(this).prop('id');
var info = "Are you sure you want to apply your changes?";
var info = "Are you sure you want to apply your changes?";
modal.find('.modal-body p').text(info);
modal.find('#button_apply_confirm').click(function() {
var data = getTableData(table);
@ -203,9 +204,9 @@
modal.modal('hide');
})
modal.modal('show');
});
// handle add record button
$(document.body).on("click", ".button_add_record", function (e) {
if (nNew || nEditing) {
@ -216,7 +217,7 @@
}
// clear search first
$("#tbl_records").DataTable().search('').columns().search('').draw();
// add new row
var default_type = records_allow_edit[0]
var nRow = jQuery('#tbl_records').dataTable().fnAddData(['', default_type, 'Active', 3600, '', '', '', '0']);
@ -225,7 +226,7 @@
nEditing = nRow;
nNew = true;
});
//handle cancel button
$(document.body).on("click", ".button_cancel", function (e) {
e.stopPropagation();
@ -239,7 +240,7 @@
nEditing = null;
}
});
//handle save button
$(document.body).on("click", ".button_save", function (e) {
e.stopPropagation();
@ -248,13 +249,13 @@
nEditing = null;
nNew = false;
});
//handle update_from_master button
$(document.body).on("click", ".button_update_from_master", function (e) {
var domain = $(this).prop('id');
applyChanges({'domain': domain}, $SCRIPT_ROOT + '/domain/' + domain + '/update');
});
{% if record_helper_setting %}
//handle wacky record types
$(document.body).on("focus", "#current_edit_record_data", function (e) {
@ -271,7 +272,7 @@
<input type=\"text\" class=\"form-control\" name=\"caa_value\" id=\"caa_value\" placeholder=\"eg. letsencrypt.org\"> \
";
} else {
var parts = record_data.val().split(" ");
var parts = record_data.val().split(" ");
var form = " <label for=\"caa_flag\">CAA Flag</label> \
<input type=\"text\" class=\"form-control\" name=\"caa_flag\" id=\"caa_flag\" placeholder=\"0\" value=\"" + parts[0] + "\"> \
<label for=\"caa_tag\">CAA Tag</label> \
@ -328,7 +329,7 @@
<input type=\"text\" class=\"form-control\" name=\"srv_target\" id=\"srv_target\" placeholder=\"sip.example.com\"> \
";
} else {
var parts = record_data.val().split(" ");
var parts = record_data.val().split(" ");
var form = " <label for=\"srv_priority\">SRV Priority</label> \
<input type=\"text\" class=\"form-control\" name=\"srv_priority\" id=\"srv_priority\" placeholder=\"0\" value=\"" + parts[0] + "\"> \
<label for=\"srv_weight\">SRV Weight</label> \
@ -369,7 +370,7 @@
<input type=\"text\" class=\"form-control\" name=\"soa_minimumttl\" id=\"soa_minimumttl\" placeholder=\"300\"> \
";
} else {
var parts = record_data.val().split(" ");
var parts = record_data.val().split(" ");
var form = " <label for=\"soa_primaryns\">Primary Name Server</label> \
<input type=\"text\" class=\"form-control\" name=\"soa_primaryns\" id=\"soa_primaryns\" value=\"" + parts[0] + "\"> \
<label for=\"soa_adminemail\">Primary Contact</label> \
@ -387,7 +388,7 @@
";
}
modal.find('.modal-body p').html(form);
modal.find('#button_save').click(function() {
modal.find('#button_save').click(function() {
soa_primaryns = modal.find('#soa_primaryns').val();
soa_adminemail = modal.find('#soa_adminemail').val();
soa_serial = modal.find('#soa_serial').val();
@ -395,7 +396,7 @@
soa_failedzonerefresh = modal.find('#soa_failedzonerefresh').val();
soa_zoneexpiry = modal.find('#soa_zoneexpiry').val();
soa_minimumttl = modal.find('#soa_minimumttl').val();
data = soa_primaryns + " " + soa_adminemail + " " + soa_serial + " " + soa_zonerefresh + " " + soa_failedzonerefresh + " " + soa_zoneexpiry + " " + soa_minimumttl;
record_data.val(data);
modal.modal('hide');

View File

@ -1,6 +1,22 @@
{% extends "base.html" %}
{% block title %}<title>DNS Control Panel - Domain Management</title>{% endblock %}
{% block dashboard_stat %}
{% if status %}
{% if status.get('status') == 'ok' %}
<div class="alert alert-success">
<strong>Success!</strong> {{ status.get('msg') }}
</div>
{% elif status.get('status') == 'error' %}
<div class="alert alert-danger">
{% if status.get('msg') != None %}
<strong>Error!</strong> {{ status.get('msg') }}
{% else %}
<strong>Error!</strong> An undefined error occurred.
{% endif %}
</div>
{% endif %}
{% endif %}
<section class="content-header">
<h1>
Manage domain <small>{{ domain.name }}</small>
@ -86,6 +102,57 @@
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="box">
<div class="box-header">
<h3 class="box-title">Change SOA-EDIT-API</h3>
</div>
<div class="box-body">
<p>The SOA-EDIT-API setting defines when and how the SOA serial number will be updated after a change is made to the domain.</p>
<ul>
<li>
(OFF) - Not set
</li>
<li>
INCEPTION-INCREMENT - Uses YYYYMMDDSS format for SOA serial numbers. If the SOA serial from the backend is within two days after inception, it gets incremented by two (the backend should keep SS below 98).
</li>
<li>
INCEPTION - Sets the SOA serial to the last inception time in YYYYMMDD01 format. Uses localtime to find the day for inception time. <strong>Not recomended.</strong>
</li>
<li>
INCREMENT-WEEK - Sets the SOA serial to the number of weeks since the epoch, which is the last inception time in weeks. <strong>Not recomended.</strong>
</li>
<li>
INCREMENT-WEEKS - Increments the serial with the number of weeks since the UNIX epoch. This should work in every setup; but the result won't look like YYYYMMDDSS anymore.
</li>
<li>
EPOCH - Sets the SOA serial to the number of seconds since the epoch.
</li>
<li>
INCEPTION-EPOCH - Sets the new SOA serial number to the maximum of the old SOA serial number, and age in seconds of the last inception.
</li>
</ul>
<b>New SOA-EDIT-API Setting:</b>
<form method="post" action="{{ url_for('domain_change_soa_edit_api', domain_name=domain.name) }}">
<select name="soa_edit_api" class="form-control" style="width:15em;">
<option selected value="0">- Unchanged -</option>
<option>OFF</option>
<option>INCEPTION-INCREMENT</option>
<option>INCEPTION</option>
<option>INCREMENT-WEEK</option>
<option>INCREMENT-WEEKS</option>
<option>EPOCH</option>
<option>INCEPTION-EPOCH</option>
</select><br/>
<button type="submit" class="btn btn-flat btn-primary" id="change_soa_edit_api">
<i class="fa fa-check"></i>&nbsp;Change SOA-EDIT-API setting for {{ domain.name }}
</button>
</form>
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="box">

View File

@ -0,0 +1,45 @@
{% extends "base.html" %}
{% block title %}<title>DNS Control Panel - SAML Authentication Error</title>{% endblock %}
{% block dashboard_stat %}
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
SAML
<small>Error</small>
</h1>
<ol class="breadcrumb">
<li><a href="{{ url_for('dashboard') }}"><i class="fa fa-dashboard"></i>Home</a></li>
<li>SAML</li>
</ol>
</section>
{% endblock %}
{% block content %}
<!-- Main content -->
<section class="content">
<div class="error-page">
<div>
<h1 class="headline text-yellow" style="font-size:46px;">SAML Authentication Error</h1></div><br/><br/>
<div class="error-content">
<h3>
<i class="fa fa-warning text-yellow"></i> Oops! Something went wrong
</h3><br>
<p>
Login failed.<br>
Error(s) when processing SAML Response:<br>
<ul>
{% for error in errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
You may <a href="{{ url_for('dashboard') }}">return to the dashboard</a>.
</p>
</div>
<!-- /.error-content -->
</div>
<!-- /.error-page -->
</section>
<!-- /.content -->
{% endblock %}

View File

@ -101,11 +101,16 @@
{% if google_enabled %}
<a href="{{ url_for('google_login') }}">Google oauth login</a>
{% endif %}
{% if saml_enabled %}
<br>
<a href="{{ url_for('saml_login') }}">SAML login</a>
{% endif %}
{% if github_enabled %}
<br>
<a href="{{ url_for('github_login') }}">Github oauth login</a>
{% endif %}
<br>
{% if signup_enabled %}
<br>
<a href="{{ url_for('register') }}" class="text-center">Create an account </a>
{% endif %}
</div>

View File

@ -19,7 +19,7 @@
<div class="col-lg-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Edit my profile</h3>
<h3 class="box-title">Edit my profile{% if external_account %} [Disabled - Authenticated externally]{% endif %}</h3>
</div>
<div class="box-body">
<!-- Custom Tabs -->
@ -29,10 +29,10 @@
Info</a></li>
<li><a href="#tabs-avatar" data-toggle="tab">Change
Avatar</a></li>
<li><a href="#tabs-password" data-toggle="tab">Change
{% if not external_account %}<li><a href="#tabs-password" data-toggle="tab">Change
Password</a></li>
<li><a href="#tabs-authentication" data-toggle="tab">Authentication
</a></li>
</a></li>{% endif %}>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="tabs-personal">
@ -40,21 +40,21 @@
<div class="form-group">
<label for="firstname">First Name</label> <input type="text"
class="form-control" name="firstname" id="firstname"
placeholder="{{ current_user.firstname }}">
placeholder="{{ current_user.firstname }}" {% if external_account %}disabled{% endif %}>
</div>
<div class="form-group">
<label for="lastname">Last Name</label> <input type="text"
class="form-control" name="lastname" id="lastname"
placeholder="{{ current_user.lastname }}">
placeholder="{{ current_user.lastname }}" {% if external_account %}disabled{% endif %}>
</div>
<div class="form-group">
<label for="email">E-mail</label> <input type="text"
class="form-control" name="email" id="email"
placeholder="{{ current_user.email }}">
</div>
placeholder="{{ current_user.email }}" {% if external_account %}disabled{% endif %}>
</div>{% if not external_account %}
<div class="form-group">
<button type="submit" class="btn btn-flat btn-primary">Submit</button>
</div>
</div>{% endif %}
</form>
</div>
<div class="tab-pane" id="tabs-avatar">
@ -69,25 +69,25 @@
else %} <img
src="{{ current_user.email|email_to_gravatar_url(size=200) }}"
alt="" /> {% endif %}
</div>
</div>{% if not external_account %}
<div>
<label for="file">Select image</label> <input type="file"
id="file" name="file">
</div>
</div>
</div>{% endif %}
</div>{% if not external_account %}
<div>
<span class="label label-danger">NOTE! </span> <span>&nbsp;Only
supports <strong>.PNG, .JPG, .JPEG</strong>. The best size
to use is <strong>200x200</strong>.
</span>
</div>
</div>
</div>{% endif %}
</div>{% if not external_account %}
<div class="form-group">
<button type="submit" class="btn btn-flat btn-primary">Submit</button>
</div>
</div>{% endif %}
</form>
</div>
<div class="tab-pane" id="tabs-password">
{% if not external_account %}<div class="tab-pane" id="tabs-password">
{% if not current_user.password %} Your account password is
managed via LDAP which isn't supported to change here. {% else
%}
@ -95,15 +95,15 @@
<div class="form-group">
<label for="password">New Password</label> <input
type="password" class="form-control" name="password"
id="newpassword" />
id="newpassword" {% if external_account %}disabled{% endif %} />
</div>
<div class="form-group">
<label for="rpassword">Re-type New Password</label> <input
type="password" class="form-control" name="rpassword"
id="rpassword" />
id="rpassword" {% if external_account %}disabled{% endif %} />
</div>
<div class="form-group">
<button type="submit" class="btn btn-flat btn-primary">Change
<button type="submit" class="btn btn-flat btn-primary" {% if external_account %}disabled{% endif %}>Change
password</button>
</div>
</form>
@ -112,7 +112,7 @@
<div class="tab-pane" id="tabs-authentication">
<form action="{{ user_profile }}" method="post">
<div class="form-group">
<input type="checkbox" id="otp_toggle" class="otp_toggle" {% if current_user.otp_secret %}checked{% endif %}>
<input type="checkbox" id="otp_toggle" class="otp_toggle" {% if current_user.otp_secret %}checked{% endif %} {% if external_account %}disabled{% endif %}>
<label for="otp_toggle">Enable Two Factor Authentication</label>
{% if current_user.otp_secret %}
<div id="token_information">
@ -124,7 +124,7 @@
{% endif %}
</div>
</form>
</div>
</div>{% endif %}
</div>
</div>
</div>

View File

@ -22,6 +22,10 @@ from app.lib import utils
from app.lib.log import logger
from app.decorators import admin_role_required, can_access_domain
if app.config['SAML_ENABLED']:
from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.utils import OneLogin_Saml2_Utils
# LOG CONFIG
logging = logger('MODEL', app.config['LOG_LEVEL'], app.config['LOG_FILE']).config()
@ -184,6 +188,70 @@ def github_login():
return abort(400)
return github.authorize(callback=url_for('authorized', _external=True))
@app.route('/saml/login')
def saml_login():
if not app.config.get('SAML_ENABLED'):
return abort(400)
req = utils.prepare_flask_request(request)
auth = utils.init_saml_auth(req)
redirect_url=OneLogin_Saml2_Utils.get_self_url(req) + url_for('saml_authorized')
return redirect(auth.login(return_to=redirect_url))
@app.route('/saml/metadata')
def saml_metadata():
if not app.config.get('SAML_ENABLED'):
return abort(400)
req = utils.prepare_flask_request(request)
auth = utils.init_saml_auth(req)
settings = auth.get_settings()
metadata = settings.get_sp_metadata()
errors = settings.validate_metadata(metadata)
if len(errors) == 0:
resp = make_response(metadata, 200)
resp.headers['Content-Type'] = 'text/xml'
else:
resp = make_response(errors.join(', '), 500)
return resp
@app.route('/saml/authorized', methods=['GET', 'POST'])
def saml_authorized():
errors = []
if not app.config.get('SAML_ENABLED'):
return abort(400)
req = utils.prepare_flask_request(request)
auth = utils.init_saml_auth(req)
auth.process_response()
errors = auth.get_errors()
if len(errors) == 0:
session['samlUserdata'] = auth.get_attributes()
session['samlNameId'] = auth.get_nameid()
session['samlSessionIndex'] = auth.get_session_index()
self_url = OneLogin_Saml2_Utils.get_self_url(req)
self_url = self_url+req['script_name']
if 'RelayState' in request.form and self_url != request.form['RelayState']:
return redirect(auth.redirect_to(request.form['RelayState']))
user = User.query.filter_by(username=session['samlNameId'].lower()).first()
if not user:
# create user
user = User(username=session['samlNameId'],
plain_text_password = None,
email=session['samlNameId'])
user.create_local_user()
session['user_id'] = user.id
if session['samlUserdata'].has_key("email"):
user.email = session['samlUserdata']["email"][0].lower()
if session['samlUserdata'].has_key("givenname"):
user.firstname = session['samlUserdata']["givenname"][0]
if session['samlUserdata'].has_key("surname"):
user.lastname = session['samlUserdata']["surname"][0]
user.plain_text_password = None
user.update_profile()
session['external_auth'] = True
login_user(user, remember=False)
return redirect(url_for('index'))
else:
return render_template('errors/SAML.html', errors=errors)
@app.route('/login', methods=['GET', 'POST'])
@login_manager.unauthorized_handler
@ -191,9 +259,10 @@ def login():
LOGIN_TITLE = app.config['LOGIN_TITLE'] if 'LOGIN_TITLE' in app.config.keys() else ''
BASIC_ENABLED = app.config['BASIC_ENABLED']
SIGNUP_ENABLED = app.config['SIGNUP_ENABLED']
LDAP_ENABLE = app.config.get('LDAP_ENABLE')
LDAP_ENABLED = app.config.get('LDAP_ENABLED')
GITHUB_ENABLE = app.config.get('GITHUB_OAUTH_ENABLE')
GOOGLE_ENABLE = app.config.get('GOOGLE_OAUTH_ENABLE')
SAML_ENABLED = app.config.get('SAML_ENABLED')
if g.user is not None and current_user.is_authenticated:
return redirect(url_for('dashboard'))
@ -209,7 +278,7 @@ def login():
user = User(username=email,
firstname=first_name,
lastname=surname,
plain_text_password=gen_salt(7),
plain_text_password=None,
email=email)
result = user.create_local_user()
@ -219,6 +288,7 @@ def login():
session['user_id'] = user.id
login_user(user, remember = False)
session['external_auth'] = True
return redirect(url_for('index'))
if 'github_token' in session:
@ -228,7 +298,7 @@ def login():
if not user:
# create user
user = User(username=user_info['name'],
plain_text_password=gen_salt(7),
plain_text_password=None,
email=user_info['email'])
result = user.create_local_user()
@ -237,6 +307,7 @@ def login():
return redirect(url_for('login'))
session['user_id'] = user.id
session['external_auth'] = True
login_user(user, remember = False)
return redirect(url_for('index'))
@ -244,7 +315,8 @@ def login():
return render_template('login.html',
github_enabled=GITHUB_ENABLE,
google_enabled=GOOGLE_ENABLE,
ldap_enabled=LDAP_ENABLE, login_title=LOGIN_TITLE,
saml_enabled=SAML_ENABLED,
ldap_enabled=LDAP_ENABLED, login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED)
# process login
@ -270,18 +342,37 @@ def login():
try:
auth = user.is_validate(method=auth_method)
if auth == False:
return render_template('login.html', error='Invalid credentials', ldap_enabled=LDAP_ENABLE, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED)
return render_template('login.html', error='Invalid credentials', ldap_enabled=LDAP_ENABLED,
login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED,
signup_enabled=SIGNUP_ENABLED,
github_enabled=GITHUB_ENABLE,
saml_enabled=SAML_ENABLED)
except Exception as e:
return render_template('login.html', error=e, ldap_enabled=LDAP_ENABLE, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED)
return render_template('login.html', error=e, ldap_enabled=LDAP_ENABLED, login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED,
signup_enabled=SIGNUP_ENABLED,
github_enabled=GITHUB_ENABLE,
saml_enabled=SAML_ENABLED)
# check if user enabled OPT authentication
if user.otp_secret:
if otp_token:
good_token = user.verify_totp(otp_token)
if not good_token:
return render_template('login.html', error='Invalid credentials', ldap_enabled=LDAP_ENABLE, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED)
return render_template('login.html', error='Invalid credentials', ldap_enabled=LDAP_ENABLED,
login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED,
signup_enabled=SIGNUP_ENABLED,
github_enabled=GITHUB_ENABLE,
saml_enabled=SAML_ENABLED)
else:
return render_template('login.html', error='Token required', ldap_enabled=LDAP_ENABLE, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED)
return render_template('login.html', error='Token required', ldap_enabled=LDAP_ENABLED,
login_title=LOGIN_TITLE,
basic_enabled=BASIC_ENABLED,
signup_enabled=SIGNUP_ENABLED,
github_enabled = GITHUB_ENABLE,
saml_enabled = SAML_ENABLED)
login_user(user, remember = remember_me)
return redirect(request.args.get('next') or url_for('index'))
@ -297,22 +388,53 @@ def login():
try:
result = user.create_local_user()
if result['status'] == True:
return render_template('login.html', username=username, password=password, ldap_enabled=LDAP_ENABLE, login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED)
if result == True:
return render_template('login.html', username=username, password=password, ldap_enabled=LDAP_ENABLED,
login_title=LOGIN_TITLE, basic_enabled=BASIC_ENABLED, signup_enabled=SIGNUP_ENABLED,
github_enabled=GITHUB_ENABLE,saml_enabled=SAML_ENABLED)
else:
return render_template('register.html', error=result['msg'])
except Exception as e:
return render_template('register.html', error=e)
@app.route('/logout')
def logout():
def clear_session():
session.pop('user_id', None)
session.pop('github_token', None)
session.pop('google_token', None)
session.clear()
logout_user()
@app.route('/logout')
def logout():
if app.config.get('SAML_ENABLED') and 'samlSessionIndex' in session and app.config.get('SAML_LOGOUT'):
req = utils.prepare_flask_request(request)
auth = utils.init_saml_auth(req)
if app.config.get('SAML_LOGOUT_URL'):
return redirect(auth.logout(name_id_format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
return_to = app.config.get('SAML_LOGOUT_URL'),
session_index = session['samlSessionIndex'], name_id=session['samlNameId']))
return redirect(auth.logout(name_id_format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress",
session_index = session['samlSessionIndex'],
name_id=session['samlNameId']))
clear_session()
return redirect(url_for('login'))
@app.route('/saml/sls')
def saml_logout():
req = utils.prepare_flask_request(request)
auth = utils.init_saml_auth(req)
url = auth.process_slo()
errors = auth.get_errors()
if len(errors) == 0:
clear_session()
if url is not None:
return redirect(url)
elif app.config.get('SAML_LOGOUT_URL') is not None:
return redirect(app.config.get('SAML_LOGOUT_URL'))
else:
return redirect(url_for('login'))
else:
return render_template('errors/SAML.html', errors=errors)
@app.route('/dashboard', methods=['GET', 'POST'])
@login_required
@ -330,7 +452,7 @@ def dashboard():
uptime = list([uptime for uptime in statistics if uptime['name'] == 'uptime'])[0]['value']
else:
uptime = 0
return render_template('dashboard.html', domain_count=domain_count, users=users, history_number=history_number, uptime=uptime, histories=history)
return render_template('dashboard.html', domain_count=domain_count, users=users, history_number=history_number, uptime=uptime, histories=history,pdns_version=app.config['PDNS_VERSION'])
@app.route('/dashboard-domains', methods=['GET'])
@ -407,27 +529,24 @@ def dashboard_domains():
def domain(domain_name):
r = Record()
domain = Domain.query.filter(Domain.name == domain_name).first()
if domain:
# query domain info from PowerDNS API
zone_info = r.get_record_data(domain.name)
if zone_info:
jrecords = zone_info['records']
else:
# can not get any record, API server might be down
return redirect(url_for('error', code=500))
if not domain:
return redirect(url_for('error', code=404))
records = []
#TODO: This should be done in the "model" instead of "view"
if NEW_SCHEMA:
for jr in jrecords:
if jr['type'] in app.config['RECORDS_ALLOW_EDIT']:
for subrecord in jr['records']:
record = Record(name=jr['name'], type=jr['type'], status='Disabled' if subrecord['disabled'] else 'Active', ttl=jr['ttl'], data=subrecord['content'])
records.append(record)
else:
for jr in jrecords:
if jr['type'] in app.config['RECORDS_ALLOW_EDIT']:
record = Record(name=jr['name'], type=jr['type'], status='Disabled' if jr['disabled'] else 'Active', ttl=jr['ttl'], data=jr['content'])
# query domain info from PowerDNS API
zone_info = r.get_record_data(domain.name)
if zone_info:
jrecords = zone_info['records']
else:
# can not get any record, API server might be down
return redirect(url_for('error', code=500))
records = []
#TODO: This should be done in the "model" instead of "view"
if NEW_SCHEMA:
for jr in jrecords:
if jr['type'] in app.config['RECORDS_ALLOW_EDIT']:
for subrecord in jr['records']:
record = Record(name=jr['name'], type=jr['type'], status='Disabled' if subrecord['disabled'] else 'Active', ttl=jr['ttl'], data=subrecord['content'])
records.append(record)
if not re.search('ip6\.arpa|in-addr\.arpa$', domain_name):
editable_records = app.config['RECORDS_ALLOW_EDIT']
@ -435,7 +554,15 @@ def domain(domain_name):
editable_records = app.config['REVERSE_ALLOW_EDIT']
return render_template('domain.html', domain=domain, records=records, editable_records=editable_records)
else:
return redirect(url_for('error', code=404))
for jr in jrecords:
if jr['type'] in app.config['RECORDS_ALLOW_EDIT']:
record = Record(name=jr['name'], type=jr['type'], status='Disabled' if jr['disabled'] else 'Active', ttl=jr['ttl'], data=jr['content'])
records.append(record)
if not re.search('ip6\.arpa|in-addr\.arpa$', domain_name):
editable_records = app.config['FORWARD_RECORDS_ALLOW_EDIT']
else:
editable_records = app.config['REVERSE_RECORDS_ALLOW_EDIT']
return render_template('domain.html', domain=domain, records=records, editable_records=editable_records,pdns_version=app.config['PDNS_VERSION'])
@app.route('/admin/domain/add', methods=['GET', 'POST'])
@ -539,6 +666,30 @@ def domain_management(domain_name):
return redirect(url_for('domain_management', domain_name=domain_name))
@app.route('/admin/domain/<path:domain_name>/change_soa_setting', methods=['POST'])
@login_required
@admin_role_required
def domain_change_soa_edit_api(domain_name):
domain = Domain.query.filter(Domain.name == domain_name).first()
if not domain:
return redirect(url_for('error', code=404))
new_setting = request.form.get('soa_edit_api')
if new_setting == None:
return redirect(url_for('error', code=500))
if new_setting == '0':
return redirect(url_for('domain_management', domain_name=domain_name))
d = Domain()
status = d.update_soa_setting(domain_name=domain_name, soa_edit_api=new_setting)
if status['status'] != None:
users = User.query.all()
d = Domain(name=domain_name)
domain_user_ids = d.get_user()
return render_template('domain_management.html', domain=domain, users=users, domain_user_ids=domain_user_ids, status=status)
else:
return redirect(url_for('error', code=500))
@app.route('/domain/<path:domain_name>/apply', methods=['POST'], strict_slashes=False)
@login_required
@can_access_domain
@ -602,14 +753,36 @@ def record_delete(domain_name, record_name, record_type):
@app.route('/domain/<path:domain_name>/dnssec', methods=['GET'])
@can_access_domain
@login_required
@can_access_domain
def domain_dnssec(domain_name):
domain = Domain()
dnssec = domain.get_domain_dnssec(domain_name)
return make_response(jsonify(dnssec), 200)
@app.route('/domain/<path:domain_name>/dnssec/enable', methods=['GET'])
@login_required
@can_access_domain
def domain_dnssec_enable(domain_name):
domain = Domain()
dnssec = domain.enable_domain_dnssec(domain_name)
return make_response(jsonify(dnssec), 200)
@app.route('/domain/<path:domain_name>/dnssec/disable', methods=['GET'])
@login_required
@can_access_domain
def domain_dnssec_disable(domain_name):
domain = Domain()
dnssec = domain.get_domain_dnssec(domain_name)
for key in dnssec['dnssec']:
response = domain.delete_dnssec_key(domain_name,key['id']);
return make_response(jsonify( { 'status': 'ok', 'msg': 'DNSSEC removed.' } ))
@app.route('/domain/<path:domain_name>/managesetting', methods=['GET', 'POST'])
@login_required
@admin_role_required
@ -751,7 +924,7 @@ def create_template_from_zone():
return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500)
@app.route('/template/<string:template>/edit', methods=['GET'])
@app.route('/template/<path:template>/edit', methods=['GET'])
@login_required
@admin_role_required
def edit_template(template):
@ -771,7 +944,7 @@ def edit_template(template):
return redirect(url_for('templates'))
@app.route('/template/<string:template>/apply', methods=['POST'], strict_slashes=False)
@app.route('/template/<path:template>/apply', methods=['POST'], strict_slashes=False)
@login_required
def apply_records(template):
try:
@ -801,7 +974,7 @@ def apply_records(template):
return make_response(jsonify({'status': 'error', 'msg': 'Error when applying new changes'}), 500)
@app.route('/template/<string:template>/delete', methods=['GET'])
@app.route('/template/<path:template>/delete', methods=['GET'])
@login_required
@admin_role_required
def delete_template(template):
@ -976,8 +1149,11 @@ def admin_settings_edit(setting):
@app.route('/user/profile', methods=['GET', 'POST'])
@login_required
def user_profile():
if request.method == 'GET':
return render_template('user_profile.html')
external_account = False
if session.has_key('external_auth'):
external_account = session['external_auth']
if request.method == 'GET' or external_account:
return render_template('user_profile.html', external_account=external_account)
if request.method == 'POST':
# get new profile info
firstname = request.form['firstname'] if 'firstname' in request.form else ''
@ -1011,7 +1187,7 @@ def user_profile():
user = User(username=current_user.username, plain_text_password=new_password, firstname=firstname, lastname=lastname, email=email, avatar=save_file_name, reload_info=False)
user.update_profile()
return render_template('user_profile.html')
return render_template('user_profile.html', external_account=external_account)
@app.route('/user/avatar/<path:filename>')
@ -1065,12 +1241,12 @@ def dyndns_update():
domain = None
domain_segments = hostname.split('.')
for index in range(len(domain_segments)):
domain_segments.pop(0)
full_domain = '.'.join(domain_segments)
potential_domain = Domain.query.filter(Domain.name == full_domain).first()
if potential_domain in domains:
domain = potential_domain
break
domain_segments.pop(0)
if not domain:
history = History(msg="DynDNS update: attempted update of {0} but it does not exist for this user".format(hostname), created_by=current_user.username)