Add Api to PowerDNS-Admin

This commit is contained in:
Pavol Ipoth
2019-03-01 23:49:31 +01:00
parent 343190b684
commit 1feb77e2f3
49 changed files with 5001 additions and 226 deletions

View File

@ -43,4 +43,9 @@ if app.config.get('SAML_ENABLED') and app.config.get('SAML_ENCRYPT'):
certutil.create_self_signed_cert()
from app import models
from app.blueprints.api import api_blueprint
app.register_blueprint(api_blueprint, url_prefix='/api/v1')
from app import views

511
app/blueprints/api.py Normal file
View File

@ -0,0 +1,511 @@
import json
from flask import Blueprint, g, request, abort
from app.models import Domain, History, Setting, ApiKey
from app.lib import utils, helper
from app.decorators import api_basic_auth, api_can_create_domain, is_json
from app.decorators import apikey_auth, apikey_is_admin
from app.decorators import apikey_can_access_domain
from app import csrf
from app.errors import DomainNotExists, DomainAccessForbidden, RequestIsNotJSON
from app.errors import ApiKeyCreateFail, ApiKeyNotUsable, NotEnoughPrivileges
from app.schema import ApiKeySchema, DomainSchema, ApiPlainKeySchema
from urllib.parse import urljoin
from app.lib.log import logging
api_blueprint = Blueprint('api_blueprint', __name__)
apikey_schema = ApiKeySchema(many=True)
domain_schema = DomainSchema(many=True)
apikey_plain_schema = ApiPlainKeySchema(many=True)
@api_blueprint.errorhandler(400)
def handle_400(err):
return json.dumps({"msg": "Bad Request"}), 400
@api_blueprint.errorhandler(401)
def handle_401(err):
return json.dumps({"msg": "Unauthorized"}), 401
@api_blueprint.errorhandler(500)
def handle_500(err):
return json.dumps({"msg": "Internal Server Error"}), 500
@api_blueprint.errorhandler(DomainNotExists)
def handle_domain_not_exists(err):
return json.dumps(err.to_dict()), err.status_code
@api_blueprint.errorhandler(DomainAccessForbidden)
def handle_domain_access_forbidden(err):
return json.dumps(err.to_dict()), err.status_code
@api_blueprint.errorhandler(ApiKeyCreateFail)
def handle_apikey_create_fail(err):
return json.dumps(err.to_dict()), err.status_code
@api_blueprint.errorhandler(ApiKeyNotUsable)
def handle_apikey_not_usable(err):
return json.dumps(err.to_dict()), err.status_code
@api_blueprint.errorhandler(NotEnoughPrivileges)
def handle_not_enough_privileges(err):
return json.dumps(err.to_dict()), err.status_code
@api_blueprint.errorhandler(RequestIsNotJSON)
def handle_request_is_not_json(err):
return json.dumps(err.to_dict()), err.status_code
@api_blueprint.before_request
@is_json
def before_request():
pass
@csrf.exempt
@api_blueprint.route('/pdnsadmin/zones', methods=['POST'])
@api_basic_auth
@api_can_create_domain
def api_login_create_zone():
pdns_api_url = Setting().get('pdns_api_url')
pdns_api_key = Setting().get('pdns_api_key')
pdns_version = Setting().get('pdns_version')
api_uri_with_prefix = utils.pdns_api_extended_uri(pdns_version)
api_full_uri = api_uri_with_prefix + '/servers/localhost/zones'
headers = {}
headers['X-API-Key'] = pdns_api_key
msg_str = "Sending request to powerdns API {0}"
msg = msg_str.format(request.get_json(force=True))
logging.debug(msg)
resp = utils.fetch_remote(
urljoin(pdns_api_url, api_full_uri),
method='POST',
data=request.get_json(force=True),
headers=headers,
accept='application/json; q=1'
)
if resp.status_code == 201:
logging.debug("Request to powerdns API successful")
data = request.get_json(force=True)
history = History(
msg='Add domain {0}'.format(data['name'].rstrip('.')),
detail=json.dumps(data),
created_by=g.user.username
)
history.add()
if g.user.role.name not in ['Administrator', 'Operator']:
logging.debug("User is ordinary user, assigning created domain")
domain = Domain(name=data['name'].rstrip('.'))
domain.update()
domain.grant_privileges([g.user.username])
domain = Domain()
domain.update()
return resp.content, resp.status_code, resp.headers.items()
@csrf.exempt
@api_blueprint.route('/pdnsadmin/zones', methods=['GET'])
@api_basic_auth
def api_login_list_zones():
if g.user.role.name not in ['Administrator', 'Operator']:
domain_obj_list = g.user.get_domains()
else:
domain_obj_list = Domain.query.all()
domain_obj_list = [] if domain_obj_list is None else domain_obj_list
return json.dumps(domain_schema.dump(domain_obj_list)), 200
@csrf.exempt
@api_blueprint.route(
'/pdnsadmin/zones/<string:domain_name>',
methods=['DELETE']
)
@api_basic_auth
@api_can_create_domain
def api_login_delete_zone(domain_name):
pdns_api_url = Setting().get('pdns_api_url')
pdns_api_key = Setting().get('pdns_api_key')
pdns_version = Setting().get('pdns_version')
api_uri_with_prefix = utils.pdns_api_extended_uri(pdns_version)
api_full_uri = api_uri_with_prefix + '/servers/localhost/zones'
api_full_uri += '/' + domain_name
headers = {}
headers['X-API-Key'] = pdns_api_key
domain = Domain.query.filter(Domain.name == domain_name)
if not domain:
abort(404)
if g.user.role.name not in ['Administrator', 'Operator']:
user_domains_obj_list = g.user.get_domains()
user_domains_list = [item.name for item in user_domains_obj_list]
if domain_name not in user_domains_list:
raise DomainAccessForbidden()
msg_str = "Sending request to powerdns API {0}"
logging.debug(msg_str.format(domain_name))
try:
resp = utils.fetch_remote(
urljoin(pdns_api_url, api_full_uri),
method='DELETE',
headers=headers,
accept='application/json; q=1'
)
if resp.status_code == 204:
logging.debug("Request to powerdns API successful")
history = History(
msg='Delete domain {0}'.format(domain_name),
detail='',
created_by=g.user.username
)
history.add()
domain = Domain()
domain.update()
except Exception as e:
logging.error('Error: {0}'.format(e))
abort(500)
return resp.content, resp.status_code, resp.headers.items()
@csrf.exempt
@api_blueprint.route('/pdnsadmin/apikeys', methods=['POST'])
@api_basic_auth
def api_generate_apikey():
data = request.get_json()
description = None
role_name = None
apikey = None
domain_obj_list = []
abort(400) if 'domains' not in data else None
abort(400) if not isinstance(data['domains'], (list,)) else None
abort(400) if 'role' not in data else None
description = data['description'] if 'description' in data else None
role_name = data['role']
domains = data['domains']
if role_name == 'User' and len(domains) == 0:
logging.error("Apikey with User role must have domains")
raise ApiKeyNotUsable()
elif role_name == 'User':
domain_obj_list = Domain.query.filter(Domain.name.in_(domains)).all()
if len(domain_obj_list) == 0:
msg = "One of supplied domains does not exists"
logging.error(msg)
raise DomainNotExists(message=msg)
if g.user.role.name not in ['Administrator', 'Operator']:
# domain list of domain api key should be valid for
# if not any domain error
# role of api key, user cannot assign role above for api key
if role_name != 'User':
msg = "User cannot assign other role than User"
logging.error(msg)
raise NotEnoughPrivileges(message=msg)
user_domain_obj_list = g.user.get_domains()
domain_list = [item.name for item in domain_obj_list]
user_domain_list = [item.name for item in user_domain_obj_list]
logging.debug("Input domain list: {0}".format(domain_list))
logging.debug("User domain list: {0}".format(user_domain_list))
inter = set(domain_list).intersection(set(user_domain_list))
if not (len(inter) == len(domain_list)):
msg = "You don't have access to one of domains"
logging.error(msg)
raise DomainAccessForbidden(message=msg)
apikey = ApiKey(
desc=description,
role_name=role_name,
domains=domain_obj_list
)
try:
apikey.create()
except Exception as e:
logging.error('Error: {0}'.format(e))
raise ApiKeyCreateFail(message='Api key create failed')
return json.dumps(apikey_plain_schema.dump([apikey])), 201
@csrf.exempt
@api_blueprint.route('/pdnsadmin/apikeys', defaults={'domain_name': None})
@api_blueprint.route('/pdnsadmin/apikeys/<string:domain_name>')
@api_basic_auth
def api_get_apikeys(domain_name):
apikeys = []
logging.debug("Getting apikeys")
if g.user.role.name not in ['Administrator', 'Operator']:
if domain_name:
msg = "Check if domain {0} exists and \
is allowed for user." . format(domain_name)
logging.debug(msg)
apikeys = g.user.get_apikeys(domain_name)
if not apikeys:
raise DomainAccessForbidden(name=domain_name)
logging.debug(apikey_schema.dump(apikeys))
else:
msg_str = "Getting all allowed domains for user {0}"
msg = msg_str . format(g.user.username)
logging.debug(msg)
try:
apikeys = g.user.get_apikeys()
logging.debug(apikey_schema.dump(apikeys))
except Exception as e:
logging.error('Error: {0}'.format(e))
abort(500)
else:
logging.debug("Getting all domains for administrative user")
try:
apikeys = ApiKey.query.all()
logging.debug(apikey_schema.dump(apikeys))
except Exception as e:
logging.error('Error: {0}'.format(e))
abort(500)
return json.dumps(apikey_schema.dump(apikeys)), 200
@csrf.exempt
@api_blueprint.route('/pdnsadmin/apikeys/<int:apikey_id>', methods=['DELETE'])
@api_basic_auth
def api_delete_apikey(apikey_id):
apikey = ApiKey.query.get(apikey_id)
if not apikey:
abort(404)
logging.debug(g.user.role.name)
if g.user.role.name not in ['Administrator', 'Operator']:
apikeys = g.user.get_apikeys()
user_domains_obj_list = g.user.get_domain().all()
apikey_domains_obj_list = apikey.domains
user_domains_list = [item.name for item in user_domains_obj_list]
apikey_domains_list = [item.name for item in apikey_domains_obj_list]
apikeys_ids = [apikey_item.id for apikey_item in apikeys]
inter = set(apikey_domains_list).intersection(set(user_domains_list))
if not (len(inter) == len(apikey_domains_list)):
msg = "You don't have access to some domains apikey belongs to"
logging.error(msg)
raise DomainAccessForbidden(message=msg)
if apikey_id not in apikeys_ids:
raise DomainAccessForbidden()
try:
apikey.delete()
except Exception as e:
logging.error('Error: {0}'.format(e))
abort(500)
return '', 204
@csrf.exempt
@api_blueprint.route('/pdnsadmin/apikeys/<int:apikey_id>', methods=['PUT'])
@api_basic_auth
def api_update_apikey(apikey_id):
# if role different and user is allowed to change it, update
# if apikey domains are different and user is allowed to handle
# that domains update domains
data = request.get_json()
description = data['description'] if 'description' in data else None
role_name = data['role'] if 'role' in data else None
domains = data['domains'] if 'domains' in data else None
domain_obj_list = None
apikey = ApiKey.query.get(apikey_id)
if not apikey:
abort(404)
logging.debug('Updating apikey with id {0}'.format(apikey_id))
if role_name == 'User' and len(domains) == 0:
logging.error("Apikey with User role must have domains")
raise ApiKeyNotUsable()
elif role_name == 'User':
domain_obj_list = Domain.query.filter(Domain.name.in_(domains)).all()
if len(domain_obj_list) == 0:
msg = "One of supplied domains does not exists"
logging.error(msg)
raise DomainNotExists(message=msg)
if g.user.role.name not in ['Administrator', 'Operator']:
if role_name != 'User':
msg = "User cannot assign other role than User"
logging.error(msg)
raise NotEnoughPrivileges(message=msg)
apikeys = g.user.get_apikeys()
apikey_domains = [item.name for item in apikey.domains]
apikeys_ids = [apikey_item.id for apikey_item in apikeys]
user_domain_obj_list = g.user.get_domain().all()
domain_list = [item.name for item in domain_obj_list]
user_domain_list = [item.name for item in user_domain_obj_list]
logging.debug("Input domain list: {0}".format(domain_list))
logging.debug("User domain list: {0}".format(user_domain_list))
inter = set(domain_list).intersection(set(user_domain_list))
if not (len(inter) == len(domain_list)):
msg = "You don't have access to one of domains"
logging.error(msg)
raise DomainAccessForbidden(message=msg)
if apikey_id not in apikeys_ids:
msg = 'Apikey does not belong to domain to which user has access'
logging.error(msg)
raise DomainAccessForbidden()
if set(domains) == set(apikey_domains):
logging.debug("Domains are same, apikey domains won't be updated")
domains = None
if role_name == apikey.role:
logging.debug("Role is same, apikey role won't be updated")
role_name = None
if description == apikey.description:
msg = "Description is same, apikey description won't be updated"
logging.debug(msg)
description = None
try:
apikey = ApiKey.query.get(apikey_id)
apikey.update(
role_name=role_name,
domains=domains,
description=description
)
except Exception as e:
logging.error('Error: {0}'.format(e))
abort(500)
return '', 204
@csrf.exempt
@api_blueprint.route(
'/servers/<string:server_id>/zones/<string:zone_id>/<path:subpath>',
methods=['GET', 'POST', 'PUT', 'PATCH', 'DELETE']
)
@apikey_auth
@apikey_can_access_domain
def api_zone_subpath_forward(server_id, zone_id, subpath):
resp = helper.forward_request()
return resp.content, resp.status_code, resp.headers.items()
@csrf.exempt
@api_blueprint.route(
'/servers/<string:server_id>/zones/<string:zone_id>',
methods=['GET', 'PUT', 'PATCH', 'DELETE']
)
@apikey_auth
@apikey_can_access_domain
def api_zone_forward(server_id, zone_id):
resp = helper.forward_request()
domain = Domain()
domain.update()
return resp.content, resp.status_code, resp.headers.items()
@api_blueprint.route(
'/servers',
methods=['GET']
)
@apikey_auth
@apikey_is_admin
def api_server_forward():
resp = helper.forward_request()
return resp.content, resp.status_code, resp.headers.items()
@api_blueprint.route(
'/servers/<path:subpath>',
methods=['GET', 'PUT']
)
@apikey_auth
@apikey_is_admin
def api_server_sub_forward(subpath):
resp = helper.forward_request()
return resp.content, resp.status_code, resp.headers.items()
@csrf.exempt
@api_blueprint.route('/servers/<string:server_id>/zones', methods=['POST'])
@apikey_auth
def api_create_zone(server_id):
resp = helper.forward_request()
if resp.status_code == 201:
logging.debug("Request to powerdns API successful")
data = request.get_json(force=True)
history = History(
msg='Add domain {0}'.format(data['name'].rstrip('.')),
detail=json.dumps(data),
created_by=g.apikey.description
)
history.add()
if g.apikey.role.name not in ['Administrator', 'Operator']:
logging.debug("Apikey is user key, assigning created domain")
domain = Domain(name=data['name'].rstrip('.'))
g.apikey.domains.append(domain)
domain = Domain()
domain.update()
return resp.content, resp.status_code, resp.headers.items()
@csrf.exempt
@api_blueprint.route('/servers/<string:server_id>/zones', methods=['GET'])
@apikey_auth
def api_get_zones(server_id):
if g.apikey.role.name not in ['Administrator', 'Operator']:
domain_obj_list = g.apikey.domains
else:
domain_obj_list = Domain.query.all()
return json.dumps(domain_schema.dump(domain_obj_list)), 200

View File

@ -1,7 +1,12 @@
from functools import wraps
from flask import g, redirect, url_for
from flask import g, redirect, url_for, request, abort
from app.models import Setting
from .models import User, ApiKey
import base64
from app.lib.log import logging
from app.errors import RequestIsNotJSON, NotEnoughPrivileges
from app.errors import DomainAccessForbidden
def admin_role_required(f):
@ -73,6 +78,140 @@ def can_create_domain(f):
def decorated_function(*args, **kwargs):
if g.user.role.name not in ['Administrator', 'Operator'] and not Setting().get('allow_user_create_domain'):
return redirect(url_for('error', code=401))
return f(*args, **kwargs)
return decorated_function
def api_basic_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
auth_header = request.headers.get('Authorization')
if auth_header:
auth_header = auth_header.replace('Basic ', '', 1)
try:
auth_header = str(base64.b64decode(auth_header), 'utf-8')
username, password = auth_header.split(":")
except TypeError as e:
logging.error('Error: {0}'.format(e))
abort(401)
user = User(
username=username,
password=password,
plain_text_password=password
)
try:
auth_method = request.args.get('auth_method', 'LOCAL')
auth_method = 'LDAP' if auth_method != 'LOCAL' else 'LOCAL'
auth = user.is_validate(
method=auth_method,
src_ip=request.remote_addr
)
if not auth:
logging.error('Checking user password failed')
abort(401)
else:
user = User.query.filter(User.username == username).first()
g.user = user
except Exception as e:
logging.error('Error: {0}'.format(e))
abort(401)
else:
logging.error('Error: Authorization header missing!')
abort(401)
return f(*args, **kwargs)
return decorated_function
def is_json(f):
@wraps(f)
def decorated_function(*args, **kwargs):
if request.method in ['POST', 'PUT', 'PATCH']:
if not request.is_json:
raise RequestIsNotJSON()
return f(*args, **kwargs)
return decorated_function
def api_can_create_domain(f):
"""
Grant access if:
- user is in Operator role or higher, or
- allow_user_create_domain is on
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.user.role.name not in ['Administrator', 'Operator'] and not Setting().get('allow_user_create_domain'):
msg = "User {0} does not have enough privileges to create domain"
logging.error(msg.format(g.user.username))
raise NotEnoughPrivileges()
return f(*args, **kwargs)
return decorated_function
def apikey_is_admin(f):
"""
Grant access if user is in Administrator role
"""
@wraps(f)
def decorated_function(*args, **kwargs):
if g.apikey.role.name != 'Administrator':
msg = "Apikey {0} does not have enough privileges to create domain"
logging.error(msg.format(g.apikey.id))
raise NotEnoughPrivileges()
return f(*args, **kwargs)
return decorated_function
def apikey_can_access_domain(f):
@wraps(f)
def decorated_function(*args, **kwargs):
apikey = g.apikey
if g.apikey.role.name not in ['Administrator', 'Operator']:
domains = apikey.domains
zone_id = kwargs.get('zone_id')
domain_names = [item.name for item in domains]
if zone_id not in domain_names:
raise DomainAccessForbidden()
return f(*args, **kwargs)
return decorated_function
def apikey_auth(f):
@wraps(f)
def decorated_function(*args, **kwargs):
auth_header = request.headers.get('X-API-KEY')
if auth_header:
try:
apikey_val = str(base64.b64decode(auth_header), 'utf-8')
except TypeError as e:
logging.error('Error: {0}'.format(e))
abort(401)
apikey = ApiKey(
key=apikey_val
)
apikey.plain_text_password = apikey_val
try:
auth_method = 'LOCAL'
auth = apikey.is_validate(
method=auth_method,
src_ip=request.remote_addr
)
g.apikey = auth
except Exception as e:
logging.error('Error: {0}'.format(e))
abort(401)
else:
logging.error('Error: API key header missing!')
abort(401)
return f(*args, **kwargs)
return decorated_function

73
app/errors.py Normal file
View File

@ -0,0 +1,73 @@
class StructuredException(Exception):
status_code = 0
def __init__(self, name=None, message="You want override this error!"):
Exception.__init__(self)
self.message = message
self.name = name
def to_dict(self):
rv = dict()
msg = ''
if self.name:
msg = '{0} {1}'.format(self.message, self.name)
else:
msg = self.message
rv['msg'] = msg
return rv
class DomainNotExists(StructuredException):
status_code = 1000
def __init__(self, name=None, message="Domain does not exist"):
StructuredException.__init__(self)
self.message = message
self.name = name
class DomainAccessForbidden(StructuredException):
status_code = 1001
def __init__(self, name=None, message="Domain access not allowed"):
StructuredException.__init__(self)
self.message = message
self.name = name
class ApiKeyCreateFail(StructuredException):
status_code = 1002
def __init__(self, name=None, message="Creation of api key failed"):
StructuredException.__init__(self)
self.message = message
self.name = name
class ApiKeyNotUsable(StructuredException):
status_code = 1003
def __init__(self, name=None, message="Api key must have domains or have \
administrative role"):
StructuredException.__init__(self)
self.message = message
self.name = name
class NotEnoughPrivileges(StructuredException):
status_code = 1004
def __init__(self, name=None, message="Not enough privileges"):
StructuredException.__init__(self)
self.message = message
self.name = name
class RequestIsNotJSON(StructuredException):
status_code = 1005
def __init__(self, name=None, message="Request is not json"):
StructuredException.__init__(self)
self.message = message
self.name = name

43
app/lib/helper.py Normal file
View File

@ -0,0 +1,43 @@
from app.models import Setting
import requests
from flask import request
import logging as logger
from urllib.parse import urljoin
logging = logger.getLogger(__name__)
def forward_request():
pdns_api_url = Setting().get('pdns_api_url')
pdns_api_key = Setting().get('pdns_api_key')
headers = {}
data = None
msg_str = "Sending request to powerdns API {0}"
if request.method != 'GET' and request.method != 'DELETE':
msg = msg_str.format(request.get_json(force=True))
logging.debug(msg)
data = request.get_json(force=True)
verify = False
headers = {
'user-agent': 'powerdnsadmin/0',
'pragma': 'no-cache',
'cache-control': 'no-cache',
'accept': 'application/json; q=1',
'X-API-KEY': pdns_api_key
}
url = urljoin(pdns_api_url, request.path)
resp = requests.request(
request.method,
url,
headers=headers,
verify=verify,
json=data
)
return resp

View File

@ -43,4 +43,4 @@ class logger(object):
console_formatter = logging.Formatter('[%(levelname)s] %(message)s')
stderr_log_handler.setFormatter(console_formatter)
return logging.getLogger(self.name)
return logging.getLogger(self.name)

View File

@ -11,6 +11,10 @@ from datetime import datetime, timedelta
from threading import Thread
from .certutil import KEY_FILE, CERT_FILE
import logging as logger
logging = logger.getLogger(__name__)
if app.config['SAML_ENABLED']:
from onelogin.saml2.auth import OneLogin_Saml2_Auth
@ -95,10 +99,12 @@ def fetch_remote(remote_url, method='GET', data=None, accept=None, params=None,
params=params
)
try:
if r.status_code not in (200, 400, 422):
if r.status_code not in (200, 201, 204, 400, 422):
r.raise_for_status()
except Exception as e:
raise RuntimeError('Error while fetching {0}'.format(remote_url)) from e
msg = "Returned status {0} and content {1}"
logging.error(msg.format(r.status_code, r.content))
raise RuntimeError('Error while fetching {0}'.format(remote_url))
return r

File diff suppressed because it is too large Load Diff

27
app/schema.py Normal file
View File

@ -0,0 +1,27 @@
from lima import fields, Schema
class DomainSchema(Schema):
id = fields.Integer()
name = fields.String()
class RoleSchema(Schema):
id = fields.Integer()
name = fields.String()
class ApiKeySchema(Schema):
id = fields.Integer()
role = fields.Embed(schema=RoleSchema)
domains = fields.Embed(schema=DomainSchema, many=True)
description = fields.String()
key = fields.String()
class ApiPlainKeySchema(Schema):
id = fields.Integer()
role = fields.Embed(schema=RoleSchema)
domains = fields.Embed(schema=DomainSchema, many=True)
description = fields.String()
plain_key = fields.String()

1440
app/swagger-spec.yaml Normal file

File diff suppressed because it is too large Load Diff

32
app/validators.py Normal file
View File

@ -0,0 +1,32 @@
import os
from bravado_core.spec import Spec
from bravado_core.validate import validate_object
from yaml import load, Loader
def validate_zone(zone):
validate_object(spec, zone_spec, zone)
def validate_apikey(apikey):
validate_object(spec, apikey_spec, apikey)
def get_swagger_spec(spec_path):
with open(spec_path, 'r') as spec:
return load(spec.read(), Loader)
bravado_config = {
'validate_swagger_spec': False,
'validate_requests': False,
'validate_responses': False,
'use_models': True,
}
dir_path = os.path.dirname(os.path.abspath(__file__))
spec_path = os.path.join(dir_path, "swagger-spec.yaml")
spec_dict = get_swagger_spec(spec_path)
spec = Spec.from_dict(spec_dict, config=bravado_config)
zone_spec = spec_dict['definitions']['Zone']
apikey_spec = spec_dict['definitions']['ApiKey']

View File

@ -23,6 +23,7 @@ from app import app, login_manager, csrf
from app.lib import utils
from app.oauth import github_oauth, google_oauth, oidc_oauth
from app.decorators import admin_role_required, operator_role_required, can_access_domain, can_configure_dnssec, can_create_domain
from yaml import Loader, load
if app.config['SAML_ENABLED']:
from onelogin.saml2.utils import OneLogin_Saml2_Utils
@ -107,7 +108,9 @@ def login_via_authorization_header(request):
return None
user = User(username=username, password=password, plain_text_password=password)
try:
auth = user.is_validate(method='LOCAL', src_ip=request.remote_addr)
auth_method = request.args.get('auth_method', 'LOCAL')
auth_method = 'LDAP' if auth_method != 'LOCAL' else 'LOCAL'
auth = user.is_validate(method=auth_method, src_ip=request.remote_addr)
if auth == False:
return None
else:
@ -130,11 +133,13 @@ def http_bad_request(e):
def http_unauthorized(e):
return redirect(url_for('error', code=401))
@app.errorhandler(404)
def http_internal_server_error(e):
return redirect(url_for('error', code=404))
@app.errorhandler(405)
def _handle_api_error(ex):
if request.path.startswith('/api/'):
return json.dumps({"msg": "NotFound"}), 404
else:
return redirect(url_for('error', code=404))
@app.errorhandler(500)
def http_page_not_found(e):
@ -149,6 +154,22 @@ def error(code, msg=None):
else:
return render_template('errors/404.html'), 404
@app.route('/swagger', methods=['GET'])
def swagger_spec():
try:
dir_path = os.path.dirname(os.path.abspath(__file__))
spec_path = os.path.join(dir_path, "swagger-spec.yaml")
spec = open(spec_path,'r')
loaded_spec = load(spec.read(), Loader)
except Exception as e:
logging.error('Cannot view swagger spec. Error: {0}'.format(e))
logging.debug(traceback.format_exc())
return redirect(url_for('error', code=500))
resp = make_response(json.dumps(loaded_spec), 200)
resp.headers['Content-Type'] = 'application/json'
return resp
@app.route('/register', methods=['GET', 'POST'])
def register():
@ -1817,7 +1838,6 @@ def dyndns_update():
return render_template('dyndns.html', response=response), 200
@app.route('/', methods=['GET', 'POST'])
@login_required
def index():