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:
Benjamin Dauvergne 2011-09-12 16:15:53 +02:00
parent f7fe8de441
commit 83206efa55
5 changed files with 78 additions and 0 deletions

View File

@ -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')

View File

@ -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)),

View File

@ -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)

View File

@ -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

View File

@ -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