2018-09-04 01:57:41 +00:00
|
|
|
import sys
|
2016-06-16 08:33:05 +00:00
|
|
|
import os
|
2018-09-04 01:57:41 +00:00
|
|
|
import re
|
2015-12-13 09:34:12 +00:00
|
|
|
import ldap
|
2017-09-03 18:23:18 +00:00
|
|
|
import ldap.filter
|
2016-06-16 08:33:05 +00:00
|
|
|
import base64
|
2015-12-13 09:34:12 +00:00
|
|
|
import bcrypt
|
2016-02-11 09:54:15 +00:00
|
|
|
import itertools
|
2015-12-13 09:34:12 +00:00
|
|
|
import traceback
|
2016-09-17 13:37:20 +00:00
|
|
|
import pyotp
|
2016-11-16 14:13:02 +00:00
|
|
|
import dns.reversename
|
2018-08-21 06:26:27 +00:00
|
|
|
import dns.inet
|
|
|
|
import dns.name
|
2018-11-24 12:45:14 +00:00
|
|
|
import pytimeparse
|
2019-03-01 22:49:31 +00:00
|
|
|
import random
|
|
|
|
import string
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2018-08-22 01:36:53 +00:00
|
|
|
from ast import literal_eval
|
2015-12-13 09:34:12 +00:00
|
|
|
from datetime import datetime
|
2018-03-30 06:49:35 +00:00
|
|
|
from urllib.parse import urljoin
|
2016-11-21 12:44:47 +00:00
|
|
|
from distutils.util import strtobool
|
2016-06-07 08:20:56 +00:00
|
|
|
from distutils.version import StrictVersion
|
2016-07-01 19:41:41 +00:00
|
|
|
from flask_login import AnonymousUserMixin
|
2019-03-01 22:49:31 +00:00
|
|
|
from app import db, app
|
2018-03-30 06:49:35 +00:00
|
|
|
from app.lib import utils
|
2019-03-01 22:49:31 +00:00
|
|
|
from app.lib.log import logging
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Anonymous(AnonymousUserMixin):
|
2019-03-01 22:49:31 +00:00
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
self.username = 'Anonymous'
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
class User(db.Model):
|
|
|
|
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
username = db.Column(db.String(64), index=True, unique=True)
|
|
|
|
password = db.Column(db.String(64))
|
|
|
|
firstname = db.Column(db.String(64))
|
|
|
|
lastname = db.Column(db.String(64))
|
|
|
|
email = db.Column(db.String(128))
|
2015-12-16 17:50:28 +00:00
|
|
|
avatar = db.Column(db.String(128))
|
2016-06-16 08:33:05 +00:00
|
|
|
otp_secret = db.Column(db.String(16))
|
2015-12-13 09:34:12 +00:00
|
|
|
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
|
|
|
|
|
2016-06-16 08:33:05 +00:00
|
|
|
def __init__(self, id=None, username=None, password=None, plain_text_password=None, firstname=None, lastname=None, role_id=None, email=None, avatar=None, otp_secret=None, reload_info=True):
|
2015-12-13 09:34:12 +00:00
|
|
|
self.id = id
|
|
|
|
self.username = username
|
|
|
|
self.password = password
|
|
|
|
self.plain_text_password = plain_text_password
|
|
|
|
self.firstname = firstname
|
|
|
|
self.lastname = lastname
|
|
|
|
self.role_id = role_id
|
|
|
|
self.email = email
|
2015-12-16 17:50:28 +00:00
|
|
|
self.avatar = avatar
|
2016-06-16 08:33:05 +00:00
|
|
|
self.otp_secret = otp_secret
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2015-12-16 07:21:30 +00:00
|
|
|
if reload_info:
|
|
|
|
user_info = self.get_user_info_by_id() if id else self.get_user_info_by_username()
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2015-12-16 07:21:30 +00:00
|
|
|
if user_info:
|
|
|
|
self.id = user_info.id
|
|
|
|
self.username = user_info.username
|
|
|
|
self.firstname = user_info.firstname
|
|
|
|
self.lastname = user_info.lastname
|
|
|
|
self.email = user_info.email
|
|
|
|
self.role_id = user_info.role_id
|
2016-06-16 08:33:05 +00:00
|
|
|
self.otp_secret = user_info.otp_secret
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def is_authenticated(self):
|
|
|
|
return True
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
def is_active(self):
|
|
|
|
return True
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
def is_anonymous(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def get_id(self):
|
|
|
|
try:
|
|
|
|
return unicode(self.id) # python 2
|
|
|
|
except NameError:
|
|
|
|
return str(self.id) # python 3
|
|
|
|
|
|
|
|
def __repr__(self):
|
2018-04-01 00:57:41 +00:00
|
|
|
return '<User {0}>'.format(self.username)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2016-06-16 08:33:05 +00:00
|
|
|
def get_totp_uri(self):
|
2018-04-01 00:57:41 +00:00
|
|
|
return "otpauth://totp/PowerDNS-Admin:{0}?secret={1}&issuer=PowerDNS-Admin".format(self.username, self.otp_secret)
|
2016-06-16 08:33:05 +00:00
|
|
|
|
|
|
|
def verify_totp(self, token):
|
2016-09-17 13:37:20 +00:00
|
|
|
totp = pyotp.TOTP(self.otp_secret)
|
2018-11-18 10:23:47 +00:00
|
|
|
return totp.verify(token)
|
2016-06-16 08:33:05 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
def get_hashed_password(self, plain_text_password=None):
|
|
|
|
# Hash a password for the first time
|
|
|
|
# (Using bcrypt, the salt is saved into the hash itself)
|
2018-09-06 04:35:54 +00:00
|
|
|
if plain_text_password is None:
|
2018-04-10 01:59:28 +00:00
|
|
|
return plain_text_password
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
pw = plain_text_password if plain_text_password else self.plain_text_password
|
2016-09-17 14:25:05 +00:00
|
|
|
return bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt())
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2016-08-23 02:52:35 +00:00
|
|
|
def check_password(self, hashed_password):
|
2018-10-02 07:29:32 +00:00
|
|
|
# Check hased password. Using bcrypt, the salt is saved into the hash itself
|
2017-11-06 22:36:11 +00:00
|
|
|
if (self.plain_text_password):
|
|
|
|
return bcrypt.checkpw(self.plain_text_password.encode('utf-8'), hashed_password.encode('utf-8'))
|
|
|
|
return False
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def get_user_info_by_id(self):
|
|
|
|
user_info = User.query.get(int(self.id))
|
|
|
|
return user_info
|
|
|
|
|
|
|
|
def get_user_info_by_username(self):
|
|
|
|
user_info = User.query.filter(User.username == self.username).first()
|
|
|
|
return user_info
|
|
|
|
|
2018-08-09 09:21:42 +00:00
|
|
|
def ldap_init_conn(self):
|
|
|
|
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
2018-08-18 15:42:18 +00:00
|
|
|
conn = ldap.initialize(Setting().get('ldap_uri'))
|
2018-08-09 09:21:42 +00:00
|
|
|
conn.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
|
|
|
|
conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
|
2019-03-01 22:49:31 +00:00
|
|
|
conn.set_option(ldap.OPT_X_TLS, ldap.OPT_X_TLS_DEMAND)
|
|
|
|
conn.set_option(ldap.OPT_X_TLS_DEMAND, True)
|
|
|
|
conn.set_option(ldap.OPT_DEBUG_LEVEL, 255)
|
2018-08-09 09:21:42 +00:00
|
|
|
conn.protocol_version = ldap.VERSION3
|
|
|
|
return conn
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
def ldap_search(self, searchFilter, baseDN):
|
|
|
|
searchScope = ldap.SCOPE_SUBTREE
|
|
|
|
retrieveAttributes = None
|
|
|
|
|
|
|
|
try:
|
2018-08-09 09:21:42 +00:00
|
|
|
conn = self.ldap_init_conn()
|
2018-09-12 15:28:05 +00:00
|
|
|
if Setting().get('ldap_type') == 'ad':
|
2019-03-01 22:49:31 +00:00
|
|
|
conn.simple_bind_s(
|
|
|
|
"{0}@{1}".format(self.username, Setting().get('ldap_domain')), self.password)
|
2018-09-12 15:28:05 +00:00
|
|
|
else:
|
2019-03-01 22:49:31 +00:00
|
|
|
conn.simple_bind_s(
|
|
|
|
Setting().get('ldap_admin_username'), Setting().get('ldap_admin_password'))
|
2018-08-09 09:21:42 +00:00
|
|
|
ldap_result_id = conn.search(baseDN, searchScope, searchFilter, retrieveAttributes)
|
2015-12-13 09:34:12 +00:00
|
|
|
result_set = []
|
2018-08-09 09:21:42 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
while 1:
|
2018-08-09 09:21:42 +00:00
|
|
|
result_type, result_data = conn.result(ldap_result_id, 0)
|
2015-12-13 09:34:12 +00:00
|
|
|
if (result_data == []):
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
if result_type == ldap.RES_SEARCH_ENTRY:
|
|
|
|
result_set.append(result_data)
|
|
|
|
return result_set
|
|
|
|
|
2018-03-30 06:49:35 +00:00
|
|
|
except ldap.LDAPError as e:
|
2015-12-13 09:34:12 +00:00
|
|
|
logging.error(e)
|
2018-08-18 15:42:18 +00:00
|
|
|
logging.debug('baseDN: {0}'.format(baseDN))
|
|
|
|
logging.debug(traceback.format_exc())
|
2018-08-31 04:57:06 +00:00
|
|
|
|
2018-08-09 09:21:42 +00:00
|
|
|
def ldap_auth(self, ldap_username, password):
|
|
|
|
try:
|
|
|
|
conn = self.ldap_init_conn()
|
|
|
|
conn.simple_bind_s(ldap_username, password)
|
|
|
|
return True
|
|
|
|
except ldap.LDAPError as e:
|
|
|
|
logging.error(e)
|
|
|
|
return False
|
|
|
|
|
2018-10-01 17:27:52 +00:00
|
|
|
def ad_recursive_groups(self, groupDN):
|
|
|
|
"""
|
2018-11-24 12:45:14 +00:00
|
|
|
Recursively list groups belonging to a group. It will allow checking deep in the Active Directory
|
2018-10-01 17:27:52 +00:00
|
|
|
whether a user is allowed to enter or not
|
|
|
|
"""
|
|
|
|
LDAP_BASE_DN = Setting().get('ldap_base_dn')
|
2019-03-18 00:54:31 +00:00
|
|
|
groupSearchFilter = "(&(objectcategory=group)(member=%s))" % ldap.filter.escape_filter_chars(groupDN)
|
2019-03-01 22:49:31 +00:00
|
|
|
result = [groupDN]
|
2018-10-01 17:27:52 +00:00
|
|
|
try:
|
|
|
|
groups = self.ldap_search(groupSearchFilter, LDAP_BASE_DN)
|
|
|
|
for group in groups:
|
2019-03-01 22:49:31 +00:00
|
|
|
result += [group[0][0]]
|
2018-10-01 17:27:52 +00:00
|
|
|
if 'memberOf' in group[0][1]:
|
|
|
|
for member in group[0][1]['memberOf']:
|
2019-03-01 22:49:31 +00:00
|
|
|
result += self.ad_recursive_groups(member.decode("utf-8"))
|
2018-10-01 17:27:52 +00:00
|
|
|
return result
|
2018-10-01 21:08:45 +00:00
|
|
|
except ldap.LDAPError as e:
|
2018-10-01 17:27:52 +00:00
|
|
|
logging.exception("Recursive AD Group search error")
|
|
|
|
return result
|
|
|
|
|
2018-07-05 07:25:05 +00:00
|
|
|
def is_validate(self, method, src_ip=''):
|
2015-12-13 09:34:12 +00:00
|
|
|
"""
|
|
|
|
Validate user credential
|
|
|
|
"""
|
2018-08-31 04:57:06 +00:00
|
|
|
role_name = 'User'
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
if method == 'LOCAL':
|
|
|
|
user_info = User.query.filter(User.username == self.username).first()
|
|
|
|
|
|
|
|
if user_info:
|
2015-12-25 04:23:52 +00:00
|
|
|
if user_info.password and self.check_password(user_info.password):
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.info(
|
|
|
|
'User "{0}" logged in successfully. Authentication request from {1}'.format(self.username, src_ip))
|
2015-12-13 09:34:12 +00:00
|
|
|
return True
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'User "{0}" inputted a wrong password. Authentication request from {1}'.format(self.username, src_ip))
|
2015-12-13 09:34:12 +00:00
|
|
|
return False
|
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.warning(
|
|
|
|
'User "{0}" does not exist. Authentication request from {1}'.format(self.username, src_ip))
|
2016-08-23 04:10:00 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
if method == 'LDAP':
|
2018-08-18 15:42:18 +00:00
|
|
|
LDAP_TYPE = Setting().get('ldap_type')
|
|
|
|
LDAP_BASE_DN = Setting().get('ldap_base_dn')
|
|
|
|
LDAP_FILTER_BASIC = Setting().get('ldap_filter_basic')
|
|
|
|
LDAP_FILTER_USERNAME = Setting().get('ldap_filter_username')
|
|
|
|
LDAP_ADMIN_GROUP = Setting().get('ldap_admin_group')
|
2018-08-31 04:57:06 +00:00
|
|
|
LDAP_OPERATOR_GROUP = Setting().get('ldap_operator_group')
|
2018-08-18 15:42:18 +00:00
|
|
|
LDAP_USER_GROUP = Setting().get('ldap_user_group')
|
|
|
|
LDAP_GROUP_SECURITY_ENABLED = Setting().get('ldap_sg_enabled')
|
2016-04-13 04:13:59 +00:00
|
|
|
|
2018-11-12 09:30:18 +00:00
|
|
|
# validate AD user password
|
2018-09-12 15:28:05 +00:00
|
|
|
if Setting().get('ldap_type') == 'ad':
|
2019-03-01 22:49:31 +00:00
|
|
|
ldap_username = "{0}@{1}".format(self.username, Setting().get('ldap_domain'))
|
2018-09-12 15:28:05 +00:00
|
|
|
if not self.ldap_auth(ldap_username, self.password):
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'User "{0}" input a wrong LDAP password. Authentication request from {1}'.format(self.username, src_ip))
|
2018-09-12 15:28:05 +00:00
|
|
|
return False
|
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
searchFilter = "(&({0}={1}){2})".format(
|
|
|
|
LDAP_FILTER_USERNAME, self.username, LDAP_FILTER_BASIC)
|
2018-08-22 09:55:44 +00:00
|
|
|
logging.debug('Ldap searchFilter {0}'.format(searchFilter))
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2018-08-18 15:42:18 +00:00
|
|
|
ldap_result = self.ldap_search(searchFilter, LDAP_BASE_DN)
|
2018-08-22 09:55:44 +00:00
|
|
|
logging.debug('Ldap search result: {0}'.format(ldap_result))
|
|
|
|
|
2018-08-09 09:21:42 +00:00
|
|
|
if not ldap_result:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.warning(
|
|
|
|
'LDAP User "{0}" does not exist. Authentication request from {1}'.format(self.username, src_ip))
|
2015-12-13 09:34:12 +00:00
|
|
|
return False
|
2018-08-09 09:21:42 +00:00
|
|
|
else:
|
|
|
|
try:
|
|
|
|
ldap_username = ldap.filter.escape_filter_chars(ldap_result[0][0][0])
|
2018-11-12 09:30:18 +00:00
|
|
|
|
|
|
|
if Setting().get('ldap_type') != 'ad':
|
|
|
|
# validate ldap user password
|
|
|
|
if not self.ldap_auth(ldap_username, self.password):
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'User "{0}" input a wrong LDAP password. Authentication request from {1}'.format(self.username, src_ip))
|
2018-11-12 09:30:18 +00:00
|
|
|
return False
|
|
|
|
|
2018-08-18 15:42:18 +00:00
|
|
|
# check if LDAP_GROUP_SECURITY_ENABLED is True
|
2018-08-09 09:21:42 +00:00
|
|
|
# user can be assigned to ADMIN or USER role.
|
2018-08-18 15:42:18 +00:00
|
|
|
if LDAP_GROUP_SECURITY_ENABLED:
|
2018-08-09 09:21:42 +00:00
|
|
|
try:
|
2018-08-22 09:55:44 +00:00
|
|
|
if LDAP_TYPE == 'ldap':
|
|
|
|
if (self.ldap_search(searchFilter, LDAP_ADMIN_GROUP)):
|
2018-08-31 04:57:06 +00:00
|
|
|
role_name = 'Administrator'
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.info(
|
|
|
|
'User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP))
|
2018-08-31 04:57:06 +00:00
|
|
|
elif (self.ldap_search(searchFilter, LDAP_OPERATOR_GROUP)):
|
|
|
|
role_name = 'Operator'
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.info('User {0} is part of the "{1}" group that allows operator access to PowerDNS-Admin'.format(
|
|
|
|
self.username, LDAP_OPERATOR_GROUP))
|
2018-08-22 09:55:44 +00:00
|
|
|
elif (self.ldap_search(searchFilter, LDAP_USER_GROUP)):
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.info(
|
|
|
|
'User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'.format(self.username, LDAP_USER_GROUP))
|
2018-08-22 09:55:44 +00:00
|
|
|
else:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error('User {0} is not part of the "{1}", "{2}" or "{3}" groups that allow access to PowerDNS-Admin'.format(
|
|
|
|
self.username, LDAP_ADMIN_GROUP, LDAP_OPERATOR_GROUP, LDAP_USER_GROUP))
|
2018-08-22 09:55:44 +00:00
|
|
|
return False
|
|
|
|
elif LDAP_TYPE == 'ad':
|
2018-10-01 17:27:52 +00:00
|
|
|
user_ldap_groups = []
|
2018-10-24 06:30:19 +00:00
|
|
|
user_ad_member_of = ldap_result[0][0][1].get('memberOf')
|
|
|
|
|
|
|
|
if not user_ad_member_of:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'User {0} does not belong to any group while LDAP_GROUP_SECURITY_ENABLED is ON'.format(self.username))
|
2018-10-24 06:30:19 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
for group in [g.decode("utf-8") for g in user_ad_member_of]:
|
2019-03-01 22:49:31 +00:00
|
|
|
user_ldap_groups += self.ad_recursive_groups(group)
|
2018-08-22 09:55:44 +00:00
|
|
|
|
|
|
|
if (LDAP_ADMIN_GROUP in user_ldap_groups):
|
2018-08-31 04:57:06 +00:00
|
|
|
role_name = 'Administrator'
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.info(
|
|
|
|
'User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'.format(self.username, LDAP_ADMIN_GROUP))
|
2018-08-31 04:57:06 +00:00
|
|
|
elif (LDAP_OPERATOR_GROUP in user_ldap_groups):
|
|
|
|
role_name = 'Operator'
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.info('User {0} is part of the "{1}" group that allows operator access to PowerDNS-Admin'.format(
|
|
|
|
self.username, LDAP_OPERATOR_GROUP))
|
2018-08-22 09:55:44 +00:00
|
|
|
elif (LDAP_USER_GROUP in user_ldap_groups):
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.info(
|
|
|
|
'User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'.format(self.username, LDAP_USER_GROUP))
|
2018-08-22 09:55:44 +00:00
|
|
|
else:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error('User {0} is not part of the "{1}", "{2}" or "{3}" groups that allow access to PowerDNS-Admin'.format(
|
|
|
|
self.username, LDAP_ADMIN_GROUP, LDAP_OPERATOR_GROUP, LDAP_USER_GROUP))
|
2018-08-22 09:55:44 +00:00
|
|
|
return False
|
2018-08-09 09:21:42 +00:00
|
|
|
else:
|
2018-08-22 09:55:44 +00:00
|
|
|
logging.error('Invalid LDAP type')
|
2018-08-09 09:21:42 +00:00
|
|
|
return False
|
|
|
|
except Exception as e:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'LDAP group lookup for user "{0}" has failed. Authentication request from {1}'.format(self.username, src_ip))
|
2018-08-09 09:21:42 +00:00
|
|
|
logging.debug(traceback.format_exc())
|
2017-09-03 18:23:18 +00:00
|
|
|
return False
|
2018-08-09 09:21:42 +00:00
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
logging.error('Wrong LDAP configuration. {0}'.format(e))
|
|
|
|
logging.debug(traceback.format_exc())
|
|
|
|
return False
|
2016-08-23 04:10:00 +00:00
|
|
|
|
|
|
|
# create user if not exist in the db
|
|
|
|
if not User.query.filter(User.username == self.username).first():
|
2017-09-03 18:23:18 +00:00
|
|
|
self.firstname = self.username
|
|
|
|
self.lastname = ''
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
2018-08-18 15:42:18 +00:00
|
|
|
# try to get user's firstname, lastname and email address from LDAP attributes
|
2018-08-22 09:55:44 +00:00
|
|
|
if LDAP_TYPE == 'ldap':
|
|
|
|
self.firstname = ldap_result[0][0][1]['givenName'][0].decode("utf-8")
|
|
|
|
self.lastname = ldap_result[0][0][1]['sn'][0].decode("utf-8")
|
|
|
|
self.email = ldap_result[0][0][1]['mail'][0].decode("utf-8")
|
|
|
|
elif LDAP_TYPE == 'ad':
|
|
|
|
self.firstname = ldap_result[0][0][1]['name'][0].decode("utf-8")
|
|
|
|
self.email = ldap_result[0][0][1]['userPrincipalName'][0].decode("utf-8")
|
2018-04-01 00:23:53 +00:00
|
|
|
except Exception as e:
|
2018-08-18 15:42:18 +00:00
|
|
|
logging.warning("Reading ldap data threw an exception {0}".format(e))
|
2018-08-09 09:21:42 +00:00
|
|
|
logging.debug(traceback.format_exc())
|
2016-08-23 04:10:00 +00:00
|
|
|
|
|
|
|
# first register user will be in Administrator role
|
|
|
|
if User.query.count() == 0:
|
|
|
|
self.role_id = Role.query.filter_by(name='Administrator').first().id
|
2018-08-31 04:57:06 +00:00
|
|
|
else:
|
|
|
|
self.role_id = Role.query.filter_by(name=role_name).first().id
|
2017-09-03 18:23:18 +00:00
|
|
|
|
2016-08-23 04:10:00 +00:00
|
|
|
self.create_user()
|
2018-04-01 00:23:53 +00:00
|
|
|
logging.info('Created user "{0}" in the DB'.format(self.username))
|
2016-08-23 04:10:00 +00:00
|
|
|
|
2018-08-31 04:57:06 +00:00
|
|
|
# user already exists in database, set their role based on group membership (if enabled)
|
2018-08-18 15:42:18 +00:00
|
|
|
if LDAP_GROUP_SECURITY_ENABLED:
|
2018-08-31 04:57:06 +00:00
|
|
|
self.set_role(role_name)
|
2018-08-25 07:53:10 +00:00
|
|
|
|
2016-08-23 04:10:00 +00:00
|
|
|
return True
|
2015-12-13 09:34:12 +00:00
|
|
|
else:
|
|
|
|
logging.error('Unsupported authentication method')
|
|
|
|
return False
|
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
def get_apikeys(self, domain_name=None):
|
|
|
|
info = []
|
|
|
|
apikey_query = db.session.query(ApiKey) \
|
|
|
|
.join(Domain.apikeys) \
|
|
|
|
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
|
|
|
.outerjoin(Account, Domain.account_id == Account.id) \
|
|
|
|
.outerjoin(AccountUser, Account.id == AccountUser.account_id) \
|
|
|
|
.filter(
|
|
|
|
db.or_(
|
|
|
|
DomainUser.user_id == User.id,
|
|
|
|
AccountUser.user_id == User.id
|
|
|
|
)
|
|
|
|
) \
|
|
|
|
.filter(User.id == self.id)
|
|
|
|
|
|
|
|
if domain_name:
|
|
|
|
info = apikey_query.filter(Domain.name == domain_name).all()
|
|
|
|
else:
|
|
|
|
info = apikey_query.all()
|
|
|
|
|
|
|
|
return info
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
def create_user(self):
|
|
|
|
"""
|
|
|
|
If user logged in successfully via LDAP in the first time
|
|
|
|
We will create a local user (in DB) in order to manage user
|
|
|
|
profile such as name, roles,...
|
|
|
|
"""
|
2018-03-01 07:26:29 +00:00
|
|
|
|
2016-09-28 08:50:37 +00:00
|
|
|
# Set an invalid password hash for non local users
|
|
|
|
self.password = '*'
|
2018-03-01 07:26:29 +00:00
|
|
|
|
2016-08-23 04:10:00 +00:00
|
|
|
db.session.add(self)
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
def create_local_user(self):
|
|
|
|
"""
|
|
|
|
Create local user witch stores username / password in the DB
|
|
|
|
"""
|
|
|
|
# check if username existed
|
|
|
|
user = User.query.filter(User.username == self.username).first()
|
|
|
|
if user:
|
2018-03-30 10:43:34 +00:00
|
|
|
return {'status': False, 'msg': 'Username is already in use'}
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
# check if email existed
|
|
|
|
user = User.query.filter(User.email == self.email).first()
|
|
|
|
if user:
|
2018-03-30 10:43:34 +00:00
|
|
|
return {'status': False, 'msg': 'Email address is already in use'}
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2016-08-23 04:10:00 +00:00
|
|
|
# first register user will be in Administrator role
|
|
|
|
self.role_id = Role.query.filter_by(name='User').first().id
|
|
|
|
if User.query.count() == 0:
|
|
|
|
self.role_id = Role.query.filter_by(name='Administrator').first().id
|
2018-03-30 06:49:35 +00:00
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
self.password = self.get_hashed_password(
|
|
|
|
self.plain_text_password) if self.plain_text_password else '*'
|
2018-04-10 01:59:28 +00:00
|
|
|
|
2018-08-25 07:53:10 +00:00
|
|
|
if self.password and self.password != '*':
|
2018-04-10 01:59:28 +00:00
|
|
|
self.password = self.password.decode("utf-8")
|
2015-12-17 15:35:04 +00:00
|
|
|
|
2016-08-23 04:10:00 +00:00
|
|
|
db.session.add(self)
|
|
|
|
db.session.commit()
|
2018-03-30 10:43:34 +00:00
|
|
|
return {'status': True, 'msg': 'Created user successfully'}
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2018-08-12 09:40:32 +00:00
|
|
|
def update_local_user(self):
|
|
|
|
"""
|
|
|
|
Update local user
|
|
|
|
"""
|
|
|
|
# Sanity check - account name
|
|
|
|
if self.username == "":
|
|
|
|
return {'status': False, 'msg': 'No user name specified'}
|
|
|
|
|
|
|
|
# read user and check that it exists
|
|
|
|
user = User.query.filter(User.username == self.username).first()
|
|
|
|
if not user:
|
|
|
|
return {'status': False, 'msg': 'User does not exist'}
|
|
|
|
|
|
|
|
# check if new email exists (only if changed)
|
|
|
|
if user.email != self.email:
|
|
|
|
checkuser = User.query.filter(User.email == self.email).first()
|
|
|
|
if checkuser:
|
|
|
|
return {'status': False, 'msg': 'New email address is already in use'}
|
|
|
|
|
|
|
|
user.firstname = self.firstname
|
|
|
|
user.lastname = self.lastname
|
|
|
|
user.email = self.email
|
|
|
|
|
|
|
|
# store new password hash (only if changed)
|
|
|
|
if self.plain_text_password != "":
|
|
|
|
user.password = self.get_hashed_password(self.plain_text_password).decode("utf-8")
|
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': True, 'msg': 'User updated successfully'}
|
|
|
|
|
2016-06-16 08:33:05 +00:00
|
|
|
def update_profile(self, enable_otp=None):
|
2015-12-16 07:21:30 +00:00
|
|
|
"""
|
|
|
|
Update user profile
|
|
|
|
"""
|
2016-08-23 04:10:00 +00:00
|
|
|
|
2015-12-16 07:21:30 +00:00
|
|
|
user = User.query.filter(User.username == self.username).first()
|
2016-08-23 04:10:00 +00:00
|
|
|
if not user:
|
|
|
|
return False
|
2016-06-16 08:33:05 +00:00
|
|
|
|
2016-08-23 04:10:00 +00:00
|
|
|
user.firstname = self.firstname if self.firstname else user.firstname
|
|
|
|
user.lastname = self.lastname if self.lastname else user.lastname
|
|
|
|
user.email = self.email if self.email else user.email
|
2019-03-01 22:49:31 +00:00
|
|
|
user.password = self.get_hashed_password(self.plain_text_password).decode(
|
|
|
|
"utf-8") if self.plain_text_password else user.password
|
2016-08-23 04:10:00 +00:00
|
|
|
user.avatar = self.avatar if self.avatar else user.avatar
|
|
|
|
|
2018-04-10 01:59:28 +00:00
|
|
|
if enable_otp is not None:
|
|
|
|
user.otp_secret = ""
|
|
|
|
|
2016-08-23 04:10:00 +00:00
|
|
|
if enable_otp == True:
|
|
|
|
# generate the opt secret key
|
|
|
|
user.otp_secret = base64.b32encode(os.urandom(10)).decode('utf-8')
|
|
|
|
|
|
|
|
try:
|
|
|
|
db.session.add(user)
|
|
|
|
db.session.commit()
|
|
|
|
return True
|
|
|
|
except Exception:
|
|
|
|
db.session.rollback()
|
|
|
|
return False
|
2015-12-16 07:21:30 +00:00
|
|
|
|
2018-06-05 18:41:39 +00:00
|
|
|
def get_account_query(self):
|
|
|
|
"""
|
|
|
|
Get query for account to which the user is associated.
|
|
|
|
"""
|
|
|
|
return db.session.query(Account) \
|
2019-03-01 22:49:31 +00:00
|
|
|
.outerjoin(AccountUser, Account.id == AccountUser.account_id) \
|
|
|
|
.filter(AccountUser.user_id == self.id)
|
2018-06-05 18:41:39 +00:00
|
|
|
|
|
|
|
def get_account(self):
|
|
|
|
"""
|
|
|
|
Get all accounts to which the user is associated.
|
|
|
|
"""
|
|
|
|
return self.get_account_query()
|
|
|
|
|
2017-09-15 13:14:04 +00:00
|
|
|
def get_domain_query(self):
|
2018-06-05 18:41:39 +00:00
|
|
|
"""
|
|
|
|
Get query for domain to which the user has access permission.
|
|
|
|
This includes direct domain permission AND permission through
|
|
|
|
account membership
|
|
|
|
"""
|
|
|
|
return db.session.query(Domain) \
|
2019-03-01 22:49:31 +00:00
|
|
|
.outerjoin(DomainUser, Domain.id == DomainUser.domain_id) \
|
|
|
|
.outerjoin(Account, Domain.account_id == Account.id) \
|
|
|
|
.outerjoin(AccountUser, Account.id == AccountUser.account_id) \
|
|
|
|
.filter(
|
|
|
|
db.or_(
|
|
|
|
DomainUser.user_id == User.id,
|
|
|
|
AccountUser.user_id == User.id
|
|
|
|
)
|
|
|
|
) \
|
|
|
|
.filter(User.id == self.id)
|
2017-09-15 13:14:04 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
def get_domain(self):
|
|
|
|
"""
|
|
|
|
Get domains which user has permission to
|
|
|
|
access
|
|
|
|
"""
|
2018-06-05 18:41:39 +00:00
|
|
|
return self.get_domain_query()
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
def get_domains(self):
|
|
|
|
return self.get_domain_query().all()
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
def delete(self):
|
|
|
|
"""
|
|
|
|
Delete a user
|
|
|
|
"""
|
2018-06-05 18:41:39 +00:00
|
|
|
# revoke all user privileges and account associations first
|
2015-12-13 09:34:12 +00:00
|
|
|
self.revoke_privilege()
|
2018-06-05 18:41:39 +00:00
|
|
|
for a in self.get_account():
|
|
|
|
a.revoke_privileges_by_id(self.id)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
User.query.filter(User.username == self.username).delete()
|
|
|
|
db.session.commit()
|
|
|
|
return True
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.rollback()
|
2018-08-31 11:00:41 +00:00
|
|
|
logging.error('Cannot delete user {0} from DB. DETAIL: {1}'.format(self.username, e))
|
2015-12-13 09:34:12 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
def revoke_privilege(self):
|
|
|
|
"""
|
2018-10-02 07:23:41 +00:00
|
|
|
Revoke all privileges from a user
|
2015-12-13 09:34:12 +00:00
|
|
|
"""
|
|
|
|
user = User.query.filter(User.username == self.username).first()
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
if user:
|
|
|
|
user_id = user.id
|
|
|
|
try:
|
|
|
|
DomainUser.query.filter(DomainUser.user_id == user_id).delete()
|
|
|
|
db.session.commit()
|
|
|
|
return True
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.rollback()
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'Cannot revoke user {0} privileges. DETAIL: {1}'.format(self.username, e))
|
2015-12-13 09:34:12 +00:00
|
|
|
return False
|
|
|
|
return False
|
|
|
|
|
2018-08-31 04:57:06 +00:00
|
|
|
def set_role(self, role_name):
|
2019-03-01 22:49:31 +00:00
|
|
|
role = Role.query.filter(Role.name == role_name).first()
|
2018-08-31 04:57:06 +00:00
|
|
|
if role:
|
2019-03-01 22:49:31 +00:00
|
|
|
user = User.query.filter(User.username == self.username).first()
|
2018-08-31 04:57:06 +00:00
|
|
|
user.role_id = role.id
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': True, 'msg': 'Set user role successfully'}
|
|
|
|
else:
|
|
|
|
return {'status': False, 'msg': 'Role does not exist'}
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
class Account(db.Model):
|
|
|
|
__tablename__ = 'account'
|
2019-03-01 22:49:31 +00:00
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
name = db.Column(db.String(40), index=True, unique=True, nullable=False)
|
|
|
|
description = db.Column(db.String(128))
|
|
|
|
contact = db.Column(db.String(128))
|
|
|
|
mail = db.Column(db.String(128))
|
|
|
|
domains = db.relationship("Domain", back_populates="account")
|
|
|
|
|
|
|
|
def __init__(self, name=None, description=None, contact=None, mail=None):
|
|
|
|
self.name = name
|
|
|
|
self.description = description
|
|
|
|
self.contact = contact
|
|
|
|
self.mail = mail
|
|
|
|
|
|
|
|
if self.name is not None:
|
2019-03-01 22:49:31 +00:00
|
|
|
self.name = ''.join(
|
|
|
|
c for c in self.name.lower() if c in "abcdefghijklmnopqrstuvwxyz0123456789")
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return '<Account {0}r>'.format(self.name)
|
|
|
|
|
|
|
|
def get_name_by_id(self, account_id):
|
|
|
|
"""
|
|
|
|
Convert account_id to account_name
|
|
|
|
"""
|
|
|
|
account = Account.query.filter(Account.id == account_id).first()
|
|
|
|
if account is None:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
return account.name
|
|
|
|
|
|
|
|
def get_id_by_name(self, account_name):
|
|
|
|
"""
|
|
|
|
Convert account_name to account_id
|
|
|
|
"""
|
2018-07-30 11:15:55 +00:00
|
|
|
# Skip actual database lookup for empty queries
|
|
|
|
if account_name is None or account_name == "":
|
|
|
|
return None
|
|
|
|
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
account = Account.query.filter(Account.name == account_name).first()
|
|
|
|
if account is None:
|
2018-06-06 13:59:15 +00:00
|
|
|
return None
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
|
|
|
|
return account.id
|
|
|
|
|
|
|
|
def unassociate_domains(self):
|
|
|
|
"""
|
|
|
|
Remove associations to this account from all domains
|
|
|
|
"""
|
|
|
|
account = Account.query.filter(Account.name == self.name).first()
|
|
|
|
for domain in account.domains:
|
2019-01-11 02:00:51 +00:00
|
|
|
Domain(name=domain.name).assoc_account(None)
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
|
|
|
|
def create_account(self):
|
|
|
|
"""
|
|
|
|
Create a new account
|
|
|
|
"""
|
|
|
|
# Sanity check - account name
|
|
|
|
if self.name == "":
|
|
|
|
return {'status': False, 'msg': 'No account name specified'}
|
|
|
|
|
|
|
|
# check that account name is not already used
|
|
|
|
account = Account.query.filter(Account.name == self.name).first()
|
|
|
|
if account:
|
|
|
|
return {'status': False, 'msg': 'Account already exists'}
|
|
|
|
|
|
|
|
db.session.add(self)
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': True, 'msg': 'Account created successfully'}
|
|
|
|
|
|
|
|
def update_account(self):
|
|
|
|
"""
|
|
|
|
Update an existing account
|
|
|
|
"""
|
|
|
|
# Sanity check - account name
|
|
|
|
if self.name == "":
|
|
|
|
return {'status': False, 'msg': 'No account name specified'}
|
|
|
|
|
|
|
|
# read account and check that it exists
|
|
|
|
account = Account.query.filter(Account.name == self.name).first()
|
|
|
|
if not account:
|
|
|
|
return {'status': False, 'msg': 'Account does not exist'}
|
|
|
|
|
|
|
|
account.description = self.description
|
|
|
|
account.contact = self.contact
|
|
|
|
account.mail = self.mail
|
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': True, 'msg': 'Account updated successfully'}
|
|
|
|
|
|
|
|
def delete_account(self):
|
|
|
|
"""
|
|
|
|
Delete an account
|
|
|
|
"""
|
2018-06-05 18:41:39 +00:00
|
|
|
# unassociate all domains and users first
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
self.unassociate_domains()
|
2018-06-05 18:41:39 +00:00
|
|
|
self.grant_privileges([])
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
Account.query.filter(Account.name == self.name).delete()
|
|
|
|
db.session.commit()
|
|
|
|
return True
|
|
|
|
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
db.session.rollback()
|
2018-08-31 11:00:41 +00:00
|
|
|
logging.error('Cannot delete account {0} from DB. DETAIL: {1}'.format(self.username, e))
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
return False
|
|
|
|
|
2018-06-05 18:41:39 +00:00
|
|
|
def get_user(self):
|
|
|
|
"""
|
|
|
|
Get users (id) associated with this account
|
|
|
|
"""
|
|
|
|
user_ids = []
|
2019-03-01 22:49:31 +00:00
|
|
|
query = db.session.query(AccountUser, Account).filter(User.id == AccountUser.user_id).filter(
|
|
|
|
Account.id == AccountUser.account_id).filter(Account.name == self.name).all()
|
2018-06-05 18:41:39 +00:00
|
|
|
for q in query:
|
|
|
|
user_ids.append(q[0].user_id)
|
|
|
|
return user_ids
|
|
|
|
|
|
|
|
def grant_privileges(self, new_user_list):
|
|
|
|
"""
|
|
|
|
Reconfigure account_user table
|
|
|
|
"""
|
|
|
|
account_id = self.get_id_by_name(self.name)
|
|
|
|
|
|
|
|
account_user_ids = self.get_user()
|
2019-03-01 22:49:31 +00:00
|
|
|
new_user_ids = [u.id for u in User.query.filter(
|
|
|
|
User.username.in_(new_user_list)).all()] if new_user_list else []
|
2018-06-05 18:41:39 +00:00
|
|
|
|
|
|
|
removed_ids = list(set(account_user_ids).difference(new_user_ids))
|
|
|
|
added_ids = list(set(new_user_ids).difference(account_user_ids))
|
|
|
|
|
|
|
|
try:
|
|
|
|
for uid in removed_ids:
|
2019-03-01 22:49:31 +00:00
|
|
|
AccountUser.query.filter(AccountUser.user_id == uid).filter(
|
|
|
|
AccountUser.account_id == account_id).delete()
|
2018-06-05 18:41:39 +00:00
|
|
|
db.session.commit()
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2018-06-05 18:41:39 +00:00
|
|
|
db.session.rollback()
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'Cannot revoke user privileges on account {0}. DETAIL: {1}'.format(self.name, e))
|
2018-06-05 18:41:39 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
for uid in added_ids:
|
|
|
|
au = AccountUser(account_id, uid)
|
|
|
|
db.session.add(au)
|
|
|
|
db.session.commit()
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2018-06-05 18:41:39 +00:00
|
|
|
db.session.rollback()
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'Cannot grant user privileges to account {0}. DETAIL: {1}'.format(self.name, e))
|
2018-06-05 18:41:39 +00:00
|
|
|
|
|
|
|
def revoke_privileges_by_id(self, user_id):
|
|
|
|
"""
|
2018-10-02 07:23:41 +00:00
|
|
|
Remove a single user from privilege list based on user_id
|
2018-06-05 18:41:39 +00:00
|
|
|
"""
|
|
|
|
new_uids = [u for u in self.get_user() if u != user_id]
|
|
|
|
users = []
|
|
|
|
for uid in new_uids:
|
|
|
|
users.append(User(id=uid).get_user_info_by_id().username)
|
|
|
|
|
|
|
|
self.grant_privileges(users)
|
2019-01-11 02:00:51 +00:00
|
|
|
|
2018-06-25 11:15:35 +00:00
|
|
|
def add_user(self, user):
|
|
|
|
"""
|
|
|
|
Add a single user to Account by User
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
au = AccountUser(self.id, user.id)
|
|
|
|
db.session.add(au)
|
|
|
|
db.session.commit()
|
|
|
|
return True
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2018-06-25 11:15:35 +00:00
|
|
|
db.session.rollback()
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'Cannot add user privileges on account {0}. DETAIL: {1}'.format(self.name, e))
|
2018-06-25 11:15:35 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
def remove_user(self, user):
|
|
|
|
"""
|
|
|
|
Remove a single user from Account by User
|
|
|
|
"""
|
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
AccountUser.query.filter(AccountUser.user_id == user.id).filter(
|
|
|
|
AccountUser.account_id == self.id).delete()
|
2018-06-25 11:15:35 +00:00
|
|
|
db.session.commit()
|
|
|
|
return True
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2018-06-25 11:15:35 +00:00
|
|
|
db.session.rollback()
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'Cannot revoke user privileges on account {0}. DETAIL: {1}'.format(self.name, e))
|
2018-06-25 11:15:35 +00:00
|
|
|
return False
|
2018-06-05 18:41:39 +00:00
|
|
|
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
class Role(db.Model):
|
2019-03-01 22:49:31 +00:00
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
2015-12-13 09:34:12 +00:00
|
|
|
name = db.Column(db.String(64), index=True, unique=True)
|
|
|
|
description = db.Column(db.String(128))
|
2019-03-01 22:49:31 +00:00
|
|
|
users = db.relationship('User', backref='role', lazy=True)
|
|
|
|
apikeys = db.relationship('ApiKey', back_populates='role', lazy=True)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2016-04-11 09:40:44 +00:00
|
|
|
def __init__(self, id=None, name=None, description=None):
|
2015-12-13 09:34:12 +00:00
|
|
|
self.id = id
|
|
|
|
self.name = name
|
|
|
|
self.description = description
|
2016-08-23 02:52:35 +00:00
|
|
|
|
|
|
|
# allow database autoincrement to do its own ID assignments
|
2016-04-11 09:40:44 +00:00
|
|
|
def __init__(self, name=None, description=None):
|
|
|
|
self.id = None
|
|
|
|
self.name = name
|
|
|
|
self.description = description
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def __repr__(self):
|
2018-04-01 00:57:41 +00:00
|
|
|
return '<Role {0}r>'.format(self.name)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
|
2016-07-02 17:24:13 +00:00
|
|
|
class DomainSetting(db.Model):
|
|
|
|
__tablename__ = 'domain_setting'
|
2019-03-01 22:49:31 +00:00
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
2016-07-02 17:24:13 +00:00
|
|
|
domain_id = db.Column(db.Integer, db.ForeignKey('domain.id'))
|
|
|
|
domain = db.relationship('Domain', back_populates='settings')
|
2019-03-01 22:49:31 +00:00
|
|
|
setting = db.Column(db.String(255), nullable=False)
|
2016-07-02 17:24:13 +00:00
|
|
|
value = db.Column(db.String(255))
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2016-07-02 17:24:13 +00:00
|
|
|
def __init__(self, id=None, setting=None, value=None):
|
|
|
|
self.id = id
|
|
|
|
self.setting = setting
|
|
|
|
self.value = value
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2016-07-02 17:24:13 +00:00
|
|
|
def __repr__(self):
|
2018-04-01 00:57:41 +00:00
|
|
|
return '<DomainSetting {0} for {1}>'.format(setting, self.domain.name)
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2016-07-02 17:24:13 +00:00
|
|
|
def __eq__(self, other):
|
2019-02-11 21:00:17 +00:00
|
|
|
return type(self) == type(other) and self.setting == other.setting
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2016-07-02 17:24:13 +00:00
|
|
|
def set(self, value):
|
|
|
|
try:
|
|
|
|
self.value = value
|
|
|
|
db.session.commit()
|
|
|
|
return True
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Unable to set DomainSetting value. DETAIL: {0}'.format(e))
|
2016-07-02 17:24:13 +00:00
|
|
|
logging.debug(traceback.format_exc())
|
|
|
|
db.session.rollback()
|
|
|
|
return False
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
domain_apikey = db.Table('domain_apikey',
|
|
|
|
db.Column('domain_id', db.Integer, db.ForeignKey('domain.id')),
|
|
|
|
db.Column('apikey_id', db.Integer, db.ForeignKey('apikey.id'))
|
|
|
|
)
|
|
|
|
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
class Domain(db.Model):
|
2019-03-01 22:49:31 +00:00
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
2015-12-13 09:34:12 +00:00
|
|
|
name = db.Column(db.String(255), index=True, unique=True)
|
|
|
|
master = db.Column(db.String(128))
|
2019-03-01 22:49:31 +00:00
|
|
|
type = db.Column(db.String(6), nullable=False)
|
2015-12-13 09:34:12 +00:00
|
|
|
serial = db.Column(db.Integer)
|
|
|
|
notified_serial = db.Column(db.Integer)
|
|
|
|
last_check = db.Column(db.Integer)
|
2016-03-24 13:01:08 +00:00
|
|
|
dnssec = db.Column(db.Integer)
|
2018-06-06 13:59:15 +00:00
|
|
|
account_id = db.Column(db.Integer, db.ForeignKey('account.id'))
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
account = db.relationship("Account", back_populates="domains")
|
2016-07-02 17:24:13 +00:00
|
|
|
settings = db.relationship('DomainSetting', back_populates='domain')
|
2019-03-01 22:49:31 +00:00
|
|
|
apikeys = db.relationship(
|
|
|
|
"ApiKey",
|
|
|
|
secondary=domain_apikey,
|
|
|
|
back_populates="domains"
|
|
|
|
)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
def __init__(self, id=None, name=None, master=None, type='NATIVE', serial=None, notified_serial=None, last_check=None, dnssec=None, account_id=None):
|
2015-12-13 09:34:12 +00:00
|
|
|
self.id = id
|
|
|
|
self.name = name
|
|
|
|
self.master = master
|
|
|
|
self.type = type
|
|
|
|
self.serial = serial
|
|
|
|
self.notified_serial = notified_serial
|
|
|
|
self.last_check = last_check
|
2016-03-24 13:01:08 +00:00
|
|
|
self.dnssec = dnssec
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
self.account_id = account_id
|
2018-08-20 02:59:19 +00:00
|
|
|
# PDNS configs
|
|
|
|
self.PDNS_STATS_URL = Setting().get('pdns_api_url')
|
|
|
|
self.PDNS_API_KEY = Setting().get('pdns_api_key')
|
|
|
|
self.PDNS_VERSION = Setting().get('pdns_version')
|
|
|
|
self.API_EXTENDED_URL = utils.pdns_api_extended_uri(self.PDNS_VERSION)
|
|
|
|
|
|
|
|
if StrictVersion(self.PDNS_VERSION) >= StrictVersion('4.0.0'):
|
|
|
|
self.NEW_SCHEMA = True
|
|
|
|
else:
|
|
|
|
self.NEW_SCHEMA = False
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def __repr__(self):
|
2018-04-01 00:57:41 +00:00
|
|
|
return '<Domain {0}>'.format(self.name)
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2016-07-02 17:24:13 +00:00
|
|
|
def add_setting(self, setting, value):
|
|
|
|
try:
|
|
|
|
self.settings.append(DomainSetting(setting=setting, value=value))
|
|
|
|
db.session.commit()
|
|
|
|
return True
|
2018-03-30 06:49:35 +00:00
|
|
|
except Exception as e:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'Can not create setting {0} for domain {1}. {2}'.format(setting, self.name, e))
|
2016-07-02 17:24:13 +00:00
|
|
|
return False
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2018-04-12 04:18:44 +00:00
|
|
|
def get_domain_info(self, domain_name):
|
|
|
|
"""
|
|
|
|
Get all domains which has in PowerDNS
|
|
|
|
"""
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain_name)), headers=headers)
|
2018-04-12 04:18:44 +00:00
|
|
|
return jdata
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
def get_domains(self):
|
|
|
|
"""
|
|
|
|
Get all domains which has in PowerDNS
|
|
|
|
"""
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers)
|
2015-12-13 09:34:12 +00:00
|
|
|
return jdata
|
|
|
|
|
|
|
|
def get_id_by_name(self, name):
|
|
|
|
"""
|
|
|
|
Return domain id
|
|
|
|
"""
|
2016-11-16 13:02:43 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
domain = Domain.query.filter(Domain.name == name).first()
|
2016-11-16 13:02:43 +00:00
|
|
|
return domain.id
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2018-08-31 15:30:08 +00:00
|
|
|
logging.error('Domain does not exist. ERROR: {0}'.format(e))
|
2016-11-16 13:02:43 +00:00
|
|
|
return None
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def update(self):
|
|
|
|
"""
|
|
|
|
Fetch zones (domains) from PowerDNS and update into DB
|
|
|
|
"""
|
|
|
|
db_domain = Domain.query.all()
|
|
|
|
list_db_domain = [d.name for d in db_domain]
|
2019-03-01 22:49:31 +00:00
|
|
|
dict_db_domain = dict((x.name, x) for x in db_domain)
|
2019-04-29 14:41:37 +00:00
|
|
|
logging.info("{} Entrys in pdnsADMIN".format(len(list_db_domain)))
|
2015-12-13 09:34:12 +00:00
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers)
|
2016-06-07 08:20:56 +00:00
|
|
|
list_jdomain = [d['name'].rstrip('.') for d in jdata]
|
2019-04-29 14:41:37 +00:00
|
|
|
logging.info("{} Entrys in PDNSApi".format(len(list_jdomain)))
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
|
|
|
# domains should remove from db since it doesn't exist in powerdns anymore
|
|
|
|
should_removed_db_domain = list(set(list_db_domain).difference(list_jdomain))
|
|
|
|
for d in should_removed_db_domain:
|
|
|
|
# revoke permission before delete domain
|
2019-03-01 22:49:31 +00:00
|
|
|
domain = Domain.query.filter(Domain.name == d).first()
|
|
|
|
domain_user = DomainUser.query.filter(DomainUser.domain_id == domain.id)
|
2015-12-13 09:34:12 +00:00
|
|
|
if domain_user:
|
|
|
|
domain_user.delete()
|
|
|
|
db.session.commit()
|
2019-03-01 22:49:31 +00:00
|
|
|
domain_setting = DomainSetting.query.filter(
|
|
|
|
DomainSetting.domain_id == domain.id)
|
2016-11-21 12:30:16 +00:00
|
|
|
if domain_setting:
|
|
|
|
domain_setting.delete()
|
|
|
|
db.session.commit()
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
domain.apikeys[:] = []
|
|
|
|
db.session.commit()
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
# then remove domain
|
|
|
|
Domain.query.filter(Domain.name == d).delete()
|
|
|
|
db.session.commit()
|
2019-04-29 14:41:37 +00:00
|
|
|
logging.info("Removed Domain {0} successfully from pdnsADMIN".format(d))
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Can not delete domain from DB. DETAIL: {0}'.format(e))
|
2015-12-13 09:34:12 +00:00
|
|
|
logging.debug(traceback.format_exc())
|
|
|
|
db.session.rollback()
|
|
|
|
|
|
|
|
# update/add new domain
|
|
|
|
for data in jdata:
|
2018-07-30 11:15:55 +00:00
|
|
|
if 'account' in data:
|
|
|
|
account_id = Account().get_id_by_name(data['account'])
|
|
|
|
else:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.debug(
|
|
|
|
"No 'account' data found in API result - Unsupported PowerDNS version?")
|
2018-07-30 11:15:55 +00:00
|
|
|
account_id = None
|
2016-06-16 03:31:36 +00:00
|
|
|
d = dict_db_domain.get(data['name'].rstrip('.'), None)
|
|
|
|
changed = False
|
2015-12-13 09:34:12 +00:00
|
|
|
if d:
|
2016-06-16 03:31:36 +00:00
|
|
|
# existing domain, only update if something actually has changed
|
2019-03-01 22:49:31 +00:00
|
|
|
if (d.master != str(data['masters'])
|
2016-06-16 03:31:36 +00:00
|
|
|
or d.type != data['kind']
|
|
|
|
or d.serial != data['serial']
|
|
|
|
or d.notified_serial != data['notified_serial']
|
2019-03-01 22:49:31 +00:00
|
|
|
or d.last_check != (1 if data['last_check'] else 0)
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
or d.dnssec != data['dnssec']
|
2019-03-01 22:49:31 +00:00
|
|
|
or d.account_id != account_id):
|
2016-06-16 03:31:36 +00:00
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
d.master = str(data['masters'])
|
|
|
|
d.type = data['kind']
|
|
|
|
d.serial = data['serial']
|
|
|
|
d.notified_serial = data['notified_serial']
|
|
|
|
d.last_check = 1 if data['last_check'] else 0
|
|
|
|
d.dnssec = 1 if data['dnssec'] else 0
|
|
|
|
d.account_id = account_id
|
|
|
|
changed = True
|
2016-06-16 03:31:36 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
else:
|
|
|
|
# add new domain
|
|
|
|
d = Domain()
|
2016-06-07 08:20:56 +00:00
|
|
|
d.name = data['name'].rstrip('.')
|
2015-12-13 09:34:12 +00:00
|
|
|
d.master = str(data['masters'])
|
|
|
|
d.type = data['kind']
|
|
|
|
d.serial = data['serial']
|
|
|
|
d.notified_serial = data['notified_serial']
|
|
|
|
d.last_check = data['last_check']
|
2016-04-14 05:19:02 +00:00
|
|
|
d.dnssec = 1 if data['dnssec'] else 0
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
d.account_id = account_id
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.add(d)
|
2016-06-16 03:31:36 +00:00
|
|
|
changed = True
|
|
|
|
if changed:
|
|
|
|
try:
|
|
|
|
db.session.commit()
|
2019-04-29 14:41:37 +00:00
|
|
|
logging.info("synced PDNS-Domain to pdnsADMIN: {0}".format(d.name))
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
except Exception as e:
|
2016-06-16 03:31:36 +00:00
|
|
|
db.session.rollback()
|
2019-04-29 14:41:37 +00:00
|
|
|
logging.info("Rolledback Domain {0} {1}".format(d.name, e))
|
|
|
|
logging.info('Update finished')
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'ok', 'msg': 'Domain table has been updated successfully'}
|
2018-03-30 06:49:35 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Can not update domain table. Error: {0}'.format(e))
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'error', 'msg': 'Can not update domain table'}
|
|
|
|
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
def add(self, domain_name, domain_type, soa_edit_api, domain_ns=[], domain_master_ips=[], account_name=None):
|
2015-12-13 09:34:12 +00:00
|
|
|
"""
|
|
|
|
Add a domain to power dns
|
|
|
|
"""
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2016-03-05 10:04:12 +00:00
|
|
|
|
2018-08-20 02:59:19 +00:00
|
|
|
if self.NEW_SCHEMA:
|
2016-06-07 08:20:56 +00:00
|
|
|
domain_name = domain_name + '.'
|
|
|
|
domain_ns = [ns + '.' for ns in domain_ns]
|
|
|
|
|
2018-05-24 18:12:12 +00:00
|
|
|
if soa_edit_api not in ["DEFAULT", "INCREASE", "EPOCH", "OFF"]:
|
|
|
|
soa_edit_api = 'DEFAULT'
|
|
|
|
|
|
|
|
elif soa_edit_api == 'OFF':
|
|
|
|
soa_edit_api = ''
|
|
|
|
|
|
|
|
post_data = {
|
|
|
|
"name": domain_name,
|
|
|
|
"kind": domain_type,
|
|
|
|
"masters": domain_master_ips,
|
|
|
|
"nameservers": domain_ns,
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
"soa_edit_api": soa_edit_api,
|
|
|
|
"account": account_name
|
2018-05-24 18:12:12 +00:00
|
|
|
}
|
2016-03-05 10:04:12 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones'), headers=headers, method='POST', data=post_data)
|
2015-12-13 09:34:12 +00:00
|
|
|
if 'error' in jdata.keys():
|
|
|
|
logging.error(jdata['error'])
|
|
|
|
return {'status': 'error', 'msg': jdata['error']}
|
|
|
|
else:
|
2019-04-29 14:41:37 +00:00
|
|
|
logging.info('Added domain successfully to PowerDNS: {0}'.format(domain_name))
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
self.update()
|
2019-04-29 14:41:37 +00:00
|
|
|
logging.info('Added domain successfully to PowerDNS and Synced PdnsADMIN: {0}'.format(domain_name))
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'ok', 'msg': 'Added domain successfully'}
|
2018-03-30 06:49:35 +00:00
|
|
|
except Exception as e:
|
2018-04-01 00:57:41 +00:00
|
|
|
logging.error('Cannot add domain {0}'.format(domain_name))
|
2018-08-31 11:00:41 +00:00
|
|
|
logging.debug(traceback.format_exc())
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'error', 'msg': 'Cannot add this domain.'}
|
|
|
|
|
2018-03-27 23:41:33 +00:00
|
|
|
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 = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2018-05-24 18:12:12 +00:00
|
|
|
|
|
|
|
if soa_edit_api not in ["DEFAULT", "INCREASE", "EPOCH", "OFF"]:
|
|
|
|
soa_edit_api = 'DEFAULT'
|
|
|
|
|
|
|
|
elif soa_edit_api == 'OFF':
|
|
|
|
soa_edit_api = ''
|
|
|
|
|
|
|
|
post_data = {
|
|
|
|
"soa_edit_api": soa_edit_api,
|
|
|
|
"kind": domain.type
|
|
|
|
}
|
|
|
|
|
2018-03-27 23:41:33 +00:00
|
|
|
try:
|
|
|
|
jdata = utils.fetch_json(
|
2019-03-01 22:49:31 +00:00
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain.name)), headers=headers,
|
|
|
|
method='PUT', data=post_data)
|
2018-03-27 23:41:33 +00:00
|
|
|
if 'error' in jdata.keys():
|
|
|
|
logging.error(jdata['error'])
|
|
|
|
return {'status': 'error', 'msg': jdata['error']}
|
|
|
|
else:
|
2018-04-02 06:38:53 +00:00
|
|
|
logging.info('soa-edit-api changed for domain {0} successfully'.format(domain_name))
|
2018-03-27 23:41:33 +00:00
|
|
|
return {'status': 'ok', 'msg': 'soa-edit-api changed successfully'}
|
2018-04-02 06:38:53 +00:00
|
|
|
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))
|
2018-03-27 23:41:33 +00:00
|
|
|
return {'status': 'error', 'msg': 'Cannot change soa-edit-api this domain.'}
|
|
|
|
|
2016-11-16 13:09:13 +00:00
|
|
|
def create_reverse_domain(self, domain_name, domain_reverse_name):
|
|
|
|
"""
|
2018-03-01 07:26:29 +00:00
|
|
|
Check the existing reverse lookup domain,
|
2016-11-16 13:09:13 +00:00
|
|
|
if not exists create a new one automatically
|
|
|
|
"""
|
2016-11-21 18:36:43 +00:00
|
|
|
domain_obj = Domain.query.filter(Domain.name == domain_name).first()
|
2019-03-01 22:49:31 +00:00
|
|
|
domain_auto_ptr = DomainSetting.query.filter(
|
|
|
|
DomainSetting.domain == domain_obj).filter(DomainSetting.setting == 'auto_ptr').first()
|
2016-11-21 12:44:47 +00:00
|
|
|
domain_auto_ptr = strtobool(domain_auto_ptr.value) if domain_auto_ptr else False
|
2018-08-18 15:42:18 +00:00
|
|
|
system_auto_ptr = Setting().get('auto_ptr')
|
2016-11-16 13:09:13 +00:00
|
|
|
self.name = domain_name
|
|
|
|
domain_id = self.get_id_by_name(domain_reverse_name)
|
2016-11-21 12:44:47 +00:00
|
|
|
if None == domain_id and \
|
|
|
|
(
|
2019-03-01 22:49:31 +00:00
|
|
|
system_auto_ptr or
|
2016-11-21 12:44:47 +00:00
|
|
|
domain_auto_ptr
|
|
|
|
):
|
2018-05-24 18:12:12 +00:00
|
|
|
result = self.add(domain_reverse_name, 'Master', 'DEFAULT', '', '')
|
2016-11-16 13:09:13 +00:00
|
|
|
self.update()
|
|
|
|
if result['status'] == 'ok':
|
2019-03-01 22:49:31 +00:00
|
|
|
history = History(msg='Add reverse lookup domain {0}'.format(domain_reverse_name), detail=str(
|
|
|
|
{'domain_type': 'Master', 'domain_master_ips': ''}), created_by='System')
|
2016-11-16 13:09:13 +00:00
|
|
|
history.add()
|
|
|
|
else:
|
|
|
|
return {'status': 'error', 'msg': 'Adding reverse lookup domain failed'}
|
|
|
|
domain_user_ids = self.get_user()
|
|
|
|
domain_users = []
|
|
|
|
u = User()
|
|
|
|
for uid in domain_user_ids:
|
|
|
|
u.id = uid
|
|
|
|
tmp = u.get_user_info_by_id()
|
|
|
|
domain_users.append(tmp.username)
|
|
|
|
if 0 != len(domain_users):
|
|
|
|
self.name = domain_reverse_name
|
2018-09-04 06:10:55 +00:00
|
|
|
self.grant_privileges(domain_users)
|
2018-10-02 07:23:41 +00:00
|
|
|
return {'status': 'ok', 'msg': 'New reverse lookup domain created with granted privileges'}
|
2016-11-16 13:09:13 +00:00
|
|
|
return {'status': 'ok', 'msg': 'New reverse lookup domain created without users'}
|
|
|
|
return {'status': 'ok', 'msg': 'Reverse lookup domain already exists'}
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2016-11-21 18:40:43 +00:00
|
|
|
def get_reverse_domain_name(self, reverse_host_address):
|
2016-11-28 07:39:07 +00:00
|
|
|
c = 1
|
2016-11-21 18:40:43 +00:00
|
|
|
if re.search('ip6.arpa', reverse_host_address):
|
2019-03-01 22:49:31 +00:00
|
|
|
for i in range(1, 32, 1):
|
|
|
|
address = re.search(
|
|
|
|
'((([a-f0-9]\.){' + str(i) + '})(?P<ipname>.+6.arpa)\.?)', reverse_host_address)
|
2016-11-21 18:40:43 +00:00
|
|
|
if None != self.get_id_by_name(address.group('ipname')):
|
2016-11-28 07:39:07 +00:00
|
|
|
c = i
|
2016-11-21 18:40:43 +00:00
|
|
|
break
|
2019-03-01 22:49:31 +00:00
|
|
|
return re.search('((([a-f0-9]\.){' + str(c) + '})(?P<ipname>.+6.arpa)\.?)', reverse_host_address).group('ipname')
|
2016-11-21 18:40:43 +00:00
|
|
|
else:
|
2019-03-01 22:49:31 +00:00
|
|
|
for i in range(1, 4, 1):
|
|
|
|
address = re.search(
|
|
|
|
'((([0-9]+\.){' + str(i) + '})(?P<ipname>.+r.arpa)\.?)', reverse_host_address)
|
2016-11-21 18:40:43 +00:00
|
|
|
if None != self.get_id_by_name(address.group('ipname')):
|
2016-11-28 07:39:07 +00:00
|
|
|
c = i
|
2016-11-21 18:40:43 +00:00
|
|
|
break
|
2019-03-01 22:49:31 +00:00
|
|
|
return re.search('((([0-9]+\.){' + str(c) + '})(?P<ipname>.+r.arpa)\.?)', reverse_host_address).group('ipname')
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def delete(self, domain_name):
|
|
|
|
"""
|
|
|
|
Delete a single domain name from powerdns
|
|
|
|
"""
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL +
|
|
|
|
'/servers/localhost/zones/{0}'.format(domain_name)), headers=headers, method='DELETE')
|
2019-04-29 14:41:37 +00:00
|
|
|
logging.info('Deleted domain {0} successfully from PowerDNS'.format(domain_name))
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'ok', 'msg': 'Delete domain successfully'}
|
2018-03-30 06:49:35 +00:00
|
|
|
except Exception as e:
|
2018-04-01 00:57:41 +00:00
|
|
|
logging.error('Cannot delete domain {0}'.format(domain_name))
|
2018-08-31 11:00:41 +00:00
|
|
|
logging.debug(traceback.format_exc())
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'error', 'msg': 'Cannot delete domain'}
|
|
|
|
|
|
|
|
def get_user(self):
|
|
|
|
"""
|
|
|
|
Get users (id) who have access to this domain name
|
|
|
|
"""
|
|
|
|
user_ids = []
|
2019-03-01 22:49:31 +00:00
|
|
|
query = db.session.query(DomainUser, Domain).filter(User.id == DomainUser.user_id).filter(
|
|
|
|
Domain.id == DomainUser.domain_id).filter(Domain.name == self.name).all()
|
2015-12-13 09:34:12 +00:00
|
|
|
for q in query:
|
|
|
|
user_ids.append(q[0].user_id)
|
|
|
|
return user_ids
|
|
|
|
|
2018-09-04 06:10:55 +00:00
|
|
|
def grant_privileges(self, new_user_list):
|
2015-12-13 09:34:12 +00:00
|
|
|
"""
|
|
|
|
Reconfigure domain_user table
|
|
|
|
"""
|
|
|
|
|
|
|
|
domain_id = self.get_id_by_name(self.name)
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
domain_user_ids = self.get_user()
|
2019-03-01 22:49:31 +00:00
|
|
|
new_user_ids = [u.id for u in User.query.filter(
|
|
|
|
User.username.in_(new_user_list)).all()] if new_user_list else []
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
removed_ids = list(set(domain_user_ids).difference(new_user_ids))
|
|
|
|
added_ids = list(set(new_user_ids).difference(domain_user_ids))
|
|
|
|
|
|
|
|
try:
|
|
|
|
for uid in removed_ids:
|
2019-03-01 22:49:31 +00:00
|
|
|
DomainUser.query.filter(DomainUser.user_id == uid).filter(
|
|
|
|
DomainUser.domain_id == domain_id).delete()
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.commit()
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.rollback()
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'Cannot revoke user privileges on domain {0}. DETAIL: {1}'.format(self.name, e))
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
try:
|
|
|
|
for uid in added_ids:
|
|
|
|
du = DomainUser(domain_id, uid)
|
|
|
|
db.session.add(du)
|
|
|
|
db.session.commit()
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.rollback()
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
'Cannot grant user privileges to domain {0}. DETAIL: {1}'.format(self.name, e))
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def update_from_master(self, domain_name):
|
|
|
|
"""
|
|
|
|
Update records from Master DNS server
|
|
|
|
"""
|
|
|
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
|
|
|
if domain:
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}/axfr-retrieve'.format(domain.name)), headers=headers, method='PUT')
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'ok', 'msg': 'Update from Master successfully'}
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Cannot update from master. DETAIL: {0}'.format(e))
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
|
|
|
|
else:
|
|
|
|
return {'status': 'error', 'msg': 'This domain doesnot exist'}
|
|
|
|
|
2016-03-24 13:01:08 +00:00
|
|
|
def get_domain_dnssec(self, domain_name):
|
|
|
|
"""
|
|
|
|
Get domain DNSSEC information
|
|
|
|
"""
|
|
|
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
|
|
|
if domain:
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2016-03-24 13:01:08 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys'.format(domain.name)), headers=headers, method='GET')
|
2016-03-24 13:01:08 +00:00
|
|
|
if 'error' in jdata:
|
|
|
|
return {'status': 'error', 'msg': 'DNSSEC is not enabled for this domain'}
|
|
|
|
else:
|
|
|
|
return {'status': 'ok', 'dnssec': jdata}
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Cannot get domain dnssec. DETAIL: {0}'.format(e))
|
2016-03-24 13:01:08 +00:00
|
|
|
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
|
|
|
|
else:
|
|
|
|
return {'status': 'error', 'msg': 'This domain doesnot exist'}
|
|
|
|
|
2018-03-01 07:26:29 +00:00
|
|
|
def enable_domain_dnssec(self, domain_name):
|
|
|
|
"""
|
|
|
|
Enable domain DNSSEC
|
|
|
|
"""
|
|
|
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
|
|
|
if domain:
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2018-03-01 07:26:29 +00:00
|
|
|
try:
|
2018-06-10 23:23:10 +00:00
|
|
|
# Enable API-RECTIFY for domain, BEFORE activating DNSSEC
|
|
|
|
post_data = {
|
|
|
|
"api_rectify": True
|
|
|
|
}
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain.name)), headers=headers, method='PUT', data=post_data)
|
2018-06-10 23:23:10 +00:00
|
|
|
if 'error' in jdata:
|
2019-03-01 22:49:31 +00:00
|
|
|
return {'status': 'error', 'msg': 'API-RECTIFY could not be enabled for this domain', 'jdata': jdata}
|
2018-06-10 23:23:10 +00:00
|
|
|
|
|
|
|
# Activate DNSSEC
|
|
|
|
post_data = {
|
|
|
|
"keytype": "ksk",
|
|
|
|
"active": True
|
|
|
|
}
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys'.format(domain.name)), headers=headers, method='POST', data=post_data)
|
2018-03-01 07:26:29 +00:00
|
|
|
if 'error' in jdata:
|
2019-03-01 22:49:31 +00:00
|
|
|
return {'status': 'error', 'msg': 'Cannot enable DNSSEC for this domain. Error: {0}'.format(jdata['error']), 'jdata': jdata}
|
2018-06-10 23:23:10 +00:00
|
|
|
|
|
|
|
return {'status': 'ok'}
|
|
|
|
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Cannot enable dns sec. DETAIL: {}'.format(e))
|
|
|
|
logging.debug(traceback.format_exc())
|
2018-03-01 07:26:29 +00:00
|
|
|
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
|
2018-06-10 23:23:10 +00:00
|
|
|
|
2018-03-01 07:26:29 +00:00
|
|
|
else:
|
2018-04-02 06:38:53 +00:00
|
|
|
return {'status': 'error', 'msg': 'This domain does not exist'}
|
2016-03-24 13:01:08 +00:00
|
|
|
|
2018-03-05 14:06:40 +00:00
|
|
|
def delete_dnssec_key(self, domain_name, key_id):
|
|
|
|
"""
|
|
|
|
Remove keys DNSSEC
|
|
|
|
"""
|
|
|
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
|
|
|
if domain:
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2018-03-05 14:06:40 +00:00
|
|
|
try:
|
2018-06-10 23:23:10 +00:00
|
|
|
# Deactivate DNSSEC
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}/cryptokeys/{1}'.format(domain.name, key_id)), headers=headers, method='DELETE')
|
2018-06-10 23:23:10 +00:00
|
|
|
if jdata != True:
|
2019-03-01 22:49:31 +00:00
|
|
|
return {'status': 'error', 'msg': 'Cannot disable DNSSEC for this domain. Error: {0}'.format(jdata['error']), 'jdata': jdata}
|
2018-06-10 23:23:10 +00:00
|
|
|
|
|
|
|
# Disable API-RECTIFY for domain, AFTER deactivating DNSSEC
|
|
|
|
post_data = {
|
|
|
|
"api_rectify": False
|
|
|
|
}
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain.name)), headers=headers, method='PUT', data=post_data)
|
2018-06-10 23:23:10 +00:00
|
|
|
if 'error' in jdata:
|
2019-03-01 22:49:31 +00:00
|
|
|
return {'status': 'error', 'msg': 'API-RECTIFY could not be disabled for this domain', 'jdata': jdata}
|
2018-06-10 23:23:10 +00:00
|
|
|
|
|
|
|
return {'status': 'ok'}
|
|
|
|
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Cannot delete dnssec key. DETAIL: {0}'.format(e))
|
|
|
|
logging.debug(traceback.format_exc())
|
2019-03-01 22:49:31 +00:00
|
|
|
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator', 'domain': domain.name, 'id': key_id}
|
2018-06-10 23:23:10 +00:00
|
|
|
|
2018-03-05 14:06:40 +00:00
|
|
|
else:
|
|
|
|
return {'status': 'error', 'msg': 'This domain doesnot exist'}
|
2016-03-24 13:01:08 +00:00
|
|
|
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
def assoc_account(self, account_id):
|
|
|
|
"""
|
|
|
|
Associate domain with a domain, specified by account id
|
|
|
|
"""
|
|
|
|
domain_name = self.name
|
|
|
|
|
|
|
|
# Sanity check - domain name
|
|
|
|
if domain_name == "":
|
|
|
|
return {'status': False, 'msg': 'No domain name specified'}
|
|
|
|
|
|
|
|
# read domain and check that it exists
|
|
|
|
domain = Domain.query.filter(Domain.name == domain_name).first()
|
|
|
|
if not domain:
|
|
|
|
return {'status': False, 'msg': 'Domain does not exist'}
|
|
|
|
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
|
|
|
|
account_name = Account().get_name_by_id(account_id)
|
|
|
|
|
|
|
|
post_data = {
|
|
|
|
"account": account_name
|
|
|
|
}
|
|
|
|
|
|
|
|
try:
|
|
|
|
jdata = utils.fetch_json(
|
2019-03-01 22:49:31 +00:00
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain_name)), headers=headers,
|
|
|
|
method='PUT', data=post_data)
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
|
|
|
|
if 'error' in jdata.keys():
|
|
|
|
logging.error(jdata['error'])
|
|
|
|
return {'status': 'error', 'msg': jdata['error']}
|
|
|
|
else:
|
|
|
|
self.update()
|
2019-03-01 22:49:31 +00:00
|
|
|
msg_str = 'Account changed for domain {0} successfully'
|
|
|
|
logging.info(msg_str.format(domain_name))
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
return {'status': 'ok', 'msg': 'account changed successfully'}
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
logging.debug(e)
|
|
|
|
logging.debug(traceback.format_exc())
|
2019-03-01 22:49:31 +00:00
|
|
|
msg_str = 'Cannot change account for domain {0}'
|
|
|
|
logging.error(msg_str.format(domain_name))
|
|
|
|
return {
|
|
|
|
'status': 'error',
|
|
|
|
'msg': 'Cannot change account for this domain.'
|
|
|
|
}
|
Initial support for Accounts
This adds initial support for accounts a concept meant to signify a customer, a department or any other entity that somehow owns or manages one or more domains.
The purpose is to be able to assign an account to any number of domains, making it easy to track who owns or manages a domain, significantly improving manageability in setups with a large number of domains.
An account consists of a mandatory, unique `name` and optional `description`, `contact` name and `mail` address. The account `name` is stripped of spaces and symbols, and lower cased before getting stored in the database and in PowerDNS, to help ensure some type of predictability and uniqueness in the database.
The term *account* is actually taken from the PowerDNS database, where the `domains.account` column is used to store the account relationship, in in the form of the account `name`.
The link to a domain in PowerDNS-Admin is done through the `domain.account_id` FOREIGN KEY, that is linked to the `account.id` PRIMARY KEY.
(cherry picked from commits 4e95f33dfb0676d1c401a033c28bca3be7d6ec26, da0d596bd019a339549e2c59630a8fdee65d0e22, 7f06e6aaf4fd8011c784f24b7bbbba5f52aef319, 1c624dad8749024033d1d15dd6242ca52b39f135)
2018-06-04 15:10:02 +00:00
|
|
|
|
|
|
|
def get_account(self):
|
|
|
|
"""
|
|
|
|
Get current account associated with this domain
|
|
|
|
"""
|
|
|
|
domain = Domain.query.filter(Domain.name == self.name).first()
|
|
|
|
|
|
|
|
return domain.account
|
|
|
|
|
2018-04-12 04:18:44 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
class DomainUser(db.Model):
|
|
|
|
__tablename__ = 'domain_user'
|
2019-03-01 22:49:31 +00:00
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
domain_id = db.Column(db.Integer, db.ForeignKey('domain.id'), nullable=False)
|
|
|
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def __init__(self, domain_id, user_id):
|
|
|
|
self.domain_id = domain_id
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
|
|
def __repr__(self):
|
2018-04-01 00:57:41 +00:00
|
|
|
return '<Domain_User {0} {1}>'.format(self.domain_id, self.user_id)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
|
2018-06-05 18:41:39 +00:00
|
|
|
class AccountUser(db.Model):
|
|
|
|
__tablename__ = 'account_user'
|
2019-03-01 22:49:31 +00:00
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
account_id = db.Column(db.Integer, db.ForeignKey('account.id'), nullable=False)
|
|
|
|
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
|
2018-06-05 18:41:39 +00:00
|
|
|
|
|
|
|
def __init__(self, account_id, user_id):
|
|
|
|
self.account_id = account_id
|
|
|
|
self.user_id = user_id
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return '<Account_User {0} {1}>'.format(self.account_id, self.user_id)
|
|
|
|
|
|
|
|
|
2019-03-28 07:25:07 +00:00
|
|
|
class RecordEntry(object):
|
|
|
|
"""
|
|
|
|
This is not a model, it's just an object
|
|
|
|
which will store records entries from PowerDNS API
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, name=None, type=None, status=None, ttl=None, data=None,
|
|
|
|
is_allowed_edit=False):
|
|
|
|
self.name = name
|
|
|
|
self.type = type
|
|
|
|
self.status = status
|
|
|
|
self.ttl = ttl
|
|
|
|
self.data = data
|
|
|
|
self._is_allowed_edit = is_allowed_edit
|
|
|
|
self._is_allowed_delete = is_allowed_edit and self.type != 'SOA'
|
|
|
|
|
|
|
|
def is_allowed_edit(self):
|
|
|
|
return self._is_allowed_edit
|
|
|
|
|
|
|
|
def is_allowed_delete(self):
|
|
|
|
return self._is_allowed_delete
|
|
|
|
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
class Record(object):
|
2019-03-01 22:49:31 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
"""
|
|
|
|
This is not a model, it's just an object
|
|
|
|
which be assigned data from PowerDNS API
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, name=None, type=None, status=None, ttl=None, data=None):
|
|
|
|
self.name = name
|
|
|
|
self.type = type
|
|
|
|
self.status = status
|
|
|
|
self.ttl = ttl
|
|
|
|
self.data = data
|
2018-08-20 02:59:19 +00:00
|
|
|
# PDNS configs
|
|
|
|
self.PDNS_STATS_URL = Setting().get('pdns_api_url')
|
|
|
|
self.PDNS_API_KEY = Setting().get('pdns_api_key')
|
|
|
|
self.PDNS_VERSION = Setting().get('pdns_version')
|
|
|
|
self.API_EXTENDED_URL = utils.pdns_api_extended_uri(self.PDNS_VERSION)
|
2018-08-21 06:26:27 +00:00
|
|
|
self.PRETTY_IPV6_PTR = Setting().get('pretty_ipv6_ptr')
|
2018-08-20 02:59:19 +00:00
|
|
|
|
|
|
|
if StrictVersion(self.PDNS_VERSION) >= StrictVersion('4.0.0'):
|
|
|
|
self.NEW_SCHEMA = True
|
|
|
|
else:
|
|
|
|
self.NEW_SCHEMA = False
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def get_record_data(self, domain):
|
|
|
|
"""
|
|
|
|
Query domain's DNS records via API
|
|
|
|
"""
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers)
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
"Cannot fetch domain's record data from remote powerdns api. DETAIL: {0}".format(e))
|
2015-12-13 09:34:12 +00:00
|
|
|
return False
|
2016-06-07 08:20:56 +00:00
|
|
|
|
2018-08-20 02:59:19 +00:00
|
|
|
if self.NEW_SCHEMA:
|
2016-06-07 08:20:56 +00:00
|
|
|
rrsets = jdata['rrsets']
|
|
|
|
for rrset in rrsets:
|
2016-08-19 23:04:20 +00:00
|
|
|
r_name = rrset['name'].rstrip('.')
|
2019-03-01 22:49:31 +00:00
|
|
|
if self.PRETTY_IPV6_PTR: # only if activated
|
|
|
|
if rrset['type'] == 'PTR': # only ptr
|
|
|
|
if 'ip6.arpa' in r_name: # only if v6-ptr
|
2016-08-19 23:07:36 +00:00
|
|
|
r_name = dns.reversename.to_address(dns.name.from_text(r_name))
|
2016-08-19 23:04:20 +00:00
|
|
|
|
|
|
|
rrset['name'] = r_name
|
2016-06-07 08:20:56 +00:00
|
|
|
rrset['content'] = rrset['records'][0]['content']
|
|
|
|
rrset['disabled'] = rrset['records'][0]['disabled']
|
|
|
|
return {'records': rrsets}
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
return jdata
|
|
|
|
|
|
|
|
def add(self, domain):
|
|
|
|
"""
|
|
|
|
Add a record to domain
|
|
|
|
"""
|
|
|
|
# validate record first
|
|
|
|
r = self.get_record_data(domain)
|
|
|
|
records = r['records']
|
2018-04-18 06:29:29 +00:00
|
|
|
check = list(filter(lambda check: check['name'] == self.name, records))
|
2015-12-13 09:34:12 +00:00
|
|
|
if check:
|
|
|
|
r = check[0]
|
2019-03-01 22:49:31 +00:00
|
|
|
if r['type'] in ('A', 'AAAA', 'CNAME'):
|
2016-07-01 19:41:41 +00:00
|
|
|
return {'status': 'error', 'msg': 'Record already exists with type "A", "AAAA" or "CNAME"'}
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
# continue if the record is ready to be added
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2016-06-07 10:05:41 +00:00
|
|
|
|
2018-08-20 02:59:19 +00:00
|
|
|
if self.NEW_SCHEMA:
|
2016-06-07 10:05:41 +00:00
|
|
|
data = {"rrsets": [
|
|
|
|
{
|
2016-11-17 14:04:07 +00:00
|
|
|
"name": self.name.rstrip('.') + '.',
|
2016-06-07 10:05:41 +00:00
|
|
|
"type": self.type,
|
|
|
|
"changetype": "REPLACE",
|
|
|
|
"ttl": self.ttl,
|
|
|
|
"records": [
|
|
|
|
{
|
|
|
|
"content": self.data,
|
|
|
|
"disabled": self.status,
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
2019-03-01 22:49:31 +00:00
|
|
|
}
|
2016-06-07 10:05:41 +00:00
|
|
|
else:
|
|
|
|
data = {"rrsets": [
|
|
|
|
{
|
|
|
|
"name": self.name,
|
|
|
|
"type": self.type,
|
|
|
|
"changetype": "REPLACE",
|
|
|
|
"records": [
|
|
|
|
{
|
|
|
|
"content": self.data,
|
|
|
|
"disabled": self.status,
|
|
|
|
"name": self.name,
|
|
|
|
"ttl": self.ttl,
|
|
|
|
"type": self.type
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
2019-03-01 22:49:31 +00:00
|
|
|
}
|
2016-06-07 10:05:41 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data)
|
2015-12-13 09:34:12 +00:00
|
|
|
logging.debug(jdata)
|
|
|
|
return {'status': 'ok', 'msg': 'Record was added successfully'}
|
2018-03-30 06:49:35 +00:00
|
|
|
except Exception as e:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error("Cannot add record {0}/{1}/{2} to domain {3}. DETAIL: {4}".format(
|
|
|
|
self.name, self.type, self.data, domain, e))
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
|
|
|
|
|
|
|
|
def compare(self, domain_name, new_records):
|
|
|
|
"""
|
|
|
|
Compare new records with current powerdns record data
|
|
|
|
Input is a list of hashes (records)
|
|
|
|
"""
|
|
|
|
# get list of current records we have in powerdns
|
|
|
|
current_records = self.get_record_data(domain_name)['records']
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
# convert them to list of list (just has [name, type]) instead of list of hash
|
|
|
|
# to compare easier
|
2019-03-01 22:49:31 +00:00
|
|
|
list_current_records = [[x['name'], x['type']] for x in current_records]
|
|
|
|
list_new_records = [[x['name'], x['type']] for x in new_records]
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
# get list of deleted records
|
|
|
|
# they are the records which exist in list_current_records but not in list_new_records
|
|
|
|
list_deleted_records = [x for x in list_current_records if x not in list_new_records]
|
|
|
|
|
|
|
|
# convert back to list of hash
|
2019-03-01 22:49:31 +00:00
|
|
|
deleted_records = [x for x in current_records if [x['name'], x['type']] in list_deleted_records and (
|
|
|
|
x['type'] in Setting().get_records_allow_to_edit() and x['type'] != 'SOA')]
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
# return a tuple
|
|
|
|
return deleted_records, new_records
|
|
|
|
|
|
|
|
def apply(self, domain, post_records):
|
|
|
|
"""
|
|
|
|
Apply record changes to domain
|
|
|
|
"""
|
2016-08-19 23:04:20 +00:00
|
|
|
records = []
|
|
|
|
for r in post_records:
|
|
|
|
r_name = domain if r['record_name'] in ['@', ''] else r['record_name'] + '.' + domain
|
|
|
|
r_type = r['record_type']
|
2019-03-01 22:49:31 +00:00
|
|
|
if self.PRETTY_IPV6_PTR: # only if activated
|
|
|
|
if self.NEW_SCHEMA: # only if new schema
|
|
|
|
if r_type == 'PTR': # only ptr
|
|
|
|
if ':' in r['record_name']: # dirty ipv6 check
|
2016-08-19 23:04:20 +00:00
|
|
|
r_name = r['record_name']
|
2018-03-01 07:26:29 +00:00
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
r_data = domain if r_type == 'CNAME' and r[
|
|
|
|
'record_data'] in ['@', ''] else r['record_data']
|
2018-11-24 12:45:14 +00:00
|
|
|
|
2016-08-19 23:04:20 +00:00
|
|
|
record = {
|
|
|
|
"name": r_name,
|
|
|
|
"type": r_type,
|
2018-09-02 13:18:33 +00:00
|
|
|
"content": r_data,
|
2016-08-19 23:04:20 +00:00
|
|
|
"disabled": True if r['record_status'] == 'Disabled' else False,
|
|
|
|
"ttl": int(r['record_ttl']) if r['record_ttl'] else 3600,
|
|
|
|
}
|
|
|
|
records.append(record)
|
2018-03-01 07:26:29 +00:00
|
|
|
|
2016-08-19 23:04:20 +00:00
|
|
|
deleted_records, new_records = self.compare(domain, records)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
records = []
|
|
|
|
for r in deleted_records:
|
2018-08-20 02:59:19 +00:00
|
|
|
r_name = r['name'].rstrip('.') + '.' if self.NEW_SCHEMA else r['name']
|
2016-08-19 23:04:20 +00:00
|
|
|
r_type = r['type']
|
2019-03-01 22:49:31 +00:00
|
|
|
if self.PRETTY_IPV6_PTR: # only if activated
|
|
|
|
if self.NEW_SCHEMA: # only if new schema
|
|
|
|
if r_type == 'PTR': # only ptr
|
|
|
|
if ':' in r['name']: # dirty ipv6 check
|
2016-08-19 23:04:20 +00:00
|
|
|
r_name = dns.reversename.from_address(r['name']).to_text()
|
2018-03-01 07:26:29 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
record = {
|
2016-08-19 23:04:20 +00:00
|
|
|
"name": r_name,
|
|
|
|
"type": r_type,
|
2015-12-13 09:34:12 +00:00
|
|
|
"changetype": "DELETE",
|
|
|
|
"records": [
|
|
|
|
]
|
|
|
|
}
|
|
|
|
records.append(record)
|
2016-06-28 17:22:11 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
postdata_for_delete = {"rrsets": records}
|
|
|
|
|
|
|
|
records = []
|
|
|
|
for r in new_records:
|
2018-08-20 02:59:19 +00:00
|
|
|
if self.NEW_SCHEMA:
|
2016-11-17 14:04:07 +00:00
|
|
|
r_name = r['name'].rstrip('.') + '.'
|
2016-08-19 23:04:20 +00:00
|
|
|
r_type = r['type']
|
2019-03-01 22:49:31 +00:00
|
|
|
if self.PRETTY_IPV6_PTR: # only if activated
|
|
|
|
if r_type == 'PTR': # only ptr
|
|
|
|
if ':' in r['name']: # dirty ipv6 check
|
2016-08-19 23:04:20 +00:00
|
|
|
r_name = r['name']
|
|
|
|
|
2016-06-07 10:05:41 +00:00
|
|
|
record = {
|
2016-08-19 23:04:20 +00:00
|
|
|
"name": r_name,
|
|
|
|
"type": r_type,
|
2016-06-07 10:05:41 +00:00
|
|
|
"changetype": "REPLACE",
|
2016-07-27 15:01:23 +00:00
|
|
|
"ttl": r['ttl'],
|
2016-06-07 10:05:41 +00:00
|
|
|
"records": [
|
|
|
|
{
|
|
|
|
"content": r['content'],
|
|
|
|
"disabled": r['disabled'],
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
else:
|
|
|
|
record = {
|
|
|
|
"name": r['name'],
|
|
|
|
"type": r['type'],
|
|
|
|
"changetype": "REPLACE",
|
|
|
|
"records": [
|
|
|
|
{
|
|
|
|
"content": r['content'],
|
|
|
|
"disabled": r['disabled'],
|
|
|
|
"name": r['name'],
|
|
|
|
"ttl": r['ttl'],
|
|
|
|
"type": r['type'],
|
2019-03-01 22:49:31 +00:00
|
|
|
"priority": 10, # priority field for pdns 3.4.1. https://doc.powerdns.com/md/authoritative/upgrading/
|
2016-06-07 10:05:41 +00:00
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
records.append(record)
|
2016-02-11 09:54:15 +00:00
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
# Adjustment to add multiple records which described in
|
|
|
|
# https://github.com/ngoduykhanh/PowerDNS-Admin/issues/5#issuecomment-181637576
|
2016-02-11 09:54:15 +00:00
|
|
|
final_records = []
|
2019-03-01 22:49:31 +00:00
|
|
|
records = sorted(records, key=lambda item: (
|
|
|
|
item["name"], item["type"], item["changetype"]))
|
2016-07-27 15:01:23 +00:00
|
|
|
for key, group in itertools.groupby(records, lambda item: (item["name"], item["type"], item["changetype"])):
|
2018-08-20 02:59:19 +00:00
|
|
|
if self.NEW_SCHEMA:
|
2016-08-19 23:04:20 +00:00
|
|
|
r_name = key[0]
|
|
|
|
r_type = key[1]
|
|
|
|
r_changetype = key[2]
|
2018-03-01 07:26:29 +00:00
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
if self.PRETTY_IPV6_PTR: # only if activated
|
|
|
|
if r_type == 'PTR': # only ptr
|
|
|
|
if ':' in r_name: # dirty ipv6 check
|
2016-08-19 23:04:20 +00:00
|
|
|
r_name = dns.reversename.from_address(r_name).to_text()
|
2018-03-01 07:26:29 +00:00
|
|
|
|
2016-06-08 04:00:55 +00:00
|
|
|
new_record = {
|
2016-08-19 23:04:20 +00:00
|
|
|
"name": r_name,
|
|
|
|
"type": r_type,
|
|
|
|
"changetype": r_changetype,
|
2016-07-27 15:01:23 +00:00
|
|
|
"ttl": None,
|
2016-06-08 04:00:55 +00:00
|
|
|
"records": []
|
2019-03-01 22:49:31 +00:00
|
|
|
}
|
2016-06-08 04:00:55 +00:00
|
|
|
for item in group:
|
|
|
|
temp_content = item['records'][0]['content']
|
|
|
|
temp_disabled = item['records'][0]['disabled']
|
|
|
|
if key[1] in ['MX', 'CNAME', 'SRV', 'NS']:
|
|
|
|
if temp_content.strip()[-1:] != '.':
|
|
|
|
temp_content += '.'
|
|
|
|
|
2016-07-27 15:01:23 +00:00
|
|
|
if new_record['ttl'] is None:
|
|
|
|
new_record['ttl'] = item['ttl']
|
2016-06-08 04:00:55 +00:00
|
|
|
new_record['records'].append({
|
|
|
|
"content": temp_content,
|
|
|
|
"disabled": temp_disabled
|
|
|
|
})
|
|
|
|
final_records.append(new_record)
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2016-07-26 18:34:56 +00:00
|
|
|
else:
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2016-06-07 10:05:41 +00:00
|
|
|
final_records.append({
|
|
|
|
"name": key[0],
|
|
|
|
"type": key[1],
|
2016-07-26 18:34:56 +00:00
|
|
|
"changetype": key[2],
|
2016-06-07 10:05:41 +00:00
|
|
|
"records": [
|
|
|
|
{
|
|
|
|
"content": item['records'][0]['content'],
|
|
|
|
"disabled": item['records'][0]['disabled'],
|
|
|
|
"name": key[0],
|
|
|
|
"ttl": item['records'][0]['ttl'],
|
|
|
|
"type": key[1],
|
|
|
|
"priority": 10,
|
|
|
|
} for item in group
|
|
|
|
]
|
2019-03-01 22:49:31 +00:00
|
|
|
})
|
2016-02-11 09:54:15 +00:00
|
|
|
|
|
|
|
postdata_for_new = {"rrsets": final_records}
|
2018-08-18 15:42:18 +00:00
|
|
|
logging.debug(postdata_for_new)
|
|
|
|
logging.debug(postdata_for_delete)
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.info(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)))
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata1 = utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(
|
|
|
|
domain)), headers=headers, method='PATCH', data=postdata_for_delete)
|
|
|
|
jdata2 = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=postdata_for_new)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2018-09-06 04:35:54 +00:00
|
|
|
if 'error' in jdata1.keys():
|
|
|
|
logging.error('Cannot apply record changes.')
|
|
|
|
logging.debug(jdata1['error'])
|
|
|
|
return {'status': 'error', 'msg': jdata1['error']}
|
|
|
|
elif 'error' in jdata2.keys():
|
2015-12-13 09:34:12 +00:00
|
|
|
logging.error('Cannot apply record changes.')
|
|
|
|
logging.debug(jdata2['error'])
|
|
|
|
return {'status': 'error', 'msg': jdata2['error']}
|
|
|
|
else:
|
2016-11-17 10:35:09 +00:00
|
|
|
self.auto_ptr(domain, new_records, deleted_records)
|
2018-04-12 04:18:44 +00:00
|
|
|
self.update_db_serial(domain)
|
2015-12-13 09:34:12 +00:00
|
|
|
logging.info('Record was applied successfully.')
|
|
|
|
return {'status': 'ok', 'msg': 'Record was applied successfully'}
|
2018-03-30 06:49:35 +00:00
|
|
|
except Exception as e:
|
2018-08-18 15:42:18 +00:00
|
|
|
logging.error("Cannot apply record changes to domain {0}. Error: {1}".format(domain, e))
|
|
|
|
logging.debug(traceback.format_exc())
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
|
|
|
|
|
2016-11-17 10:35:09 +00:00
|
|
|
def auto_ptr(self, domain, new_records, deleted_records):
|
2016-11-21 12:46:54 +00:00
|
|
|
"""
|
|
|
|
Add auto-ptr records
|
|
|
|
"""
|
|
|
|
domain_obj = Domain.query.filter(Domain.name == domain).first()
|
2019-03-01 22:49:31 +00:00
|
|
|
domain_auto_ptr = DomainSetting.query.filter(
|
|
|
|
DomainSetting.domain == domain_obj).filter(DomainSetting.setting == 'auto_ptr').first()
|
2016-11-21 12:46:54 +00:00
|
|
|
domain_auto_ptr = strtobool(domain_auto_ptr.value) if domain_auto_ptr else False
|
2016-11-21 15:52:54 +00:00
|
|
|
|
2018-08-18 15:42:18 +00:00
|
|
|
system_auto_ptr = Setting().get('auto_ptr')
|
2016-11-21 18:44:10 +00:00
|
|
|
|
2016-11-21 12:46:54 +00:00
|
|
|
if system_auto_ptr or domain_auto_ptr:
|
2016-11-17 10:35:09 +00:00
|
|
|
try:
|
2016-11-16 14:15:35 +00:00
|
|
|
d = Domain()
|
2016-11-16 13:12:40 +00:00
|
|
|
for r in new_records:
|
|
|
|
if r['type'] in ['A', 'AAAA']:
|
2016-11-16 14:15:35 +00:00
|
|
|
r_name = r['name'] + '.'
|
2016-11-16 13:12:40 +00:00
|
|
|
r_content = r['content']
|
2016-11-21 18:44:10 +00:00
|
|
|
reverse_host_address = dns.reversename.from_address(r_content).to_text()
|
|
|
|
domain_reverse_name = d.get_reverse_domain_name(reverse_host_address)
|
2016-11-16 13:12:40 +00:00
|
|
|
d.create_reverse_domain(domain, domain_reverse_name)
|
2016-11-17 14:04:07 +00:00
|
|
|
self.name = dns.reversename.from_address(r_content).to_text().rstrip('.')
|
2016-11-16 14:15:35 +00:00
|
|
|
self.type = 'PTR'
|
|
|
|
self.status = r['disabled']
|
|
|
|
self.ttl = r['ttl']
|
|
|
|
self.data = r_name
|
|
|
|
self.add(domain_reverse_name)
|
2016-11-17 10:37:09 +00:00
|
|
|
for r in deleted_records:
|
|
|
|
if r['type'] in ['A', 'AAAA']:
|
|
|
|
r_content = r['content']
|
2016-11-21 18:44:10 +00:00
|
|
|
reverse_host_address = dns.reversename.from_address(r_content).to_text()
|
|
|
|
domain_reverse_name = d.get_reverse_domain_name(reverse_host_address)
|
|
|
|
self.name = reverse_host_address
|
2016-11-17 10:37:09 +00:00
|
|
|
self.type = 'PTR'
|
|
|
|
self.data = r_content
|
|
|
|
self.delete(domain_reverse_name)
|
|
|
|
return {'status': 'ok', 'msg': 'Auto-PTR record was updated successfully'}
|
|
|
|
except Exception as e:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error(
|
|
|
|
"Cannot update auto-ptr record changes to domain {0}. DETAIL: {1}".format(domain, e))
|
2016-11-17 10:37:09 +00:00
|
|
|
return {'status': 'error', 'msg': 'Auto-PTR creation failed. There was something wrong, please contact administrator.'}
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def delete(self, domain):
|
|
|
|
"""
|
|
|
|
Delete a record from domain
|
|
|
|
"""
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2015-12-13 09:34:12 +00:00
|
|
|
data = {"rrsets": [
|
|
|
|
{
|
2016-11-17 14:04:07 +00:00
|
|
|
"name": self.name.rstrip('.') + '.',
|
2015-12-13 09:34:12 +00:00
|
|
|
"type": self.type,
|
|
|
|
"changetype": "DELETE",
|
2016-08-23 02:52:35 +00:00
|
|
|
"records": [
|
2015-12-13 09:34:12 +00:00
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
2019-03-01 22:49:31 +00:00
|
|
|
}
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data)
|
2015-12-13 09:34:12 +00:00
|
|
|
logging.debug(jdata)
|
|
|
|
return {'status': 'ok', 'msg': 'Record was removed successfully'}
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error("Cannot remove record {0}/{1}/{2} from domain {3}. DETAIL: {4}".format(
|
|
|
|
self.name, self.type, self.data, domain, e))
|
2015-12-13 09:34:12 +00:00
|
|
|
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
|
|
|
|
|
2018-01-23 09:08:50 +00:00
|
|
|
def is_allowed_edit(self):
|
2015-12-13 09:34:12 +00:00
|
|
|
"""
|
2018-01-23 09:08:50 +00:00
|
|
|
Check if record is allowed to edit
|
2015-12-13 09:34:12 +00:00
|
|
|
"""
|
2018-08-22 10:57:12 +00:00
|
|
|
return self.type in Setting().get_records_allow_to_edit()
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2018-01-23 09:08:50 +00:00
|
|
|
def is_allowed_delete(self):
|
|
|
|
"""
|
|
|
|
Check if record is allowed to removed
|
|
|
|
"""
|
2018-08-22 10:57:12 +00:00
|
|
|
return (self.type in Setting().get_records_allow_to_edit() and self.type != 'SOA')
|
2018-01-23 09:08:50 +00:00
|
|
|
|
2016-06-20 09:32:14 +00:00
|
|
|
def exists(self, domain):
|
|
|
|
"""
|
|
|
|
Check if record is present within domain records, and if it's present set self to found record
|
|
|
|
"""
|
|
|
|
jdata = self.get_record_data(domain)
|
|
|
|
jrecords = jdata['records']
|
|
|
|
|
|
|
|
for jr in jrecords:
|
2019-02-02 12:28:56 +00:00
|
|
|
if jr['name'] == self.name and jr['type'] == self.type:
|
2016-06-20 09:32:14 +00:00
|
|
|
self.name = jr['name']
|
|
|
|
self.type = jr['type']
|
|
|
|
self.status = jr['disabled']
|
|
|
|
self.ttl = jr['ttl']
|
|
|
|
self.data = jr['content']
|
|
|
|
self.priority = 10
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def update(self, domain, content):
|
|
|
|
"""
|
|
|
|
Update single record
|
|
|
|
"""
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2016-06-20 09:32:14 +00:00
|
|
|
|
2018-08-20 02:59:19 +00:00
|
|
|
if self.NEW_SCHEMA:
|
2016-06-20 09:32:14 +00:00
|
|
|
data = {"rrsets": [
|
|
|
|
{
|
|
|
|
"name": self.name + '.',
|
|
|
|
"type": self.type,
|
|
|
|
"ttl": self.ttl,
|
|
|
|
"changetype": "REPLACE",
|
|
|
|
"records": [
|
|
|
|
{
|
|
|
|
"content": content,
|
|
|
|
"disabled": self.status,
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
2019-03-01 22:49:31 +00:00
|
|
|
}
|
2016-06-20 09:32:14 +00:00
|
|
|
else:
|
|
|
|
data = {"rrsets": [
|
|
|
|
{
|
|
|
|
"name": self.name,
|
|
|
|
"type": self.type,
|
|
|
|
"changetype": "REPLACE",
|
|
|
|
"records": [
|
|
|
|
{
|
|
|
|
"content": content,
|
|
|
|
"disabled": self.status,
|
|
|
|
"name": self.name,
|
|
|
|
"ttl": self.ttl,
|
|
|
|
"type": self.type,
|
|
|
|
"priority": 10
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
2019-03-01 22:49:31 +00:00
|
|
|
}
|
2016-06-20 09:32:14 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
utils.fetch_json(urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL +
|
|
|
|
'/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='PATCH', data=data)
|
2018-04-01 00:57:41 +00:00
|
|
|
logging.debug("dyndns data: {0}".format(data))
|
2016-06-20 09:32:14 +00:00
|
|
|
return {'status': 'ok', 'msg': 'Record was updated successfully'}
|
2018-03-30 06:49:35 +00:00
|
|
|
except Exception as e:
|
2019-03-01 22:49:31 +00:00
|
|
|
logging.error("Cannot add record {0}/{1}/{2} to domain {3}. DETAIL: {4}".format(
|
|
|
|
self.name, self.type, self.data, domain, e))
|
2016-06-20 09:32:14 +00:00
|
|
|
return {'status': 'error', 'msg': 'There was something wrong, please contact administrator'}
|
|
|
|
|
2018-04-12 04:18:44 +00:00
|
|
|
def update_db_serial(self, domain):
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/localhost/zones/{0}'.format(domain)), headers=headers, method='GET')
|
2018-04-12 04:18:44 +00:00
|
|
|
serial = jdata['serial']
|
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
domain = Domain.query.filter(Domain.name == domain).first()
|
2018-04-12 04:18:44 +00:00
|
|
|
if domain:
|
|
|
|
domain.serial = serial
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': True, 'msg': 'Synced local serial for domain name {0}'.format(domain)}
|
|
|
|
else:
|
|
|
|
return {'status': False, 'msg': 'Could not find domain name {0} in local db'.format(domain)}
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
class Server(object):
|
2019-03-01 22:49:31 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
"""
|
|
|
|
This is not a model, it's just an object
|
|
|
|
which be assigned data from PowerDNS API
|
|
|
|
"""
|
|
|
|
|
|
|
|
def __init__(self, server_id=None, server_config=None):
|
|
|
|
self.server_id = server_id
|
|
|
|
self.server_config = server_config
|
2018-08-20 02:59:19 +00:00
|
|
|
# PDNS configs
|
|
|
|
self.PDNS_STATS_URL = Setting().get('pdns_api_url')
|
|
|
|
self.PDNS_API_KEY = Setting().get('pdns_api_key')
|
|
|
|
self.PDNS_VERSION = Setting().get('pdns_version')
|
|
|
|
self.API_EXTENDED_URL = utils.pdns_api_extended_uri(self.PDNS_VERSION)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def get_config(self):
|
|
|
|
"""
|
|
|
|
Get server config
|
|
|
|
"""
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/{0}/config'.format(self.server_id)), headers=headers, method='GET')
|
2015-12-13 09:34:12 +00:00
|
|
|
return jdata
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error("Can not get server configuration. DETAIL: {0}".format(e))
|
2015-12-13 09:34:12 +00:00
|
|
|
logging.debug(traceback.format_exc())
|
|
|
|
return []
|
|
|
|
|
|
|
|
def get_statistic(self):
|
|
|
|
"""
|
|
|
|
Get server statistics
|
|
|
|
"""
|
|
|
|
headers = {}
|
2018-08-20 02:59:19 +00:00
|
|
|
headers['X-API-Key'] = self.PDNS_API_KEY
|
2016-06-07 06:50:31 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
2019-03-01 22:49:31 +00:00
|
|
|
jdata = utils.fetch_json(
|
|
|
|
urljoin(self.PDNS_STATS_URL, self.API_EXTENDED_URL + '/servers/{0}/statistics'.format(self.server_id)), headers=headers, method='GET')
|
2015-12-13 09:34:12 +00:00
|
|
|
return jdata
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error("Can not get server statistics. DETAIL: {0}".format(e))
|
2015-12-13 09:34:12 +00:00
|
|
|
logging.debug(traceback.format_exc())
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
class History(db.Model):
|
2019-03-01 22:49:31 +00:00
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
2015-12-13 09:34:12 +00:00
|
|
|
msg = db.Column(db.String(256))
|
2018-06-11 03:58:47 +00:00
|
|
|
# detail = db.Column(db.Text().with_variant(db.Text(length=2**24-2), 'mysql'))
|
|
|
|
detail = db.Column(db.Text())
|
2015-12-13 09:34:12 +00:00
|
|
|
created_by = db.Column(db.String(128))
|
|
|
|
created_on = db.Column(db.DateTime, default=datetime.utcnow)
|
|
|
|
|
|
|
|
def __init__(self, id=None, msg=None, detail=None, created_by=None):
|
|
|
|
self.id = id
|
|
|
|
self.msg = msg
|
|
|
|
self.detail = detail
|
|
|
|
self.created_by = created_by
|
|
|
|
|
|
|
|
def __repr__(self):
|
2018-04-01 00:57:41 +00:00
|
|
|
return '<History {0}>'.format(self.msg)
|
2015-12-13 09:34:12 +00:00
|
|
|
|
|
|
|
def add(self):
|
|
|
|
"""
|
|
|
|
Add an event to history table
|
|
|
|
"""
|
|
|
|
h = History()
|
|
|
|
h.msg = self.msg
|
|
|
|
h.detail = self.detail
|
|
|
|
h.created_by = self.created_by
|
|
|
|
db.session.add(h)
|
|
|
|
db.session.commit()
|
|
|
|
|
|
|
|
def remove_all(self):
|
|
|
|
"""
|
|
|
|
Remove all history from DB
|
|
|
|
"""
|
|
|
|
try:
|
2018-08-31 11:00:41 +00:00
|
|
|
db.session.query(History).delete()
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.commit()
|
|
|
|
logging.info("Removed all history")
|
|
|
|
return True
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.rollback()
|
2018-08-31 11:00:41 +00:00
|
|
|
logging.error("Cannot remove history. DETAIL: {0}".format(e))
|
2015-12-13 09:34:12 +00:00
|
|
|
logging.debug(traceback.format_exc())
|
|
|
|
return False
|
|
|
|
|
2019-03-01 22:49:31 +00:00
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
class Setting(db.Model):
|
2019-03-01 22:49:31 +00:00
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
2015-12-13 09:34:12 +00:00
|
|
|
name = db.Column(db.String(64))
|
2018-08-22 01:36:53 +00:00
|
|
|
value = db.Column(db.Text())
|
2015-12-13 09:34:12 +00:00
|
|
|
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
defaults = {
|
2018-08-18 11:41:59 +00:00
|
|
|
'maintenance': False,
|
|
|
|
'fullscreen_layout': True,
|
|
|
|
'record_helper': True,
|
|
|
|
'login_ldap_first': True,
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
'default_record_table_size': 15,
|
|
|
|
'default_domain_table_size': 10,
|
2018-08-18 11:41:59 +00:00
|
|
|
'auto_ptr': False,
|
2018-09-02 01:03:01 +00:00
|
|
|
'record_quick_edit': True,
|
2018-08-21 06:26:27 +00:00
|
|
|
'pretty_ipv6_ptr': False,
|
|
|
|
'dnssec_admins_only': False,
|
2018-09-01 10:53:05 +00:00
|
|
|
'allow_user_create_domain': False,
|
2018-08-21 06:26:27 +00:00
|
|
|
'bg_domain_updates': False,
|
2018-08-18 15:42:18 +00:00
|
|
|
'site_name': 'PowerDNS-Admin',
|
2018-10-01 17:15:09 +00:00
|
|
|
'session_timeout': 10,
|
2018-08-18 11:41:59 +00:00
|
|
|
'pdns_api_url': '',
|
|
|
|
'pdns_api_key': '',
|
|
|
|
'pdns_version': '4.1.1',
|
|
|
|
'local_db_enabled': True,
|
|
|
|
'signup_enabled': True,
|
|
|
|
'ldap_enabled': False,
|
|
|
|
'ldap_type': 'ldap',
|
|
|
|
'ldap_uri': '',
|
2018-08-18 15:42:18 +00:00
|
|
|
'ldap_base_dn': '',
|
2018-08-18 11:41:59 +00:00
|
|
|
'ldap_admin_username': '',
|
|
|
|
'ldap_admin_password': '',
|
|
|
|
'ldap_filter_basic': '',
|
|
|
|
'ldap_filter_username': '',
|
|
|
|
'ldap_sg_enabled': False,
|
2018-08-31 04:57:06 +00:00
|
|
|
'ldap_admin_group': '',
|
|
|
|
'ldap_operator_group': '',
|
|
|
|
'ldap_user_group': '',
|
2018-09-12 15:28:05 +00:00
|
|
|
'ldap_domain': '',
|
2018-08-18 11:41:59 +00:00
|
|
|
'github_oauth_enabled': False,
|
|
|
|
'github_oauth_key': '',
|
|
|
|
'github_oauth_secret': '',
|
|
|
|
'github_oauth_scope': 'email',
|
|
|
|
'github_oauth_api_url': 'https://api.github.com/user',
|
|
|
|
'github_oauth_token_url': 'https://github.com/login/oauth/access_token',
|
|
|
|
'github_oauth_authorize_url': 'https://github.com/login/oauth/authorize',
|
|
|
|
'google_oauth_enabled': False,
|
2019-03-01 22:49:31 +00:00
|
|
|
'google_oauth_client_id': '',
|
|
|
|
'google_oauth_client_secret': '',
|
2018-10-22 01:33:46 +00:00
|
|
|
'google_token_url': 'https://oauth2.googleapis.com/token',
|
|
|
|
'google_oauth_scope': 'openid email profile',
|
2019-03-01 22:49:31 +00:00
|
|
|
'google_authorize_url': 'https://accounts.google.com/o/oauth2/v2/auth',
|
|
|
|
'google_base_url': 'https://www.googleapis.com/oauth2/v3/',
|
2018-10-21 22:38:12 +00:00
|
|
|
'oidc_oauth_enabled': False,
|
|
|
|
'oidc_oauth_key': '',
|
|
|
|
'oidc_oauth_secret': '',
|
|
|
|
'oidc_oauth_scope': 'email',
|
|
|
|
'oidc_oauth_api_url': '',
|
|
|
|
'oidc_oauth_token_url': '',
|
|
|
|
'oidc_oauth_authorize_url': '',
|
2019-03-23 02:21:39 +00:00
|
|
|
'forward_records_allow_edit': {'A': True, 'AAAA': True, 'AFSDB': False, 'ALIAS': False, 'CAA': True, 'CERT': False, 'CDNSKEY': False, 'CDS': False, 'CNAME': True, 'DNSKEY': False, 'DNAME': False, 'DS': False, 'HINFO': False, 'KEY': False, 'LOC': True, 'LUA': False, 'MX': True, 'NAPTR': False, 'NS': True, 'NSEC': False, 'NSEC3': False, 'NSEC3PARAM': False, 'OPENPGPKEY': False, 'PTR': True, 'RP': False, 'RRSIG': False, 'SOA': False, 'SPF': True, 'SSHFP': False, 'SRV': True, 'TKEY': False, 'TSIG': False, 'TLSA': False, 'SMIMEA': False, 'TXT': True, 'URI': False},
|
|
|
|
'reverse_records_allow_edit': {'A': False, 'AAAA': False, 'AFSDB': False, 'ALIAS': False, 'CAA': False, 'CERT': False, 'CDNSKEY': False, 'CDS': False, 'CNAME': False, 'DNSKEY': False, 'DNAME': False, 'DS': False, 'HINFO': False, 'KEY': False, 'LOC': True, 'LUA': False, 'MX': False, 'NAPTR': False, 'NS': True, 'NSEC': False, 'NSEC3': False, 'NSEC3PARAM': False, 'OPENPGPKEY': False, 'PTR': True, 'RP': False, 'RRSIG': False, 'SOA': False, 'SPF': False, 'SSHFP': False, 'SRV': False, 'TKEY': False, 'TSIG': False, 'TLSA': False, 'SMIMEA': False, 'TXT': True, 'URI': False},
|
2018-11-24 12:45:14 +00:00
|
|
|
'ttl_options': '1 minute,5 minutes,30 minutes,60 minutes,24 hours',
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
}
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
def __init__(self, id=None, name=None, value=None):
|
|
|
|
self.id = id
|
|
|
|
self.name = name
|
|
|
|
self.value = value
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2016-04-11 09:40:44 +00:00
|
|
|
# allow database autoincrement to do its own ID assignments
|
|
|
|
def __init__(self, name=None, value=None):
|
|
|
|
self.id = None
|
|
|
|
self.name = name
|
2016-08-23 02:52:35 +00:00
|
|
|
self.value = value
|
2015-12-13 09:34:12 +00:00
|
|
|
|
2018-06-22 00:03:25 +00:00
|
|
|
def set_maintenance(self, mode):
|
2019-03-01 22:49:31 +00:00
|
|
|
maintenance = Setting.query.filter(Setting.name == 'maintenance').first()
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
|
|
|
|
if maintenance is None:
|
|
|
|
value = self.defaults['maintenance']
|
2018-08-18 11:41:59 +00:00
|
|
|
maintenance = Setting(name='maintenance', value=str(value))
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
db.session.add(maintenance)
|
|
|
|
|
|
|
|
mode = str(mode)
|
|
|
|
|
2015-12-13 09:34:12 +00:00
|
|
|
try:
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
if maintenance.value != mode:
|
|
|
|
maintenance.value = mode
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.commit()
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
return True
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Cannot set maintenance to {0}. DETAIL: {1}'.format(mode, e))
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
logging.debug(traceback.format_exec())
|
2015-12-13 09:34:12 +00:00
|
|
|
db.session.rollback()
|
|
|
|
return False
|
|
|
|
|
2016-04-29 21:36:37 +00:00
|
|
|
def toggle(self, setting):
|
2019-03-01 22:49:31 +00:00
|
|
|
current_setting = Setting.query.filter(Setting.name == setting).first()
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
|
|
|
|
if current_setting is None:
|
|
|
|
value = self.defaults[setting]
|
2018-08-18 11:41:59 +00:00
|
|
|
current_setting = Setting(name=setting, value=str(value))
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
db.session.add(current_setting)
|
|
|
|
|
2016-04-29 21:36:37 +00:00
|
|
|
try:
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
if current_setting.value == "True":
|
|
|
|
current_setting.value = "False"
|
2016-04-29 21:36:37 +00:00
|
|
|
else:
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
current_setting.value = "True"
|
|
|
|
db.session.commit()
|
|
|
|
return True
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Cannot toggle setting {0}. DETAIL: {1}'.format(setting, e))
|
2016-04-29 21:36:37 +00:00
|
|
|
logging.debug(traceback.format_exec())
|
|
|
|
db.session.rollback()
|
2016-06-09 01:23:08 +00:00
|
|
|
return False
|
2016-08-23 02:52:35 +00:00
|
|
|
|
2016-06-09 01:23:08 +00:00
|
|
|
def set(self, setting, value):
|
2019-03-01 22:49:31 +00:00
|
|
|
current_setting = Setting.query.filter(Setting.name == setting).first()
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
|
|
|
|
if current_setting is None:
|
|
|
|
current_setting = Setting(name=setting, value=None)
|
|
|
|
db.session.add(current_setting)
|
|
|
|
|
|
|
|
value = str(value)
|
|
|
|
|
2016-06-09 01:23:08 +00:00
|
|
|
try:
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
current_setting.value = value
|
|
|
|
db.session.commit()
|
|
|
|
return True
|
2018-08-31 11:00:41 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Cannot edit setting {0}. DETAIL: {1}'.format(setting, e))
|
2016-06-09 01:23:08 +00:00
|
|
|
logging.debug(traceback.format_exec())
|
|
|
|
db.session.rollback()
|
2016-07-01 19:41:41 +00:00
|
|
|
return False
|
2018-01-22 15:22:19 +00:00
|
|
|
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
def get(self, setting):
|
|
|
|
if setting in self.defaults:
|
|
|
|
result = self.query.filter(Setting.name == setting).first()
|
|
|
|
if result is not None:
|
2018-08-18 11:41:59 +00:00
|
|
|
return strtobool(result.value) if result.value in ['True', 'False'] else result.value
|
Move setting definitions into code (rather than database).
For a setting to be useful, the code has to be able to make sense of it anyway. For this reason it makes sense, that the available settings are defined within the code, rather than in the database, where a missing row has previously caused problems. Instead, settings are now written to the database, when they are changed.
So instead of relying on the database initialization process to create all available settings for us in the database, the supported settings and their defaults are now in a `defaults` dict in the Setting class. With this in place, we can stop populating the `setting` table as a part of database initialization and it will be much easier to support new settings in the future (we no longer need to do anything to the database, to achieve that).
Another benefit is that any changes to default values will take effect automatically, unless the admin has already modified that setting to his/her liking.
To make it easier to get the value of a setting, falling back to defaults etc, a new function `get` has been added to the Setting class. Call it as `Setting().get('setting_name'), and it will take care of returning a setting from the database or return the default value for that setting, if nothing was found.
The `get` function returns `None`, if the setting passed to the function, does not exist in the `Setting.defaults` dict - Indicating that we don't know of a setting by that name.
2018-06-21 23:56:51 +00:00
|
|
|
else:
|
|
|
|
return self.defaults[setting]
|
|
|
|
else:
|
|
|
|
logging.error('Unknown setting queried: {0}'.format(setting))
|
|
|
|
|
2018-08-22 01:36:53 +00:00
|
|
|
def get_records_allow_to_edit(self):
|
|
|
|
return list(set(self.get_forward_records_allow_to_edit() + self.get_reverse_records_allow_to_edit()))
|
|
|
|
|
|
|
|
def get_forward_records_allow_to_edit(self):
|
2018-09-03 10:27:09 +00:00
|
|
|
records = self.get('forward_records_allow_edit')
|
|
|
|
f_records = literal_eval(records) if isinstance(records, str) else records
|
2018-09-04 01:57:41 +00:00
|
|
|
r_name = [r for r in f_records if f_records[r]]
|
|
|
|
# Sort alphabetically if python version is smaller than 3.6
|
|
|
|
if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 6):
|
|
|
|
r_name.sort()
|
|
|
|
return r_name
|
2018-08-22 01:36:53 +00:00
|
|
|
|
|
|
|
def get_reverse_records_allow_to_edit(self):
|
2018-09-03 10:27:09 +00:00
|
|
|
records = self.get('reverse_records_allow_edit')
|
|
|
|
r_records = literal_eval(records) if isinstance(records, str) else records
|
2018-09-04 01:57:41 +00:00
|
|
|
r_name = [r for r in r_records if r_records[r]]
|
|
|
|
# Sort alphabetically if python version is smaller than 3.6
|
|
|
|
if sys.version_info[0] < 3 or (sys.version_info[0] == 3 and sys.version_info[1] < 6):
|
|
|
|
r_name.sort()
|
|
|
|
return r_name
|
2018-08-18 11:41:59 +00:00
|
|
|
|
2018-11-24 12:45:14 +00:00
|
|
|
def get_ttl_options(self):
|
2019-03-01 22:49:31 +00:00
|
|
|
return [(pytimeparse.parse(ttl), ttl) for ttl in self.get('ttl_options').split(',')]
|
2018-11-24 12:45:14 +00:00
|
|
|
|
2018-01-22 15:22:19 +00:00
|
|
|
|
|
|
|
class DomainTemplate(db.Model):
|
|
|
|
__tablename__ = "domain_template"
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
name = db.Column(db.String(255), index=True, unique=True)
|
|
|
|
description = db.Column(db.String(255))
|
2019-03-01 22:49:31 +00:00
|
|
|
records = db.relationship(
|
|
|
|
'DomainTemplateRecord', back_populates='template', cascade="all, delete-orphan")
|
2018-01-22 15:22:19 +00:00
|
|
|
|
|
|
|
def __repr__(self):
|
2018-04-01 00:57:41 +00:00
|
|
|
return '<DomainTemplate {0}>'.format(self.name)
|
2018-01-22 15:22:19 +00:00
|
|
|
|
|
|
|
def __init__(self, name=None, description=None):
|
|
|
|
self.id = None
|
|
|
|
self.name = name
|
|
|
|
self.description = description
|
|
|
|
|
|
|
|
def replace_records(self, records):
|
|
|
|
try:
|
|
|
|
self.records = []
|
|
|
|
for record in records:
|
|
|
|
self.records.append(record)
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': 'ok', 'msg': 'Template records have been modified'}
|
2018-03-31 01:21:02 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Cannot create template records Error: {0}'.format(e))
|
2018-01-22 15:22:19 +00:00
|
|
|
db.session.rollback()
|
|
|
|
return {'status': 'error', 'msg': 'Can not create template records'}
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
try:
|
|
|
|
db.session.add(self)
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': 'ok', 'msg': 'Template has been created'}
|
2018-03-31 01:21:02 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Can not update domain template table. Error: {0}'.format(e))
|
2018-01-22 15:22:19 +00:00
|
|
|
db.session.rollback()
|
|
|
|
return {'status': 'error', 'msg': 'Can not update domain template table'}
|
|
|
|
|
|
|
|
def delete_template(self):
|
|
|
|
try:
|
|
|
|
self.records = []
|
|
|
|
db.session.delete(self)
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': 'ok', 'msg': 'Template has been deleted'}
|
2018-03-31 01:21:02 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Can not delete domain template. Error: {0}'.format(e))
|
2018-01-22 15:22:19 +00:00
|
|
|
db.session.rollback()
|
|
|
|
return {'status': 'error', 'msg': 'Can not delete domain template'}
|
|
|
|
|
|
|
|
|
|
|
|
class DomainTemplateRecord(db.Model):
|
|
|
|
__tablename__ = "domain_template_record"
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
name = db.Column(db.String(255))
|
|
|
|
type = db.Column(db.String(64))
|
|
|
|
ttl = db.Column(db.Integer)
|
2018-04-12 04:44:56 +00:00
|
|
|
data = db.Column(db.Text)
|
2018-01-22 15:22:19 +00:00
|
|
|
status = db.Column(db.Boolean)
|
|
|
|
template_id = db.Column(db.Integer, db.ForeignKey('domain_template.id'))
|
|
|
|
template = db.relationship('DomainTemplate', back_populates='records')
|
|
|
|
|
|
|
|
def __repr__(self):
|
2018-04-01 00:57:41 +00:00
|
|
|
return '<DomainTemplateRecord {0}>'.format(self.id)
|
2018-01-22 15:22:19 +00:00
|
|
|
|
|
|
|
def __init__(self, id=None, name=None, type=None, ttl=None, data=None, status=None):
|
|
|
|
self.id = id
|
|
|
|
self.name = name
|
|
|
|
self.type = type
|
|
|
|
self.ttl = ttl
|
|
|
|
self.data = data
|
|
|
|
self.status = status
|
|
|
|
|
|
|
|
def apply(self):
|
|
|
|
try:
|
|
|
|
db.session.commit()
|
2018-03-31 01:21:02 +00:00
|
|
|
except Exception as e:
|
|
|
|
logging.error('Can not update domain template table. Error: {0}'.format(e))
|
2018-01-22 15:22:19 +00:00
|
|
|
db.session.rollback()
|
|
|
|
return {'status': 'error', 'msg': 'Can not update domain template table'}
|
2019-03-01 22:49:31 +00:00
|
|
|
|
|
|
|
|
|
|
|
class ApiKey(db.Model):
|
|
|
|
__tablename__ = "apikey"
|
|
|
|
id = db.Column(db.Integer, primary_key=True)
|
|
|
|
key = db.Column(db.String(255), unique=True, nullable=False)
|
|
|
|
description = db.Column(db.String(255))
|
|
|
|
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
|
|
|
|
role = db.relationship('Role', back_populates="apikeys", lazy=True)
|
|
|
|
domains = db.relationship(
|
|
|
|
"Domain",
|
|
|
|
secondary=domain_apikey,
|
|
|
|
back_populates="apikeys"
|
|
|
|
)
|
|
|
|
|
|
|
|
def __init__(self, key=None, desc=None, role_name=None, domains=[]):
|
|
|
|
self.id = None
|
|
|
|
self.description = desc
|
|
|
|
self.role_name = role_name
|
|
|
|
self.domains[:] = domains
|
|
|
|
if not key:
|
|
|
|
rand_key = ''.join(
|
|
|
|
random.choice(
|
|
|
|
string.ascii_letters + string.digits
|
|
|
|
) for _ in range(15)
|
|
|
|
)
|
|
|
|
self.plain_key = rand_key
|
|
|
|
self.key = self.get_hashed_password(rand_key).decode('utf-8')
|
|
|
|
logging.debug("Hashed key: {0}".format(self.key))
|
|
|
|
else:
|
|
|
|
self.key = key
|
|
|
|
|
|
|
|
def create(self):
|
|
|
|
try:
|
|
|
|
self.role = Role.query.filter(Role.name == self.role_name).first()
|
|
|
|
db.session.add(self)
|
|
|
|
db.session.commit()
|
|
|
|
except Exception as e:
|
|
|
|
logging.error('Can not update api key table. Error: {0}'.format(e))
|
|
|
|
db.session.rollback()
|
|
|
|
raise e
|
|
|
|
|
|
|
|
def delete(self):
|
|
|
|
try:
|
|
|
|
db.session.delete(self)
|
|
|
|
db.session.commit()
|
|
|
|
except Exception as e:
|
|
|
|
msg_str = 'Can not delete api key template. Error: {0}'
|
|
|
|
logging.error(msg_str.format(e))
|
|
|
|
db.session.rollback()
|
|
|
|
raise e
|
|
|
|
|
|
|
|
def update(self, role_name=None, description=None, domains=None):
|
|
|
|
try:
|
|
|
|
if role_name:
|
|
|
|
role = Role.query.filter(Role.name == role_name).first()
|
|
|
|
self.role_id = role.id
|
|
|
|
|
|
|
|
if description:
|
|
|
|
self.description = description
|
|
|
|
|
|
|
|
if domains:
|
|
|
|
domain_object_list = Domain.query \
|
|
|
|
.filter(Domain.name.in_(domains)) \
|
|
|
|
.all()
|
|
|
|
self.domains[:] = domain_object_list
|
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
except Exception as e:
|
|
|
|
msg_str = 'Update of apikey failed. Error: {0}'
|
|
|
|
logging.error(msg_str.format(e))
|
|
|
|
db.session.rollback
|
|
|
|
raise e
|
|
|
|
|
|
|
|
def get_hashed_password(self, plain_text_password=None):
|
|
|
|
# Hash a password for the first time
|
|
|
|
# (Using bcrypt, the salt is saved into the hash itself)
|
|
|
|
if plain_text_password is None:
|
|
|
|
return plain_text_password
|
|
|
|
|
|
|
|
if plain_text_password:
|
|
|
|
pw = plain_text_password
|
|
|
|
else:
|
|
|
|
pw = self.plain_text_password
|
|
|
|
|
|
|
|
return bcrypt.hashpw(
|
|
|
|
pw.encode('utf-8'),
|
|
|
|
app.config.get('SALT').encode('utf-8')
|
|
|
|
)
|
|
|
|
|
|
|
|
def check_password(self, hashed_password):
|
|
|
|
# Check hased password. Using bcrypt,
|
|
|
|
# the salt is saved into the hash itself
|
|
|
|
if (self.plain_text_password):
|
|
|
|
return bcrypt.checkpw(
|
|
|
|
self.plain_text_password.encode('utf-8'),
|
|
|
|
hashed_password.encode('utf-8'))
|
|
|
|
return False
|
|
|
|
|
|
|
|
def is_validate(self, method, src_ip=''):
|
|
|
|
"""
|
|
|
|
Validate user credential
|
|
|
|
"""
|
|
|
|
if method == 'LOCAL':
|
|
|
|
passw_hash = self.get_hashed_password(self.plain_text_password)
|
|
|
|
apikey = ApiKey.query \
|
|
|
|
.filter(ApiKey.key == passw_hash.decode('utf-8')) \
|
|
|
|
.first()
|
|
|
|
|
|
|
|
if not apikey:
|
|
|
|
raise Exception("Unauthorized")
|
|
|
|
|
|
|
|
return apikey
|