From 6756bd4efaaa75d8f8e49d6d397a56cb27f92596 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Tue, 7 Mar 2017 10:47:03 +0100 Subject: [PATCH] allow customizing setting user id in session (fixes #15191) --- mellon/adapters.py | 4 ++ mellon/metadata.py | 109 ++++++++++++++++++++++++++++++ mellon/templates/mellon/mellon_error.html | 11 +++ mellon/views.py | 12 +++- 4 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 mellon/metadata.py create mode 100644 mellon/templates/mellon/mellon_error.html diff --git a/mellon/adapters.py b/mellon/adapters.py index 7d6bea2..a39b02d 100644 --- a/mellon/adapters.py +++ b/mellon/adapters.py @@ -297,3 +297,7 @@ class DefaultAdapter(object): self.logger.info(u'removing group %s (%s) from user %s (%s)', rel.group, rel.group.pk, rel.user, rel.user.pk) qs.delete() + + def login(self, request, user): + auth.login(request, user) + return True diff --git a/mellon/metadata.py b/mellon/metadata.py new file mode 100644 index 0000000..7e1229d --- /dev/null +++ b/mellon/metadata.py @@ -0,0 +1,109 @@ +import datetime +from xml.etree import ElementTree as ET +import logging +import threading + +import requests +import isodate + +from django.core.files.storage import default_storage +from django.utils.timezone import now + + +class MetadataException(RuntimeError): + pass + + +class Metadata(object): + doc = None + cache_duration = None + valid_until = None + __cache = {} + url = None + path = None + timeout = 10 + + def __init__(self, url=None, path=None, content=None, timeout=None): + self.url = url or self.url + self.path = path or self.path + self.timeout = timeout or self.timeout + + if url: + self.load_from_url() + elif path: + self.load_from_path() + else: + self.load_content(content) + + def load_content(self, content): + self.doc = ET.fromstring(content) + try: + self.cache_duration = isodate.parse_duration( + self.doc.attrib.get('cacheDuration')) + except: + self.cache_duration = datetime.timedelta(seconds=30) + try: + self.valid_until = isodate.parse_datetime( + self.doc.attrib.get('validUntil')) + except: + self.valid_until = None + + @property + def cache_path(self): + return 'metadata_cache_' + self.url.replace('/', '_') + + @property + def cache_exists(self): + return default_storage.exists(self.cache_path) + + @property + def cache_timestamp(self): + return default_storage.created_time(self.cache_path) + + def retrieve_url(self): + logger = logging.getLogger(__name__) + try: + response = requests.get(self.url, timeout=self.timeout) + response.raise_for_status() + except requests.RequestException as e: + if self.cache_exists: + logger.warning(u'unable to retrieve metadata from %s, using filesystem cache: %s', + self.url, e) + return self.cache_timestamp, default_storage.open(self.cache_path).read() + logger.error(u'unable to retrieve metadata from %s, and no filesystem cache: %s', + self.url, e) + raise MetadataException('unable to retrieve uncached metadata from URL') + else: + content = response.content + if self.cache_exists: + default_storage.delete(self.cache_path) + default_storage.save(self.cache_path, content) + self.load_content(content) + self.__cache[self.url] = now(), content + + def should_update(self, timestamp): + if now() - timestamp < self.cache_duration: + return False + return not self.valid_until or now() > self.valid_until + + def background_update(self): + try: + self.retrieve_url() + except: + pass + + def load_from_url(self): + # 1. check memory cache + # - if absent synchronously retrieve URL + if self.url not in self.__cache: + self.retrieve_url() + timestamp, content = self.__cache[self.url] + else: + timestamp, content = self.__cache[self.url] + self.load_content(content) + if self.should_update(timestamp): + timestamp = (now() - self.cache_duration + 3 * + datetime.timedelta(seconds=self.timeout)) + self.__cache[self.url] = timestamp, content + t = threading.Thread(target=self.background_update) + t.start() diff --git a/mellon/templates/mellon/mellon_error.html b/mellon/templates/mellon/mellon_error.html new file mode 100644 index 0000000..a6bdc1e --- /dev/null +++ b/mellon/templates/mellon/mellon_error.html @@ -0,0 +1,11 @@ +{% extends "mellon/base.html" %} +{% load i18n %} + +{% block mellon_content %} +

+ {{ mellon_error_message }} +

+ +{% endblock %} diff --git a/mellon/views.py b/mellon/views.py index 22dd16c..ea11c4f 100644 --- a/mellon/views.py +++ b/mellon/views.py @@ -189,7 +189,17 @@ class LoginView(ProfileMixin, LogMixin, View): next_url = self.get_next_url(default=resolve_url(settings.LOGIN_REDIRECT_URL)) if user is not None: if user.is_active: - auth.login(request, user) + for adapter in utils.get_adapters(): + if adapter.login(request, user): + break + else: + self.log.warning('no adapter accepted to login login user %r', attributes) + return render( + request, "mellon/mellon_error.html", + { + 'mellon_error_message': 'No adapter accepted to login the user', + } + ) self.log.info('user %r (NameID is %r) logged in using SAML', unicode(user), attributes['name_id_content']) request.session['mellon_session'] = utils.flatten_datetime(attributes) -- 2.1.4