diff --git a/configs/development.py b/configs/development.py index cdced36..d4bd24f 100644 --- a/configs/development.py +++ b/configs/development.py @@ -7,7 +7,6 @@ SALT = '$2b$12$yLUMTIfl21FKJQpTkRQXCu' SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2' BIND_ADDRESS = '0.0.0.0' PORT = 9191 -OFFLINE_MODE = False ### DATABASE CONFIG SQLA_DB_USER = 'pda' diff --git a/configs/docker_config.py b/configs/docker_config.py index 7285252..1099a4a 100644 --- a/configs/docker_config.py +++ b/configs/docker_config.py @@ -50,7 +50,6 @@ legal_envvars = ( 'SAML_LOGOUT', 'SAML_LOGOUT_URL', 'SAML_ASSERTION_ENCRYPTED', - 'OFFLINE_MODE', 'REMOTE_USER_LOGOUT_URL', 'REMOTE_USER_COOKIES', 'SIGNUP_ENABLED', @@ -77,7 +76,6 @@ legal_envvars_bool = ( 'SAML_WANT_MESSAGE_SIGNED', 'SAML_LOGOUT', 'SAML_ASSERTION_ENCRYPTED', - 'OFFLINE_MODE', 'REMOTE_USER_ENABLED', 'SIGNUP_ENABLED', 'LOCAL_DB_ENABLED', diff --git a/docker-compose.yml b/docker-compose.yml index 0f569c4..74ff185 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -15,4 +15,3 @@ services: - GUNICORN_TIMEOUT=60 - GUNICORN_WORKERS=2 - GUNICORN_LOGLEVEL=DEBUG - - OFFLINE_MODE=False # True for offline, False for external resources diff --git a/powerdnsadmin/__init__.py b/powerdnsadmin/__init__.py index 02479a5..5adf748 100755 --- a/powerdnsadmin/__init__.py +++ b/powerdnsadmin/__init__.py @@ -74,8 +74,6 @@ def create_app(config=None): app.jinja_env.filters['display_record_name'] = utils.display_record_name app.jinja_env.filters['display_master_name'] = utils.display_master_name app.jinja_env.filters['display_second_to_time'] = utils.display_time - app.jinja_env.filters[ - 'email_to_gravatar_url'] = utils.email_to_gravatar_url app.jinja_env.filters[ 'display_setting_state'] = utils.display_setting_state app.jinja_env.filters['pretty_domain_name'] = utils.pretty_domain_name @@ -93,9 +91,4 @@ def create_app(config=None): setting = Setting() return dict(SETTING=setting) - @app.context_processor - def inject_mode(): - setting = app.config.get('OFFLINE_MODE', False) - return dict(OFFLINE_MODE=setting) - return app diff --git a/powerdnsadmin/default_config.py b/powerdnsadmin/default_config.py index 93b97b7..81d0e32 100644 --- a/powerdnsadmin/default_config.py +++ b/powerdnsadmin/default_config.py @@ -8,7 +8,6 @@ SECRET_KEY = 'e951e5a1f4b94151b360f47edf596dd2' BIND_ADDRESS = '0.0.0.0' PORT = 9191 HSTS_ENABLED = False -OFFLINE_MODE = False FILESYSTEM_SESSIONS_ENABLED = False SESSION_COOKIE_SAMESITE = 'Lax' CSRF_COOKIE_HTTPONLY = True diff --git a/powerdnsadmin/lib/utils.py b/powerdnsadmin/lib/utils.py index 769587e..f63a11c 100644 --- a/powerdnsadmin/lib/utils.py +++ b/powerdnsadmin/lib/utils.py @@ -2,14 +2,12 @@ import logging import re import json import requests -import hashlib import ipaddress import idna from collections.abc import Iterable from distutils.version import StrictVersion from urllib.parse import urlparse -from datetime import datetime, timedelta def auth_from_url(url): @@ -186,17 +184,6 @@ def pdns_api_extended_uri(version): return "" -def email_to_gravatar_url(email="", size=100): - """ - AD doesn't necessarily have email - """ - if email is None: - email = "" - - hash_string = hashlib.md5(email.encode('utf-8')).hexdigest() - return "https://s.gravatar.com/avatar/{0}?s={1}".format(hash_string, size) - - def display_setting_state(value): if value == 1: return "ON" diff --git a/powerdnsadmin/models/setting.py b/powerdnsadmin/models/setting.py index 51e78e5..a4016bf 100644 --- a/powerdnsadmin/models/setting.py +++ b/powerdnsadmin/models/setting.py @@ -193,7 +193,8 @@ class Setting(db.Model): 'otp_force': False, 'max_history_records': 1000, 'deny_domain_override': False, - 'account_name_extra_chars': False + 'account_name_extra_chars': False, + 'gravatar_enabled': False, } def __init__(self, id=None, name=None, value=None): diff --git a/powerdnsadmin/routes/admin.py b/powerdnsadmin/routes/admin.py index c5f28d1..9474503 100644 --- a/powerdnsadmin/routes/admin.py +++ b/powerdnsadmin/routes/admin.py @@ -1259,20 +1259,41 @@ def history_table(): # ajax call data @login_required @operator_role_required def setting_basic(): - if request.method == 'GET': - settings = [ - 'maintenance', 'fullscreen_layout', 'record_helper', - 'login_ldap_first', 'default_record_table_size', - 'default_domain_table_size', 'auto_ptr', 'record_quick_edit', - 'pretty_ipv6_ptr', 'dnssec_admins_only', - 'allow_user_create_domain', 'allow_user_remove_domain', 'allow_user_view_history', 'bg_domain_updates', 'site_name', - 'session_timeout', 'warn_session_timeout', 'ttl_options', - 'pdns_api_timeout', 'verify_ssl_connections', 'verify_user_email', - 'delete_sso_accounts', 'otp_field_enabled', 'custom_css', 'enable_api_rr_history', 'max_history_records', 'otp_force', - 'deny_domain_override', 'enforce_api_ttl', 'account_name_extra_chars' - ] + settings = [ + 'account_name_extra_chars', + 'allow_user_create_domain', + 'allow_user_remove_domain', + 'allow_user_view_history', + 'auto_ptr', + 'bg_domain_updates', + 'custom_css', + 'default_domain_table_size', + 'default_record_table_size', + 'delete_sso_accounts', + 'deny_domain_override', + 'dnssec_admins_only', + 'enable_api_rr_history', + 'enforce_api_ttl', + 'fullscreen_layout', + 'gravatar_enabled', + 'login_ldap_first', + 'maintenance', + 'max_history_records', + 'otp_field_enabled', + 'otp_force', + 'pdns_api_timeout', + 'pretty_ipv6_ptr', + 'record_helper', + 'record_quick_edit', + 'session_timeout', + 'site_name', + 'ttl_options', + 'verify_ssl_connections', + 'verify_user_email', + 'warn_session_timeout', + ] - return render_template('admin_setting_basic.html', settings=settings) + return render_template('admin_setting_basic.html', settings=settings) @admin_bp.route('/setting/basic//edit', methods=['POST']) diff --git a/powerdnsadmin/routes/user.py b/powerdnsadmin/routes/user.py index f411c29..65d7e08 100644 --- a/powerdnsadmin/routes/user.py +++ b/powerdnsadmin/routes/user.py @@ -1,5 +1,10 @@ import datetime -from flask import Blueprint, request, render_template, make_response, jsonify, redirect, url_for, g, session, current_app +import hashlib +import imghdr +import mimetypes + +from flask import Blueprint, request, render_template, make_response, jsonify, redirect, url_for, g, session, \ + current_app, after_this_request, abort from flask_login import current_user, login_required, login_manager from ..models.user import User, Anonymous @@ -96,4 +101,55 @@ def qrcode(): 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' - } \ No newline at end of file + } + + +@user_bp.route('/image', methods=['GET']) +@login_required +def image(): + """Returns the user profile image or avatar.""" + + @after_this_request + def add_cache_headers(response_): + """When the response is ok, add cache headers.""" + if 200 <= response_.status_code <= 399: + response_.cache_control.private = True + response_.cache_control.max_age = int(datetime.timedelta(days=1).total_seconds()) + return response_ + + def return_image(content, content_type=None): + """Return the given binary image content. Guess the type if not given.""" + if not content_type: + guess = mimetypes.guess_type('example.' + imghdr.what(None, h=content)) + if guess and guess[0]: + content_type = guess[0] + + return content, 200, {'Content-Type': content_type} + + # To prevent "cache poisoning", the username query parameter is required + if request.args.get('username', None) != current_user.username: + abort(400) + + setting = Setting() + + if session['authentication_type'] == 'LDAP': + search_filter = '(&({0}={1}){2})'.format(setting.get('ldap_filter_username'), + current_user.username, + setting.get('ldap_filter_basic')) + result = User().ldap_search(search_filter, setting.get('ldap_base_dn')) + if result and result[0] and result[0][0] and result[0][0][1]: + user_obj = result[0][0][1] + for key in ['jpegPhoto', 'thumbnailPhoto']: + if key in user_obj and user_obj[key] and user_obj[key][0]: + current_app.logger.debug(f'Return {key} from ldap as user image') + return return_image(user_obj[key][0]) + + email = current_user.email + if email and setting.get('gravatar_enabled'): + hash_ = hashlib.md5(email.encode('utf-8')).hexdigest() + url = f'https://s.gravatar.com/avatar/{hash_}?s=100' + current_app.logger.debug('Redirect user image request to gravatar') + return redirect(url, 307) + + # Fallback to the local default image + return current_app.send_static_file('img/user_image.png') diff --git a/powerdnsadmin/static/custom/css/custom.css b/powerdnsadmin/static/custom/css/custom.css index f5dcb34..1cd3728 100644 --- a/powerdnsadmin/static/custom/css/custom.css +++ b/powerdnsadmin/static/custom/css/custom.css @@ -42,15 +42,6 @@ table td { background-position: center; } -.navbar-nav>.user-menu>.dropdown-menu>li.user-header>img.img-circle.offline { - filter: brightness(0); - border-color: black; -} - -.navbar-nav>.user-menu .user-image.offline { - filter: brightness(0); -} - .search-input { width: 100%; } \ No newline at end of file diff --git a/powerdnsadmin/static/img/gravatar.png b/powerdnsadmin/static/img/gravatar.png deleted file mode 100644 index 8360286..0000000 Binary files a/powerdnsadmin/static/img/gravatar.png and /dev/null differ diff --git a/powerdnsadmin/static/img/user_image.png b/powerdnsadmin/static/img/user_image.png new file mode 100644 index 0000000..325b0ef Binary files /dev/null and b/powerdnsadmin/static/img/user_image.png differ diff --git a/powerdnsadmin/templates/admin_pdns_stats.html b/powerdnsadmin/templates/admin_pdns_stats.html index 7f22c4f..cd5a000 100644 --- a/powerdnsadmin/templates/admin_pdns_stats.html +++ b/powerdnsadmin/templates/admin_pdns_stats.html @@ -35,7 +35,7 @@ {% for statistic in statistics %} - {{ statistic['name'] }} @@ -70,7 +70,7 @@ {% for config in configs %} - {{ config['name'] }} diff --git a/powerdnsadmin/templates/base.html b/powerdnsadmin/templates/base.html index 649663a..85b2f82 100644 --- a/powerdnsadmin/templates/base.html +++ b/powerdnsadmin/templates/base.html @@ -8,13 +8,8 @@ {% block title %}{{ SITE_NAME }}{% endblock %} - {% if OFFLINE_MODE %} - {% else %} - - - {% endif %} @@ -25,20 +20,10 @@ {% if SETTING.get('custom_css') %} {% endif %} - - - {% endblock %} - {% if OFFLINE_MODE %} - {% set gravatar_url = url_for('static', filename='img/gravatar.png') %} - {% elif current_user.email is defined %} - {% set gravatar_url = current_user.email|email_to_gravatar_url(size=80) %} - {% endif %} +{% set user_image_url = url_for('user.image', username=current_user.username) %}
{% block pageheader %}
@@ -62,14 +47,14 @@