mirror of
https://github.com/cwinfo/powerdns-admin.git
synced 2025-06-14 20:16:05 +00:00
Resolve the conflicts for #228
This commit is contained in:
@ -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
48
app/lib/certutil.py
Normal 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))
|
116
app/lib/utils.py
116
app/lib/utils.py
@ -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
|
102
app/models.py
102
app/models.py
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -83,7 +83,7 @@
|
||||
<small>{{ current_user.role.name }}</small>
|
||||
</p>
|
||||
</li>
|
||||
|
||||
|
||||
<!-- Menu Footer-->
|
||||
<li class="user-footer">
|
||||
<div class="pull-left">
|
||||
|
@ -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">×</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 %}
|
||||
|
@ -4,9 +4,9 @@
|
||||
|
||||
{% macro dnssec(domain) %}
|
||||
{% if domain.dnssec %}
|
||||
<td><span class="label label-success"><i class="fa fa-lock-alt"></i> Enabled</span></td>
|
||||
<td><span style="cursor:pointer" class="label label-success button_dnssec" id="{{ domain.name }}"><i class="fa fa-lock"></i> Enabled</span></td>
|
||||
{% else %}
|
||||
<td><span class="label label-primary"><i class="fa fa-unlock-alt"></i> Disabled</span></td>
|
||||
<td><span style="cursor:pointer" class="label label-primary button_dnssec" id="{{ domain.name }}"><i class="fa fa-unlock-alt"></i> Disabled</span></td>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
@ -82,7 +82,7 @@
|
||||
<button type="button" class="btn btn-flat btn-warning"> <i class="fa fa-exclamation-circle"></i> </button>
|
||||
</td>
|
||||
<td width="6%">
|
||||
<button type="button" class="btn btn-flat btn-warning"> <i class="fa fa-exclamation-circle"></i> </button>
|
||||
<button type="button" class="btn btn-flat btn-warning""> <i class="fa fa-exclamation-circle"></i> </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');
|
||||
|
@ -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> 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">
|
||||
|
45
app/templates/errors/SAML.html
Normal file
45
app/templates/errors/SAML.html
Normal 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 %}
|
@ -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>
|
||||
|
@ -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> 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>
|
||||
|
262
app/views.py
262
app/views.py
@ -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)
|
||||
|
Reference in New Issue
Block a user