2019-12-02 03:32:03 +00:00
|
|
|
import os
|
|
|
|
import base64
|
2020-01-03 03:03:20 +00:00
|
|
|
import traceback
|
2020-01-02 22:01:13 +00:00
|
|
|
import bcrypt
|
2019-12-07 13:46:14 +00:00
|
|
|
import pyotp
|
2019-12-02 03:32:03 +00:00
|
|
|
import ldap
|
|
|
|
import ldap.filter
|
2022-07-27 09:35:47 +00:00
|
|
|
from collections import OrderedDict
|
2019-12-02 03:32:03 +00:00
|
|
|
from flask import current_app
|
|
|
|
from flask_login import AnonymousUserMixin
|
2021-03-16 18:39:53 +00:00
|
|
|
from sqlalchemy import orm
|
2021-12-17 10:41:51 +00:00
|
|
|
import qrcode as qrc
|
|
|
|
import qrcode.image.svg as qrc_svg
|
|
|
|
from io import BytesIO
|
2019-12-02 03:32:03 +00:00
|
|
|
|
|
|
|
from .base import db
|
|
|
|
from .role import Role
|
2019-12-08 01:57:24 +00:00
|
|
|
from .setting import Setting
|
2019-12-02 03:32:03 +00:00
|
|
|
from .domain_user import DomainUser
|
2021-08-05 17:37:48 +00:00
|
|
|
from .account_user import AccountUser
|
2019-12-02 03:32:03 +00:00
|
|
|
|
|
|
|
|
|
|
|
class Anonymous(AnonymousUserMixin):
|
|
|
|
def __init__(self):
|
|
|
|
self.username = 'Anonymous'
|
|
|
|
|
|
|
|
|
|
|
|
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))
|
|
|
|
otp_secret = db.Column(db.String(16))
|
2019-12-22 01:40:05 +00:00
|
|
|
confirmed = db.Column(db.SmallInteger, nullable=False, default=0)
|
2019-12-02 03:32:03 +00:00
|
|
|
role_id = db.Column(db.Integer, db.ForeignKey('role.id'))
|
2022-12-22 21:50:01 +00:00
|
|
|
role = db.relationship('Role', back_populates="users", lazy=True)
|
2021-03-16 18:39:53 +00:00
|
|
|
accounts = None
|
2019-12-02 03:32:03 +00:00
|
|
|
|
|
|
|
def __init__(self,
|
|
|
|
id=None,
|
|
|
|
username=None,
|
|
|
|
password=None,
|
|
|
|
plain_text_password=None,
|
|
|
|
firstname=None,
|
|
|
|
lastname=None,
|
|
|
|
role_id=None,
|
|
|
|
email=None,
|
|
|
|
otp_secret=None,
|
2019-12-21 14:43:03 +00:00
|
|
|
confirmed=False,
|
2019-12-02 03:32:03 +00:00
|
|
|
reload_info=True):
|
|
|
|
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
|
|
|
|
self.otp_secret = otp_secret
|
2019-12-21 14:43:03 +00:00
|
|
|
self.confirmed = confirmed
|
2019-12-02 03:32:03 +00:00
|
|
|
|
|
|
|
if reload_info:
|
|
|
|
user_info = self.get_user_info_by_id(
|
|
|
|
) if id else self.get_user_info_by_username()
|
|
|
|
|
|
|
|
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
|
|
|
|
self.otp_secret = user_info.otp_secret
|
2019-12-21 14:43:03 +00:00
|
|
|
self.confirmed = user_info.confirmed
|
2019-12-02 03:32:03 +00:00
|
|
|
|
|
|
|
def is_authenticated(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def is_active(self):
|
|
|
|
return True
|
|
|
|
|
|
|
|
def is_anonymous(self):
|
|
|
|
return False
|
|
|
|
|
|
|
|
def get_id(self):
|
2022-05-07 19:14:48 +00:00
|
|
|
return str(self.id)
|
2019-12-02 03:32:03 +00:00
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return '<User {0}>'.format(self.username)
|
|
|
|
|
|
|
|
def get_totp_uri(self):
|
2023-03-13 17:54:49 +00:00
|
|
|
return "otpauth://totp/{0}:{1}?secret={2}&issuer=PowerDNS-Admin".format(
|
|
|
|
Setting().get('site_name'), self.username, self.otp_secret)
|
2019-12-02 03:32:03 +00:00
|
|
|
|
|
|
|
def verify_totp(self, token):
|
|
|
|
totp = pyotp.TOTP(self.otp_secret)
|
2022-09-07 12:23:34 +00:00
|
|
|
return totp.verify(token, valid_window = 5)
|
2019-12-02 03:32:03 +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)
|
|
|
|
if plain_text_password is None:
|
|
|
|
return plain_text_password
|
|
|
|
|
|
|
|
pw = plain_text_password if plain_text_password else self.plain_text_password
|
|
|
|
return bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt())
|
|
|
|
|
|
|
|
def check_password(self, hashed_password):
|
2020-01-02 22:01:13 +00:00
|
|
|
# Check hashed password. Using bcrypt, the salt is saved into the hash itself
|
2022-09-06 13:31:43 +00:00
|
|
|
if hasattr(self, "plain_text_password"):
|
2022-12-14 00:34:12 +00:00
|
|
|
if self.plain_text_password != None:
|
|
|
|
return bcrypt.checkpw(self.plain_text_password.encode('utf-8'),
|
|
|
|
hashed_password.encode('utf-8'))
|
2019-12-02 03:32:03 +00:00
|
|
|
return False
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
def ldap_init_conn(self):
|
|
|
|
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
|
|
|
|
conn = ldap.initialize(Setting().get('ldap_uri'))
|
|
|
|
conn.set_option(ldap.OPT_REFERRALS, ldap.OPT_OFF)
|
|
|
|
conn.set_option(ldap.OPT_PROTOCOL_VERSION, 3)
|
|
|
|
conn.set_option(ldap.OPT_X_TLS_DEMAND, True)
|
|
|
|
conn.set_option(ldap.OPT_DEBUG_LEVEL, 255)
|
|
|
|
conn.protocol_version = ldap.VERSION3
|
|
|
|
return conn
|
|
|
|
|
2021-08-05 17:37:48 +00:00
|
|
|
def ldap_search(self, searchFilter, baseDN, retrieveAttributes=None):
|
2019-12-02 03:32:03 +00:00
|
|
|
searchScope = ldap.SCOPE_SUBTREE
|
|
|
|
|
|
|
|
try:
|
|
|
|
conn = self.ldap_init_conn()
|
|
|
|
if Setting().get('ldap_type') == 'ad':
|
|
|
|
conn.simple_bind_s(
|
|
|
|
"{0}@{1}".format(self.username,
|
|
|
|
Setting().get('ldap_domain')),
|
|
|
|
self.password)
|
|
|
|
else:
|
|
|
|
conn.simple_bind_s(Setting().get('ldap_admin_username'),
|
|
|
|
Setting().get('ldap_admin_password'))
|
|
|
|
ldap_result_id = conn.search(baseDN, searchScope, searchFilter,
|
|
|
|
retrieveAttributes)
|
|
|
|
result_set = []
|
|
|
|
|
|
|
|
while 1:
|
|
|
|
result_type, result_data = conn.result(ldap_result_id, 0)
|
|
|
|
if (result_data == []):
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
if result_type == ldap.RES_SEARCH_ENTRY:
|
|
|
|
result_set.append(result_data)
|
|
|
|
return result_set
|
|
|
|
|
|
|
|
except ldap.LDAPError as e:
|
|
|
|
current_app.logger.error(e)
|
|
|
|
current_app.logger.debug('baseDN: {0}'.format(baseDN))
|
|
|
|
current_app.logger.debug(traceback.format_exc())
|
|
|
|
|
|
|
|
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:
|
|
|
|
current_app.logger.error(e)
|
|
|
|
return False
|
|
|
|
|
2020-01-02 22:01:13 +00:00
|
|
|
def is_validate(self, method, src_ip='', trust_user=False):
|
2019-12-02 03:32:03 +00:00
|
|
|
"""
|
|
|
|
Validate user credential
|
|
|
|
"""
|
|
|
|
role_name = 'User'
|
|
|
|
|
|
|
|
if method == 'LOCAL':
|
|
|
|
user_info = User.query.filter(
|
|
|
|
User.username == self.username).first()
|
|
|
|
|
|
|
|
if user_info:
|
2020-01-02 22:01:13 +00:00
|
|
|
if trust_user or (user_info.password and self.check_password(
|
|
|
|
user_info.password)):
|
2019-12-02 03:32:03 +00:00
|
|
|
current_app.logger.info(
|
|
|
|
'User "{0}" logged in successfully. Authentication request from {1}'
|
|
|
|
.format(self.username, src_ip))
|
|
|
|
return True
|
|
|
|
current_app.logger.error(
|
|
|
|
'User "{0}" inputted a wrong password. Authentication request from {1}'
|
|
|
|
.format(self.username, src_ip))
|
|
|
|
return False
|
|
|
|
|
|
|
|
current_app.logger.warning(
|
|
|
|
'User "{0}" does not exist. Authentication request from {1}'.
|
|
|
|
format(self.username, src_ip))
|
|
|
|
return False
|
|
|
|
|
|
|
|
if method == 'LDAP':
|
|
|
|
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')
|
2020-01-08 22:19:51 +00:00
|
|
|
LDAP_FILTER_GROUP = Setting().get('ldap_filter_group')
|
|
|
|
LDAP_FILTER_GROUPNAME = Setting().get('ldap_filter_groupname')
|
2019-12-02 03:32:03 +00:00
|
|
|
LDAP_ADMIN_GROUP = Setting().get('ldap_admin_group')
|
|
|
|
LDAP_OPERATOR_GROUP = Setting().get('ldap_operator_group')
|
|
|
|
LDAP_USER_GROUP = Setting().get('ldap_user_group')
|
|
|
|
LDAP_GROUP_SECURITY_ENABLED = Setting().get('ldap_sg_enabled')
|
|
|
|
|
|
|
|
# validate AD user password
|
2020-01-02 22:01:13 +00:00
|
|
|
if Setting().get('ldap_type') == 'ad' and not trust_user:
|
2019-12-02 03:32:03 +00:00
|
|
|
ldap_username = "{0}@{1}".format(self.username,
|
|
|
|
Setting().get('ldap_domain'))
|
|
|
|
if not self.ldap_auth(ldap_username, self.password):
|
|
|
|
current_app.logger.error(
|
|
|
|
'User "{0}" input a wrong LDAP password. Authentication request from {1}'
|
|
|
|
.format(self.username, src_ip))
|
|
|
|
return False
|
|
|
|
|
|
|
|
searchFilter = "(&({0}={1}){2})".format(LDAP_FILTER_USERNAME,
|
|
|
|
self.username,
|
|
|
|
LDAP_FILTER_BASIC)
|
|
|
|
current_app.logger.debug('Ldap searchFilter {0}'.format(searchFilter))
|
|
|
|
|
|
|
|
ldap_result = self.ldap_search(searchFilter, LDAP_BASE_DN)
|
|
|
|
current_app.logger.debug('Ldap search result: {0}'.format(ldap_result))
|
|
|
|
|
|
|
|
if not ldap_result:
|
|
|
|
current_app.logger.warning(
|
|
|
|
'LDAP User "{0}" does not exist. Authentication request from {1}'
|
|
|
|
.format(self.username, src_ip))
|
|
|
|
return False
|
|
|
|
else:
|
|
|
|
try:
|
|
|
|
ldap_username = ldap.filter.escape_filter_chars(
|
|
|
|
ldap_result[0][0][0])
|
|
|
|
|
2020-01-02 22:01:13 +00:00
|
|
|
if Setting().get('ldap_type') != 'ad' and not trust_user:
|
2019-12-02 03:32:03 +00:00
|
|
|
# validate ldap user password
|
|
|
|
if not self.ldap_auth(ldap_username, self.password):
|
|
|
|
current_app.logger.error(
|
|
|
|
'User "{0}" input a wrong LDAP password. Authentication request from {1}'
|
|
|
|
.format(self.username, src_ip))
|
|
|
|
return False
|
|
|
|
|
|
|
|
# check if LDAP_GROUP_SECURITY_ENABLED is True
|
|
|
|
# user can be assigned to ADMIN or USER role.
|
|
|
|
if LDAP_GROUP_SECURITY_ENABLED:
|
|
|
|
try:
|
|
|
|
if LDAP_TYPE == 'ldap':
|
2020-01-08 22:19:51 +00:00
|
|
|
groupSearchFilter = "(&({0}={1}){2})".format(LDAP_FILTER_GROUPNAME, ldap_username, LDAP_FILTER_GROUP)
|
2020-01-08 22:40:14 +00:00
|
|
|
current_app.logger.debug('Ldap groupSearchFilter {0}'.format(groupSearchFilter))
|
2023-03-18 20:27:02 +00:00
|
|
|
if (LDAP_ADMIN_GROUP and self.ldap_search(groupSearchFilter, LDAP_ADMIN_GROUP)):
|
2019-12-02 03:32:03 +00:00
|
|
|
role_name = 'Administrator'
|
|
|
|
current_app.logger.info(
|
|
|
|
'User {0} is part of the "{1}" group that allows admin access to PowerDNS-Admin'
|
2023-03-18 20:27:02 +00:00
|
|
|
.format(self.username, LDAP_ADMIN_GROUP))
|
|
|
|
elif (LDAP_OPERATOR_GROUP and self.ldap_search(groupSearchFilter, LDAP_OPERATOR_GROUP)):
|
2019-12-02 03:32:03 +00:00
|
|
|
role_name = 'Operator'
|
|
|
|
current_app.logger.info(
|
|
|
|
'User {0} is part of the "{1}" group that allows operator access to PowerDNS-Admin'
|
2023-03-18 20:27:02 +00:00
|
|
|
.format(self.username, LDAP_OPERATOR_GROUP))
|
|
|
|
elif (LDAP_USER_GROUP and self.ldap_search(groupSearchFilter, LDAP_USER_GROUP)):
|
2019-12-02 03:32:03 +00:00
|
|
|
current_app.logger.info(
|
|
|
|
'User {0} is part of the "{1}" group that allows user access to PowerDNS-Admin'
|
2023-03-18 20:27:02 +00:00
|
|
|
.format(self.username, LDAP_USER_GROUP))
|
2019-12-02 03:32:03 +00:00
|
|
|
else:
|
|
|
|
current_app.logger.error(
|
2023-03-18 20:27:02 +00:00
|
|
|
'User {0} is not part of any security groups that allow access to PowerDNS-Admin'
|
|
|
|
.format(self.username))
|
2019-12-02 03:32:03 +00:00
|
|
|
return False
|
|
|
|
elif LDAP_TYPE == 'ad':
|
2022-07-27 09:35:47 +00:00
|
|
|
ldap_group_security_roles = OrderedDict(
|
|
|
|
Administrator=LDAP_ADMIN_GROUP,
|
|
|
|
Operator=LDAP_OPERATOR_GROUP,
|
|
|
|
User=LDAP_USER_GROUP,
|
|
|
|
)
|
|
|
|
user_dn = ldap_result[0][0][0]
|
|
|
|
sf_groups = ""
|
|
|
|
|
|
|
|
for group in ldap_group_security_roles.values():
|
|
|
|
if not group:
|
|
|
|
continue
|
|
|
|
|
|
|
|
sf_groups += f"(distinguishedName={group})"
|
|
|
|
|
|
|
|
sf_member_user = f"(member:1.2.840.113556.1.4.1941:={user_dn})"
|
|
|
|
search_filter = f"(&(|{sf_groups}){sf_member_user})"
|
|
|
|
current_app.logger.debug(f"LDAP groupSearchFilter '{search_filter}'")
|
|
|
|
|
|
|
|
ldap_user_groups = [
|
|
|
|
group[0][0]
|
|
|
|
for group in self.ldap_search(
|
|
|
|
search_filter,
|
|
|
|
LDAP_BASE_DN
|
|
|
|
)
|
|
|
|
]
|
|
|
|
|
|
|
|
if not ldap_user_groups:
|
2019-12-02 03:32:03 +00:00
|
|
|
current_app.logger.error(
|
2022-07-27 09:35:47 +00:00
|
|
|
f"User '{self.username}' "
|
|
|
|
"does not belong to any group "
|
|
|
|
"while LDAP_GROUP_SECURITY_ENABLED is ON"
|
|
|
|
)
|
2019-12-02 03:32:03 +00:00
|
|
|
return False
|
|
|
|
|
2022-07-27 09:35:47 +00:00
|
|
|
current_app.logger.debug(
|
|
|
|
"LDAP User security groups "
|
|
|
|
f"for user '{self.username}': "
|
|
|
|
" ".join(ldap_user_groups)
|
|
|
|
)
|
2019-12-02 03:32:03 +00:00
|
|
|
|
2022-07-27 09:35:47 +00:00
|
|
|
for role, ldap_group in ldap_group_security_roles.items():
|
|
|
|
# Continue when groups is not defined or
|
|
|
|
# user is'nt member of LDAP group
|
|
|
|
if not ldap_group or not ldap_group in ldap_user_groups:
|
|
|
|
continue
|
|
|
|
|
|
|
|
role_name = role
|
2019-12-02 03:32:03 +00:00
|
|
|
current_app.logger.info(
|
2022-07-27 09:35:47 +00:00
|
|
|
f"User '{self.username}' member of "
|
|
|
|
f"the '{ldap_group}' group that allows "
|
|
|
|
f"'{role}' access to to PowerDNS-Admin"
|
|
|
|
)
|
|
|
|
|
|
|
|
# Stop loop on first found
|
|
|
|
break
|
|
|
|
|
2019-12-02 03:32:03 +00:00
|
|
|
else:
|
|
|
|
current_app.logger.error('Invalid LDAP type')
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
|
|
current_app.logger.error(
|
|
|
|
'LDAP group lookup for user "{0}" has failed. Authentication request from {1}'
|
|
|
|
.format(self.username, src_ip))
|
|
|
|
current_app.logger.debug(traceback.format_exc())
|
|
|
|
return False
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
current_app.logger.error('Wrong LDAP configuration. {0}'.format(e))
|
|
|
|
current_app.logger.debug(traceback.format_exc())
|
|
|
|
return False
|
|
|
|
|
|
|
|
# create user if not exist in the db
|
|
|
|
if not User.query.filter(User.username == self.username).first():
|
|
|
|
self.firstname = self.username
|
|
|
|
self.lastname = ''
|
|
|
|
try:
|
|
|
|
# try to get user's firstname, lastname and email address from LDAP attributes
|
|
|
|
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")
|
|
|
|
except Exception as e:
|
|
|
|
current_app.logger.warning(
|
|
|
|
"Reading ldap data threw an exception {0}".format(e))
|
|
|
|
current_app.logger.debug(traceback.format_exc())
|
|
|
|
|
|
|
|
# first register user will be in Administrator role
|
|
|
|
if User.query.count() == 0:
|
|
|
|
self.role_id = Role.query.filter_by(
|
|
|
|
name='Administrator').first().id
|
|
|
|
else:
|
|
|
|
self.role_id = Role.query.filter_by(
|
|
|
|
name=role_name).first().id
|
|
|
|
|
|
|
|
self.create_user()
|
|
|
|
current_app.logger.info('Created user "{0}" in the DB'.format(
|
|
|
|
self.username))
|
|
|
|
|
|
|
|
# user already exists in database, set their role based on group membership (if enabled)
|
|
|
|
if LDAP_GROUP_SECURITY_ENABLED:
|
|
|
|
self.set_role(role_name)
|
|
|
|
|
|
|
|
return True
|
|
|
|
else:
|
|
|
|
current_app.logger.error('Unsupported authentication method')
|
|
|
|
return False
|
|
|
|
|
|
|
|
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,...
|
|
|
|
"""
|
|
|
|
|
|
|
|
# Set an invalid password hash for non local users
|
|
|
|
self.password = '*'
|
|
|
|
|
|
|
|
db.session.add(self)
|
|
|
|
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:
|
|
|
|
return {'status': False, 'msg': 'Username is already in use'}
|
|
|
|
|
|
|
|
# check if email existed
|
|
|
|
user = User.query.filter(User.email == self.email).first()
|
|
|
|
if user:
|
|
|
|
return {'status': False, 'msg': 'Email address is already in use'}
|
|
|
|
|
|
|
|
# first register user will be in Administrator role
|
2021-01-07 22:07:20 +00:00
|
|
|
if self.role_id is None:
|
|
|
|
self.role_id = Role.query.filter_by(name='User').first().id
|
2019-12-02 03:32:03 +00:00
|
|
|
if User.query.count() == 0:
|
|
|
|
self.role_id = Role.query.filter_by(
|
|
|
|
name='Administrator').first().id
|
|
|
|
|
2022-12-14 00:34:12 +00:00
|
|
|
if hasattr(self, "plain_text_password"):
|
|
|
|
if self.plain_text_password != None:
|
|
|
|
self.password = self.get_hashed_password(
|
|
|
|
self.plain_text_password)
|
|
|
|
else:
|
|
|
|
self.password = '*'
|
2019-12-02 03:32:03 +00:00
|
|
|
|
|
|
|
if self.password and self.password != '*':
|
|
|
|
self.password = self.password.decode("utf-8")
|
|
|
|
|
|
|
|
db.session.add(self)
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': True, 'msg': 'Created user successfully'}
|
|
|
|
|
|
|
|
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)
|
2022-09-06 13:31:43 +00:00
|
|
|
if hasattr(self, "plain_text_password"):
|
2022-12-14 00:34:12 +00:00
|
|
|
if self.plain_text_password != None:
|
|
|
|
user.password = self.get_hashed_password(
|
|
|
|
self.plain_text_password).decode("utf-8")
|
2019-12-02 03:32:03 +00:00
|
|
|
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': True, 'msg': 'User updated successfully'}
|
|
|
|
|
|
|
|
def update_profile(self, enable_otp=None):
|
|
|
|
"""
|
|
|
|
Update user profile
|
|
|
|
"""
|
|
|
|
user = User.query.filter(User.username == self.username).first()
|
|
|
|
if not user:
|
|
|
|
return False
|
|
|
|
|
|
|
|
user.firstname = self.firstname if self.firstname else user.firstname
|
|
|
|
user.lastname = self.lastname if self.lastname else user.lastname
|
2022-12-14 00:34:12 +00:00
|
|
|
|
|
|
|
if hasattr(self, "plain_text_password"):
|
|
|
|
if self.plain_text_password != None:
|
|
|
|
user.password = self.get_hashed_password(
|
|
|
|
self.plain_text_password).decode("utf-8")
|
2019-12-02 03:32:03 +00:00
|
|
|
|
2019-12-22 02:19:35 +00:00
|
|
|
if self.email:
|
|
|
|
# Can not update to a new email that
|
|
|
|
# already been used.
|
|
|
|
existing_email = User.query.filter(
|
|
|
|
User.email == self.email,
|
|
|
|
User.username != self.username).first()
|
|
|
|
if existing_email:
|
|
|
|
return False
|
|
|
|
# If need to verify new email,
|
|
|
|
# update the "confirmed" status.
|
|
|
|
if user.email != self.email:
|
|
|
|
user.email = self.email
|
|
|
|
if Setting().get('verify_user_email'):
|
|
|
|
user.confirmed = 0
|
|
|
|
|
2019-12-02 03:32:03 +00:00
|
|
|
if enable_otp is not None:
|
|
|
|
user.otp_secret = ""
|
|
|
|
|
|
|
|
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
|
|
|
|
|
2019-12-21 14:43:03 +00:00
|
|
|
def update_confirmed(self, confirmed):
|
|
|
|
"""
|
|
|
|
Update user email confirmation status
|
|
|
|
"""
|
|
|
|
self.confirmed = confirmed
|
|
|
|
db.session.commit()
|
|
|
|
|
2019-12-02 03:32:03 +00:00
|
|
|
def get_domains(self):
|
|
|
|
"""
|
2023-03-12 19:41:10 +00:00
|
|
|
Get list of zones which the user is granted to have
|
2019-12-02 03:32:03 +00:00
|
|
|
access.
|
|
|
|
|
|
|
|
Note: This doesn't include the permission granting from Account
|
|
|
|
which user belong to
|
|
|
|
"""
|
|
|
|
return self.get_domain_query().all()
|
|
|
|
|
2021-08-05 17:37:48 +00:00
|
|
|
def get_user_domains(self):
|
|
|
|
from ..models.base import db
|
|
|
|
from .account import Account
|
|
|
|
from .domain import Domain
|
|
|
|
from .account_user import AccountUser
|
|
|
|
from .domain_user import DomainUser
|
|
|
|
|
|
|
|
domains = db.session.query(Domain) \
|
|
|
|
.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 == self.id,
|
|
|
|
AccountUser.user_id == self.id
|
|
|
|
)).all()
|
|
|
|
return domains
|
|
|
|
|
2019-12-02 03:32:03 +00:00
|
|
|
def delete(self):
|
|
|
|
"""
|
|
|
|
Delete a user
|
|
|
|
"""
|
|
|
|
# revoke all user privileges first
|
|
|
|
self.revoke_privilege()
|
|
|
|
|
|
|
|
try:
|
|
|
|
User.query.filter(User.username == self.username).delete()
|
|
|
|
db.session.commit()
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
|
|
db.session.rollback()
|
|
|
|
current_app.logger.error('Cannot delete user {0} from DB. DETAIL: {1}'.format(
|
|
|
|
self.username, e))
|
|
|
|
return False
|
|
|
|
|
2021-08-05 17:37:48 +00:00
|
|
|
def revoke_privilege(self, update_user=False):
|
2019-12-02 03:32:03 +00:00
|
|
|
"""
|
|
|
|
Revoke all privileges from a user
|
|
|
|
"""
|
|
|
|
user = User.query.filter(User.username == self.username).first()
|
|
|
|
|
|
|
|
if user:
|
|
|
|
user_id = user.id
|
|
|
|
try:
|
|
|
|
DomainUser.query.filter(DomainUser.user_id == user_id).delete()
|
2021-08-05 17:37:48 +00:00
|
|
|
if (update_user)==True:
|
|
|
|
AccountUser.query.filter(AccountUser.user_id == user_id).delete()
|
2019-12-02 03:32:03 +00:00
|
|
|
db.session.commit()
|
|
|
|
return True
|
|
|
|
except Exception as e:
|
|
|
|
db.session.rollback()
|
|
|
|
current_app.logger.error(
|
|
|
|
'Cannot revoke user {0} privileges. DETAIL: {1}'.format(
|
|
|
|
self.username, e))
|
|
|
|
return False
|
|
|
|
return False
|
|
|
|
|
|
|
|
def set_role(self, role_name):
|
|
|
|
role = Role.query.filter(Role.name == role_name).first()
|
|
|
|
if role:
|
|
|
|
user = User.query.filter(User.username == self.username).first()
|
|
|
|
user.role_id = role.id
|
|
|
|
db.session.commit()
|
|
|
|
return {'status': True, 'msg': 'Set user role successfully'}
|
|
|
|
else:
|
2020-01-02 22:01:13 +00:00
|
|
|
return {'status': False, 'msg': 'Role does not exist'}
|
2020-03-06 15:01:18 +00:00
|
|
|
|
2021-03-16 18:39:53 +00:00
|
|
|
@orm.reconstructor
|
|
|
|
def set_account(self):
|
|
|
|
self.accounts = self.get_accounts()
|
|
|
|
|
2020-03-06 15:01:18 +00:00
|
|
|
def get_accounts(self):
|
|
|
|
"""
|
|
|
|
Get accounts associated with this user
|
|
|
|
"""
|
|
|
|
from .account import Account
|
|
|
|
from .account_user import AccountUser
|
|
|
|
accounts = []
|
|
|
|
query = db.session\
|
|
|
|
.query(
|
|
|
|
AccountUser,
|
|
|
|
Account)\
|
2021-03-16 18:39:53 +00:00
|
|
|
.filter(self.id == AccountUser.user_id)\
|
2020-03-06 15:01:18 +00:00
|
|
|
.filter(Account.id == AccountUser.account_id)\
|
2021-11-19 15:55:29 +00:00
|
|
|
.order_by(Account.name)\
|
2020-03-06 15:01:18 +00:00
|
|
|
.all()
|
|
|
|
for q in query:
|
|
|
|
accounts.append(q[1])
|
|
|
|
return accounts
|
2022-04-07 11:31:27 +00:00
|
|
|
|
2021-12-17 10:41:51 +00:00
|
|
|
def get_qrcode_value(self):
|
|
|
|
img = qrc.make(self.get_totp_uri(),
|
|
|
|
image_factory=qrc_svg.SvgPathImage)
|
|
|
|
stream = BytesIO()
|
|
|
|
img.save(stream)
|
|
|
|
return stream.getvalue()
|
2021-08-05 17:37:48 +00:00
|
|
|
|
|
|
|
|
|
|
|
def read_entitlements(self, key):
|
|
|
|
"""
|
|
|
|
Get entitlements from ldap server associated with this user
|
|
|
|
"""
|
|
|
|
LDAP_BASE_DN = Setting().get('ldap_base_dn')
|
|
|
|
LDAP_FILTER_USERNAME = Setting().get('ldap_filter_username')
|
|
|
|
LDAP_FILTER_BASIC = Setting().get('ldap_filter_basic')
|
|
|
|
searchFilter = "(&({0}={1}){2})".format(LDAP_FILTER_USERNAME,
|
|
|
|
self.username,
|
|
|
|
LDAP_FILTER_BASIC)
|
|
|
|
current_app.logger.debug('Ldap searchFilter {0}'.format(searchFilter))
|
|
|
|
ldap_result = self.ldap_search(searchFilter, LDAP_BASE_DN, [key])
|
|
|
|
current_app.logger.debug('Ldap search result: {0}'.format(ldap_result))
|
|
|
|
entitlements=[]
|
|
|
|
if ldap_result:
|
|
|
|
dict=ldap_result[0][0][1]
|
|
|
|
if len(dict)!=0:
|
|
|
|
for entitlement in dict[key]:
|
|
|
|
entitlements.append(entitlement.decode("utf-8"))
|
|
|
|
else:
|
|
|
|
e="Not found value in the autoprovisioning attribute field "
|
|
|
|
current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e))
|
|
|
|
return entitlements
|
|
|
|
|
|
|
|
def updateUser(self, Entitlements):
|
|
|
|
"""
|
|
|
|
Update user associations based on ldap attribute
|
|
|
|
"""
|
|
|
|
entitlements= getCorrectEntitlements(Entitlements)
|
|
|
|
if len(entitlements)!=0:
|
|
|
|
self.revoke_privilege(True)
|
|
|
|
for entitlement in entitlements:
|
|
|
|
arguments=entitlement.split(':')
|
|
|
|
entArgs=arguments[arguments.index('powerdns-admin')+1:]
|
|
|
|
role= entArgs[0]
|
|
|
|
self.set_role(role)
|
|
|
|
if (role=="User") and len(entArgs)>1:
|
|
|
|
current_domains=getUserInfo(self.get_user_domains())
|
|
|
|
current_accounts=getUserInfo(self.get_accounts())
|
|
|
|
domain=entArgs[1]
|
|
|
|
self.addMissingDomain(domain, current_domains)
|
|
|
|
if len(entArgs)>2:
|
|
|
|
account=entArgs[2]
|
|
|
|
self.addMissingAccount(account, current_accounts)
|
|
|
|
|
|
|
|
def addMissingDomain(self, autoprovision_domain, current_domains):
|
|
|
|
"""
|
2023-03-12 19:41:10 +00:00
|
|
|
Add domain gathered by autoprovisioning to the current zones list of a user
|
2021-08-05 17:37:48 +00:00
|
|
|
"""
|
|
|
|
from ..models.domain import Domain
|
|
|
|
user = db.session.query(User).filter(User.username == self.username).first()
|
|
|
|
if autoprovision_domain not in current_domains:
|
|
|
|
domain= db.session.query(Domain).filter(Domain.name == autoprovision_domain).first()
|
|
|
|
if domain!=None:
|
|
|
|
domain.add_user(user)
|
|
|
|
|
|
|
|
def addMissingAccount(self, autoprovision_account, current_accounts):
|
|
|
|
"""
|
|
|
|
Add account gathered by autoprovisioning to the current accounts list of a user
|
|
|
|
"""
|
|
|
|
from ..models.account import Account
|
|
|
|
user = db.session.query(User).filter(User.username == self.username).first()
|
|
|
|
if autoprovision_account not in current_accounts:
|
|
|
|
account= db.session.query(Account).filter(Account.name == autoprovision_account).first()
|
|
|
|
if account!=None:
|
|
|
|
account.add_user(user)
|
|
|
|
|
|
|
|
def getCorrectEntitlements(Entitlements):
|
|
|
|
"""
|
|
|
|
Gather a list of valid records from the ldap attribute given
|
|
|
|
"""
|
|
|
|
from ..models.role import Role
|
|
|
|
urn_value=Setting().get('urn_value')
|
|
|
|
urnArgs=[x.lower() for x in urn_value.split(':')]
|
|
|
|
entitlements=[]
|
|
|
|
for Entitlement in Entitlements:
|
|
|
|
arguments=Entitlement.split(':')
|
|
|
|
|
|
|
|
if ('powerdns-admin' in arguments):
|
|
|
|
prefix=arguments[0:arguments.index('powerdns-admin')]
|
|
|
|
prefix=[x.lower() for x in prefix]
|
|
|
|
if (prefix!=urnArgs):
|
|
|
|
e= "Typo in first part of urn value"
|
|
|
|
current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e))
|
|
|
|
continue
|
|
|
|
|
|
|
|
else:
|
|
|
|
e="Entry not a PowerDNS-Admin record"
|
|
|
|
current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e))
|
|
|
|
continue
|
|
|
|
|
|
|
|
if len(arguments)<=len(urnArgs)+1: #prefix:powerdns-admin
|
|
|
|
e="No value given after the prefix"
|
|
|
|
current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e))
|
|
|
|
continue
|
|
|
|
|
|
|
|
entArgs=arguments[arguments.index('powerdns-admin')+1:]
|
|
|
|
role=entArgs[0]
|
|
|
|
roles= Role.query.all()
|
|
|
|
role_names=get_role_names(roles)
|
|
|
|
|
|
|
|
if role not in role_names:
|
|
|
|
e="Role given by entry not a role availabe in PowerDNS-Admin. Check for spelling errors"
|
|
|
|
current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e))
|
|
|
|
continue
|
|
|
|
|
|
|
|
if len(entArgs)>1:
|
|
|
|
if (role!="User"):
|
|
|
|
e="Too many arguments for Admin or Operator"
|
|
|
|
current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e))
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
if len(entArgs)<=3:
|
|
|
|
if entArgs[1] and not checkIfDomainExists(entArgs[1]):
|
|
|
|
continue
|
|
|
|
if len(entArgs)==3:
|
|
|
|
if entArgs[2] and not checkIfAccountExists(entArgs[2]):
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
e="Too many arguments"
|
|
|
|
current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e))
|
|
|
|
continue
|
|
|
|
|
|
|
|
entitlements.append(Entitlement)
|
|
|
|
|
|
|
|
return entitlements
|
|
|
|
|
|
|
|
|
|
|
|
def checkIfDomainExists(domainName):
|
|
|
|
from ..models.domain import Domain
|
|
|
|
domain= db.session.query(Domain).filter(Domain.name == domainName)
|
|
|
|
if len(domain.all())==0:
|
|
|
|
e= domainName + " is not found in the database"
|
|
|
|
current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e))
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def checkIfAccountExists(accountName):
|
|
|
|
from ..models.account import Account
|
|
|
|
account= db.session.query(Account).filter(Account.name == accountName)
|
|
|
|
if len(account.all())==0:
|
|
|
|
e= accountName + " is not found in the database"
|
|
|
|
current_app.logger.warning("Cannot apply autoprovisioning on user: {}".format(e))
|
|
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
|
|
def get_role_names(roles):
|
|
|
|
"""
|
|
|
|
returns all the roles available in database in string format
|
|
|
|
"""
|
|
|
|
roles_list=[]
|
|
|
|
for role in roles:
|
2022-04-07 11:31:27 +00:00
|
|
|
roles_list.append(role.name)
|
2021-08-05 17:37:48 +00:00
|
|
|
return roles_list
|
2022-04-07 11:31:27 +00:00
|
|
|
|
2021-08-05 17:37:48 +00:00
|
|
|
def getUserInfo(DomainsOrAccounts):
|
|
|
|
current=[]
|
|
|
|
for DomainOrAccount in DomainsOrAccounts:
|
|
|
|
current.append(DomainOrAccount.name)
|
2022-04-07 11:31:27 +00:00
|
|
|
return current
|