add decorator to cache dynamically generated data inside a view function
It also add support for the ETag and Last-Modified headers.
This commit is contained in:
parent
f7fe8de441
commit
83206efa55
|
@ -28,6 +28,7 @@ from authentic2.authsaml2 import signals
|
|||
from authentic2.authsaml2.models import *
|
||||
from backends import AuthSAML2PersistentBackend, \
|
||||
AuthSAML2TransientBackend
|
||||
from authentic2.utils import cache_and_validate
|
||||
|
||||
__logout_redirection_timeout = getattr(settings, 'IDP_LOGOUT_TIMEOUT', 600)
|
||||
|
||||
|
@ -57,6 +58,7 @@ metadata_map = (
|
|||
)
|
||||
metadata_options = { 'key': settings.SAML_SIGNATURE_PUBLIC_KEY }
|
||||
|
||||
@cache_and_validate(settings.LOCAL_METADATA_CACHE_TIMEOUT)
|
||||
def metadata(request):
|
||||
'''Endpoint to retrieve the metadata file'''
|
||||
logger.info('metadata: return metadata')
|
||||
|
|
|
@ -11,12 +11,14 @@ from django.utils.translation import ugettext as _
|
|||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
|
||||
from authentic2.saml.models import LibertyArtifact
|
||||
from authentic2.saml.common import get_idff12_metadata, create_idff12_server, \
|
||||
load_provider, load_federation, load_session, save_federation, \
|
||||
save_session, return_idff12_response, get_idff12_request_message, \
|
||||
get_soap_message, return_saml_soap_response
|
||||
from authentic2.utils import cache_and_validate
|
||||
|
||||
def fill_assertion(request, saml_request, assertion, provider_id):
|
||||
'''Stuff an assertion with information extracted from the user record
|
||||
|
@ -49,6 +51,7 @@ def build_assertion(request, login):
|
|||
assertion = login.assertion
|
||||
fill_assertion(request, login.request, assertion, login.remoteProviderId)
|
||||
|
||||
@cache_and_validate(settings.LOCAL_METADATA_CACHE_TIMEOUT)
|
||||
def metadata(request):
|
||||
'''Return ID-FFv1.2 metadata for our IdP'''
|
||||
return HttpResponse(get_idff12_metadata(request, reverse(metadata)),
|
||||
|
|
|
@ -46,6 +46,7 @@ from authentic2.idp import signals as idp_signals
|
|||
# from authentic2.idp.models import *
|
||||
|
||||
from authentic2.authsaml2.models import SAML2TransientUser
|
||||
from authentic2.utils import cache_and_validate
|
||||
|
||||
'''SAMLv2 IdP implementation
|
||||
|
||||
|
@ -75,6 +76,7 @@ metadata_map = (
|
|||
)
|
||||
metadata_options = { 'key': settings.SAML_SIGNATURE_PUBLIC_KEY }
|
||||
|
||||
@cache_and_validate(settings.LOCAL_METADATA_CACHE_TIMEOUT)
|
||||
def metadata(request):
|
||||
'''Endpoint to retrieve the metadata file'''
|
||||
logger.info('metadata: return metadata')
|
||||
|
@ -252,6 +254,10 @@ def build_assertion(request, login, nid_format = 'transient', attributes=None):
|
|||
else:
|
||||
logger.debug("build_assertion: nameID not persistent, no federation management")
|
||||
federation = None
|
||||
kwargs = nameid2kwargs(login.assertion.subject.nameID)
|
||||
kwargs['entity_id'] = login.remoteProviderId
|
||||
kwargs['user'] = request.user
|
||||
logger.info('build_assertion: sending nameID %(name_id_format)s:%(name_id_content)s to %(entity_id)s for user %(user)s' % kwargs)
|
||||
if attributes:
|
||||
logger.debug("build_assertion: add attributes to the assertion")
|
||||
saml2_add_attribute_values(login.assertion, attributes)
|
||||
|
|
|
@ -90,6 +90,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
|||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.http.ConditionalGetMiddleware',
|
||||
'django.contrib.csrf.middleware.CsrfMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
|
@ -179,6 +180,7 @@ IDP_BACKENDS = [ ]
|
|||
IDP_SAML2 = True
|
||||
|
||||
# You MUST changes these keys, they are just for testing !
|
||||
LOCAL_METADATA_CACHE_TIMEOUT = 600
|
||||
SAML_SIGNATURE_PUBLIC_KEY = '''-----BEGIN CERTIFICATE-----
|
||||
MIIDIzCCAgugAwIBAgIJANUBoick1pDpMA0GCSqGSIb3DQEBBQUAMBUxEzARBgNV
|
||||
BAoTCkVudHJvdXZlcnQwHhcNMTAxMjE0MTUzMzAyWhcNMTEwMTEzMTUzMzAyWjAV
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import time
|
||||
import hashlib
|
||||
import datetime as dt
|
||||
|
||||
from django.views.decorators.http import condition
|
||||
from django.conf import settings
|
||||
from django.http import HttpResponse
|
||||
|
||||
class MWT(object):
|
||||
"""Memoize With Timeout"""
|
||||
|
@ -37,3 +43,62 @@ class MWT(object):
|
|||
func.func_name = f.func_name
|
||||
|
||||
return func
|
||||
|
||||
|
||||
def cache_and_validate(timeout, hashing=hashlib.md5):
|
||||
'''
|
||||
Decorator to add caching, with support for ETag and Last-modified
|
||||
validation.
|
||||
|
||||
Just give it the time for caching.
|
||||
'''
|
||||
def transform(f):
|
||||
f.cache = dict()
|
||||
def get_content(request, *args, **kwargs):
|
||||
'''
|
||||
Content is kept as
|
||||
|
||||
(last_generation_time, last_modified_time, etag, content)
|
||||
|
||||
inside the f.cache dictionnary
|
||||
'''
|
||||
key=args+tuple(sorted(kwargs.items()))
|
||||
if request.method == 'PURGE' and request.environ.get('REMOTE_ADDR') \
|
||||
in settings.INTERNAL_IPS:
|
||||
# purge the cache place
|
||||
f.cache.pop(key, None)
|
||||
now = dt.datetime.now()
|
||||
if key in f.cache:
|
||||
date, last_modified, etag, mime_type, old_content = f.cache[key]
|
||||
if now - date < dt.timedelta(seconds=timeout):
|
||||
return date, last_modified, etag, mime_type, old_content
|
||||
else:
|
||||
content = f(request, *args, **kwargs)
|
||||
if old_content == content.content:
|
||||
data = (now, last_modified, etag, old_content)
|
||||
return data
|
||||
else:
|
||||
content = f(request, *args, **kwargs)
|
||||
if content.status_code == 200:
|
||||
content_type = content.get('Content-Type', None)
|
||||
data = now, now, hashing(content.content).hexdigest(), content_type, content.content
|
||||
f.cache[key] = data
|
||||
else:
|
||||
data = None, None, None, None, content
|
||||
return data
|
||||
def get_last_modified(request, *args, **kwargs):
|
||||
_, last_modified, _, _, _ = get_content(request, *args, **kwargs)
|
||||
return last_modified
|
||||
def get_etag(request, *args, **kwargs):
|
||||
_, _, etag, _, _ = get_content(request, *args, **kwargs)
|
||||
return etag
|
||||
@condition(etag_func=get_etag, last_modified_func=get_last_modified)
|
||||
def replacement(request, *args, **kwargs):
|
||||
_, _, _, content_type, content = get_content(request, *args, **kwargs)
|
||||
if isinstance(content, basestring):
|
||||
return HttpResponse(content, content_type=content_type)
|
||||
else:
|
||||
return content
|
||||
return replacement
|
||||
return transform
|
||||
|
||||
|
|
Loading…
Reference in New Issue