diff options
-rw-r--r-- | fietsboek/__init__.py | 38 | ||||
-rw-r--r-- | fietsboek/config.py | 11 | ||||
-rw-r--r-- | fietsboek/email.py | 50 | ||||
-rw-r--r-- | fietsboek/jinja2.py | 2 | ||||
-rw-r--r-- | fietsboek/templates/layout.jinja2 | 2 | ||||
-rw-r--r-- | fietsboek/views/account.py | 13 | ||||
-rw-r--r-- | fietsboek/views/default.py | 9 | ||||
-rw-r--r-- | fietsboek/views/tileproxy.py | 54 |
8 files changed, 78 insertions, 101 deletions
diff --git a/fietsboek/__init__.py b/fietsboek/__init__.py index 5e342c5..e208a61 100644 --- a/fietsboek/__init__.py +++ b/fietsboek/__init__.py @@ -9,7 +9,6 @@ import redis from pyramid.config import Configurator from pyramid.session import SignedCookieSessionFactory from pyramid.csrf import CookieCSRFStoragePolicy -from pyramid.settings import asbool, aslist from pyramid.i18n import default_locale_negotiator from .security import SecurityPolicy @@ -40,7 +39,7 @@ def locale_negotiator(request): if locale: return locale - installed_locales = request.registry.settings['available_locales'] + installed_locales = request.config.available_locales sentinel = object() negotiated = request.accept_language.lookup(installed_locales, default=sentinel) if negotiated is sentinel: @@ -48,55 +47,30 @@ def locale_negotiator(request): return negotiated -def main(global_config, **settings): +def main(_global_config, **settings): """ This function returns a Pyramid WSGI application. """ - # pylint: disable=unused-argument, import-outside-toplevel, cyclic-import - from .views import tileproxy - parsed_config = mod_config.parse(settings) - if settings.get('session_key', '<EDIT THIS>') == '<EDIT THIS>': - raise ValueError("Please set a session signing key (session_key) in your settings!") - - if 'fietsboek.data_dir' not in settings: - raise ValueError("Please set a data directory (fietsboek.data_dir) in your settings!") - def data_manager(request): - data_dir = request.registry.settings["fietsboek.data_dir"] - return DataManager(Path(data_dir)) + return DataManager(Path(request.config.data_dir)) def redis_(request): - return redis.from_url(request.registry.settings["redis.url"]) + return redis.from_url(request.config.redis_url) def config_(_request): return parsed_config - settings['enable_account_registration'] = asbool( - settings.get('enable_account_registration', 'false')) - settings['available_locales'] = aslist( - settings.get('available_locales', 'en')) - settings['fietsboek.pages'] = aslist( - settings.get('fietsboek.pages', '')) - settings['fietsboek.tile_proxy.disable'] = asbool( - settings.get('fietsboek.tile_proxy.disable', 'false')) - settings['thunderforest.maps'] = aslist( - settings.get('thunderforest.maps', '')) - settings['fietsboek.default_tile_layers'] = aslist( - settings.get('fietsboek.default_tile_layers', - 'osm satellite osmde opentopo topplusopen opensea cycling hiking')) - settings['fietsboek.tile_layers'] = tileproxy.extract_tile_layers(settings) - # Load the pages page_manager = Pages() - for path in settings['fietsboek.pages']: + for path in parsed_config.pages: path = Path(path) if path.is_dir(): page_manager.load_directory(path) elif path.is_file(): page_manager.load_file(path) - def pages(request): + def pages(_request): return page_manager my_session_factory = SignedCookieSessionFactory(settings['session_key']) diff --git a/fietsboek/config.py b/fietsboek/config.py index c6da878..e90deda 100644 --- a/fietsboek/config.py +++ b/fietsboek/config.py @@ -183,7 +183,7 @@ class Config(BaseModel): """Tile layers.""" @validator("session_key") - def good_session_key(cls, value): + def _good_session_key(cls, value): """Ensures that the session key has been changed from its default value. """ @@ -191,7 +191,7 @@ class Config(BaseModel): raise ValueError("You need to edit the default session key!") @validator("email_smtp_url") - def known_smtp_url(cls, value): + def _known_smtp_url(cls, value): """Ensures that the SMTP URL is valid.""" parsed = urllib.parse.urlparse(value) if parsed.scheme not in {'debug', 'smtp', 'smtp+ssl', 'smtp+starttls'}: @@ -212,17 +212,16 @@ def parse(config): errors = [] # First, we try to extract the tile layers. tile_layers = [] - for key in config.keys(): + for key, value in config.items(): match = re.match("^fietsboek\\.tile_layer\\.([A-Za-z0-9_-]+)$", key) if not match: continue provider_id = match.group(1) - name = config[key] - prefix = f'{key}.' + prefix = f'{value}.' inner = {k[len(prefix):]: v for (k, v) in config.items() if k.startswith(prefix)} inner['layer_id'] = provider_id - inner['name'] = name + inner['name'] = value try: layer_config = TileLayerConfig.parse_obj(inner) tile_layers.append(layer_config) diff --git a/fietsboek/email.py b/fietsboek/email.py index c07c97d..497debe 100644 --- a/fietsboek/email.py +++ b/fietsboek/email.py @@ -1,5 +1,6 @@ """Utility functions for email sending.""" import logging +import re import smtplib import sys @@ -9,15 +10,14 @@ from email.message import EmailMessage LOGGER = logging.getLogger(__name__) -def prepare_message(settings, addr_to, subject): +def prepare_message(sender, addr_to, subject): """Prepares an email message with the right headers. The body of the message can be set by using :meth:`~email.message.EmailMessage.set_content` on the returned message. - :param settings: The application settings, used to access the email - specific settings. - :type settings: dict + :param sender: The email sender. + :type sender: str :param addr_to: Address of the recipient. :type addr_to: str :param subject: Subject of the message. @@ -25,44 +25,44 @@ def prepare_message(settings, addr_to, subject): :return: A prepared message. :rtype: email.message.EmailMessage """ - from_address = settings.get('email.from') - if not from_address: - LOGGER.warning("`email.from` is not set, make sure to check your configuration!") message = EmailMessage() message['To'] = addr_to - message['From'] = f'Fietsboek <{from_address}>' + if '<' not in sender and '>' not in sender: + message['From'] = f'Fietsboek <{sender}>' + else: + message['From'] = sender message['Subject'] = subject return message -def send_message(settings, message): +def send_message(server_url, username, password, message): """Sends an email message using the STMP server configured in the settings. The recipient is taken from the 'To'-header of the message. - :parm settings: The application settings. - :type settings: dict + :param server_url: The URL of the server for mail sending. + :type server_url: str + :param username: The username to authenticate, can be ``None`` or empty. + :type username str: + :param password: The password to authenticate, can be ``None`` or empty. + :type password: str :param message: The message to send. :type message: email.message.EmailMessage """ - smtp_server = settings.get('email.smtp_url') - if not smtp_server: - LOGGER.warning("`email.smtp_url` not set, no email can be sent!") - return - smtp_url = urlparse(smtp_server) - if smtp_url.scheme == 'debug': + parsed_url = urlparse(server_url) + if parsed_url.scheme == 'debug': print(message, file=sys.stderr) return try: - if smtp_url.scheme == 'smtp': - client = smtplib.SMTP(smtp_url.hostname, smtp_url.port) - elif smtp_url.scheme == 'smtp+ssl': - client = smtplib.SMTP_SSL(smtp_url.hostname, smtp_url.port) - elif smtp_url.scheme == 'smtp+starttls': - client = smtplib.SMTP(smtp_url.hostname, smtp_url.port) + if parsed_url.scheme == 'smtp': + client = smtplib.SMTP(parsed_url.hostname, parsed_url.port) + elif parsed_url.scheme == 'smtp+ssl': + client = smtplib.SMTP_SSL(parsed_url.hostname, parsed_url.port) + elif parsed_url.scheme == 'smtp+starttls': + client = smtplib.SMTP(parsed_url.hostname, parsed_url.port) client.starttls() - if 'email.smtp_user' in settings and 'email.smtp_password' in settings: - client.login(settings['email.smtp_user'], settings['email.smtp_password']) + if username and password: + client.login(username, password) client.send_message(message) client.quit() except smtplib.SMTPException: diff --git a/fietsboek/jinja2.py b/fietsboek/jinja2.py index a0b9457..e7ef522 100644 --- a/fietsboek/jinja2.py +++ b/fietsboek/jinja2.py @@ -95,7 +95,7 @@ def global_embed_tile_layers(request): from .views import tileproxy tile_sources = tileproxy.sources_for(request) - if request.registry.settings.get("fietsboek.tile_proxy.disable"): + if request.config.disable_tile_proxy: def _url(source): return source.url_template else: diff --git a/fietsboek/templates/layout.jinja2 b/fietsboek/templates/layout.jinja2 index 9236b36..80981d1 100644 --- a/fietsboek/templates/layout.jinja2 +++ b/fietsboek/templates/layout.jinja2 @@ -81,7 +81,7 @@ const Bestaetigung = false; <li> <a class="dropdown-item" href="{{ request.route_url('login') }}">{{ _("page.navbar.login") }}</a> </li> - {% if request.registry.settings.get('enable_account_registration') %} + {% if request.config.enable_account_registration %} <li> <a class="dropdown-item" href="{{ request.route_url('create-account') }}">{{ _("page.navbar.create_account") }}</a> </li> diff --git a/fietsboek/views/account.py b/fietsboek/views/account.py index 2816325..f9f48e9 100644 --- a/fietsboek/views/account.py +++ b/fietsboek/views/account.py @@ -18,7 +18,7 @@ def create_account(request): :rtype: pyramid.response.Response """ # pylint: disable=unused-argument - if not request.registry.settings['enable_account_registration']: + if not request.config.enable_account_registration: return HTTPForbidden() return {} @@ -34,7 +34,7 @@ def do_create_account(request): :rtype: pyramid.response.Response """ # pylint: disable=duplicate-code - if not request.registry.settings['enable_account_registration']: + if not request.config.enable_account_registration: return HTTPForbidden() password = request.params["password"] try: @@ -61,13 +61,18 @@ def do_create_account(request): request.dbsession.add(token) message = email.prepare_message( - request.registry.settings, + request.config.email_from, user.email, request.localizer.translate(_('email.verify_mail.subject')), ) message.set_content(request.localizer.translate(_('email.verify.text')) .format(request.route_url('use-token', uuid=token.uuid))) - email.send_message(request.registry.settings, message) + email.send_message( + request.config.email_smtp_url, + request.config.email_username, + request.config.email_password.get_secret_value(), + message, + ) request.session.flash(request.localizer.translate(_("flash.a_confirmation_link_has_been_sent"))) return HTTPFound(request.route_url('login')) diff --git a/fietsboek/views/default.py b/fietsboek/views/default.py index a36e4c3..f4aaa8f 100644 --- a/fietsboek/views/default.py +++ b/fietsboek/views/default.py @@ -166,7 +166,7 @@ def do_password_reset(request): request.session.flash(request.localizer.translate(_("flash.password_token_generated"))) mail = email.prepare_message( - request.registry.settings, + request.config.email_from, user.email, request.localizer.translate(_("page.password_reset.email.subject")), ) @@ -175,7 +175,12 @@ def do_password_reset(request): .translate(_("page.password_reset.email.body")) .format(request.route_url('use-token', uuid=token.uuid)) ) - email.send_message(request.registry.settings, mail) + email.send_message( + request.config.email_smtp_url, + request.config.email_username, + request.config.email_password.get_secret_value(), + mail, + ) return HTTPFound(request.route_url('password-reset')) diff --git a/fietsboek/views/tileproxy.py b/fietsboek/views/tileproxy.py index 631982e..3e2abc1 100644 --- a/fietsboek/views/tileproxy.py +++ b/fietsboek/views/tileproxy.py @@ -9,7 +9,6 @@ Additionally, this protects the users' IP, as only fietsboek can see it. import datetime import random import logging -import re from typing import NamedTuple from itertools import chain @@ -177,7 +176,7 @@ def tile_proxy(request): :return: The HTTP response. :rtype: pyramid.response.Response """ - if request.registry.settings.get("fietsboek.tile_proxy.disable"): + if request.config.disable_tile_proxy: raise HTTPBadRequest("Tile proxying is disabled") provider = request.matchdict['provider'] @@ -205,7 +204,7 @@ def tile_proxy(request): headers = { "user-agent": f"Fietsboek-Tile-Proxy/{__VERSION__}", } - from_mail = request.registry.settings.get('email.from') + from_mail = request.config.email_from if from_mail: headers["from"] = from_mail @@ -235,42 +234,41 @@ def sources_for(request): :return: A list of tile sources. :rtype: list[TileSource] """ - settings = request.registry.settings return [ source for source in chain( (default_layer for default_layer in DEFAULT_TILE_LAYERS - if default_layer.key in settings["fietsboek.default_tile_layers"]), - settings["fietsboek.tile_layers"] + if default_layer.key in request.config.default_tile_layers), + extract_tile_layers(request.config), ) if source.access == LayerAccess.PUBLIC or request.identity is not None ] -def extract_tile_layers(settings): +def extract_tile_layers(config): """Extract all defined tile layers from the settings. - :param settings: The application settings. - :type settings: dict + :param config: The fietsboek config. + :type config: fietsboek.config.Config :return: A list of extracted tile sources. :rtype: list[TileSource] """ layers = [] - layers.extend(_extract_thunderforest(settings)) - layers.extend(_extract_user_layers(settings)) + layers.extend(_extract_thunderforest(config)) + layers.extend(_extract_user_layers(config)) return layers -def _extract_thunderforest(settings): +def _extract_thunderforest(config): # Thunderforest Shortcut! - tf_api_key = settings.get("thunderforest.api_key") + tf_api_key = config.thunderforest_key.get_secret_value() if tf_api_key: - tf_access = LayerAccess(settings.get("thunderforest.access", "restricted")) + tf_access = config.thunderforest_access tf_attribution = ' | '.join([ _jb_copy, _href("https://www.thunderforest.com/", "Thunderforest"), _href("https://www.openstreetmap.org/", "OpenStreetMap"), ]) - for tf_map in settings["thunderforest.maps"]: + for tf_map in config.thunderforest_maps: url = (f"https://tile.thunderforest.com/{tf_map}/" f"{{z}}/{{x}}/{{y}}.png?apikey={tf_api_key}") yield TileSource( @@ -279,19 +277,15 @@ def _extract_thunderforest(settings): ) -def _extract_user_layers(settings): +def _extract_user_layers(config): # Any other custom maps - for key in settings.keys(): - match = re.match("^fietsboek\\.tile_layer\\.([A-Za-z0-9_-]+)$", key) - if not match: - continue - - provider_id = match.group(1) - name = settings[key] - url = settings[f"{key}.url"] - layer_type = LayerType(settings.get(f"{key}.type", "base")) - zoom = int(settings.get(f"{key}.zoom", 22)) - attribution = settings.get(f"{key}.attribution", _jb_copy) - access = LayerAccess(settings.get(f"{key}.access", "public")) - - yield TileSource(provider_id, name, url, layer_type, zoom, access, attribution) + for layer in config.tile_layers: + yield TileSource( + layer.layer_id, + layer.name, + layer.url, + layer.layer_type, + layer.zoom, + layer.access, + layer.attribution + ) |