Initial Session Management version.
This commit is contained in:
parent
4d2bdb9a97
commit
50e5287b48
8 changed files with 144 additions and 1 deletions
|
@ -32,6 +32,7 @@ MIDDLEWARE_CLASSES = [
|
||||||
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
|
'oidc_provider.middleware.SessionManagementMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
TEMPLATES = [
|
TEMPLATES = [
|
||||||
|
@ -87,3 +88,4 @@ LOGIN_REDIRECT_URL = '/'
|
||||||
# OIDC Provider settings
|
# OIDC Provider settings
|
||||||
|
|
||||||
SITE_URL = 'http://localhost:8000'
|
SITE_URL = 'http://localhost:8000'
|
||||||
|
OIDC_SESSION_MANAGEMENT_ENABLE = True
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
from datetime import timedelta
|
from datetime import timedelta
|
||||||
|
from hashlib import (
|
||||||
|
md5,
|
||||||
|
sha256,
|
||||||
|
)
|
||||||
import logging
|
import logging
|
||||||
try:
|
try:
|
||||||
from urllib import urlencode
|
from urllib import urlencode
|
||||||
from urlparse import urlsplit, parse_qs, urlunsplit
|
from urlparse import urlsplit, parse_qs, urlunsplit
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from urllib.parse import urlsplit, parse_qs, urlunsplit, urlencode
|
from urllib.parse import urlsplit, parse_qs, urlunsplit, urlencode
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
|
@ -173,6 +178,29 @@ class AuthorizeEndpoint(object):
|
||||||
|
|
||||||
query_fragment['state'] = self.params['state'] if self.params['state'] else ''
|
query_fragment['state'] = self.params['state'] if self.params['state'] else ''
|
||||||
|
|
||||||
|
if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'):
|
||||||
|
# Generate client origin URI from the redirect_uri param.
|
||||||
|
redirect_uri_parsed = urlsplit(self.params['redirect_uri'])
|
||||||
|
client_origin = '{0}://{1}'.format(redirect_uri_parsed.scheme, redirect_uri_parsed.netloc)
|
||||||
|
|
||||||
|
# Create random salt.
|
||||||
|
salt = md5(uuid4().hex.encode()).hexdigest()
|
||||||
|
|
||||||
|
# The generation of suitable Session State values is based
|
||||||
|
# on a salted cryptographic hash of Client ID, origin URL,
|
||||||
|
# and OP browser state.
|
||||||
|
session_state = '{client_id} {origin} {browser_state} {salt}'.format(
|
||||||
|
client_id=self.client.client_id,
|
||||||
|
origin=client_origin,
|
||||||
|
browser_state=self.request.COOKIES['op_browser_state'],
|
||||||
|
salt=salt)
|
||||||
|
session_state = sha256(session_state).hexdigest()
|
||||||
|
session_state += '.' + salt
|
||||||
|
if self.grant_type == 'authorization_code':
|
||||||
|
query_params['session_state'] = session_state
|
||||||
|
elif self.grant_type in ['implicit', 'hybrid']:
|
||||||
|
query_fragment['session_state'] = session_state
|
||||||
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.debug('[Authorize] Error when trying to create response uri: %s', error)
|
logger.debug('[Authorize] Error when trying to create response uri: %s', error)
|
||||||
raise AuthorizeError(self.params['redirect_uri'], 'server_error', self.grant_type)
|
raise AuthorizeError(self.params['redirect_uri'], 'server_error', self.grant_type)
|
||||||
|
|
17
oidc_provider/middleware.py
Normal file
17
oidc_provider/middleware.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
from hashlib import sha224
|
||||||
|
|
||||||
|
from django.conf import settings as django_settings
|
||||||
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
|
|
||||||
|
|
||||||
|
class SessionManagementMiddleware(MiddlewareMixin):
|
||||||
|
"""
|
||||||
|
Maintain a `op_browser_state` cookie along with the `sessionid` cookie that
|
||||||
|
represents the End-User's login state at the OP. If the user is not logged
|
||||||
|
in then use `SECRET_KEY` value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def process_response(self, request, response):
|
||||||
|
session_state = sha224(request.session.session_key or django_settings.SECRET_KEY).hexdigest()
|
||||||
|
response.set_cookie('op_browser_state', session_state)
|
||||||
|
return response
|
|
@ -61,6 +61,13 @@ class DefaultSettings(object):
|
||||||
"""
|
"""
|
||||||
return 'oidc_provider.lib.utils.common.default_sub_generator'
|
return 'oidc_provider.lib.utils.common.default_sub_generator'
|
||||||
|
|
||||||
|
@property
|
||||||
|
def OIDC_SESSION_MANAGEMENT_ENABLE(self):
|
||||||
|
"""
|
||||||
|
OPTIONAL. If enabled, the Server will support Session Management 1.0 specification.
|
||||||
|
"""
|
||||||
|
return False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def OIDC_SKIP_CONSENT_ALWAYS(self):
|
def OIDC_SKIP_CONSENT_ALWAYS(self):
|
||||||
"""
|
"""
|
||||||
|
|
16
oidc_provider/static/oidc_provider/js/sha256.min.js
vendored
Normal file
16
oidc_provider/static/oidc_provider/js/sha256.min.js
vendored
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
/**
|
||||||
|
* [js-sha256]{@link https://github.com/emn178/js-sha256}
|
||||||
|
*
|
||||||
|
* @version 0.3.2
|
||||||
|
* @author Chen, Yi-Cyuan [emn178@gmail.com]
|
||||||
|
* @copyright Chen, Yi-Cyuan 2014-2016
|
||||||
|
* @license MIT
|
||||||
|
*/
|
||||||
|
(function(I){"object"==typeof process&&process.versions&&process.versions.node&&(I=global);var a="0123456789abcdef".split(""),Q=[-2147483648,8388608,32768,128],C=[24,16,8,0],L=[1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,
|
||||||
|
113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298],c=[],J=function(a){return A(a,!0)},A=function(D,A){var K="string"!=typeof D;K&&D.constructor==I.ArrayBuffer&&(D=new Uint8Array(D));var m,
|
||||||
|
n,p,q,r,t,u,v,e,J=!0,O=!1,b,B=0,M=0,P=0,N=D.length,h,d,E,F,G,H;A?(m=3238371032,n=914150663,p=812702999,q=4144912697,r=4290775857,t=1750603025,u=1694076839,v=3204075428):(m=1779033703,n=3144134277,p=1013904242,q=2773480762,r=1359893119,t=2600822924,u=528734635,v=1541459225);e=0;do{c[0]=e;c[16]=c[1]=c[2]=c[3]=c[4]=c[5]=c[6]=c[7]=c[8]=c[9]=c[10]=c[11]=c[12]=c[13]=c[14]=c[15]=0;if(K)for(b=M;B<N&&64>b;++B)c[b>>2]|=D[B]<<C[b++&3];else for(b=M;B<N&&64>b;++B)e=D.charCodeAt(B),128>e?c[b>>2]|=e<<C[b++&3]:(2048>
|
||||||
|
e?c[b>>2]|=(192|e>>6)<<C[b++&3]:(55296>e||57344<=e?c[b>>2]|=(224|e>>12)<<C[b++&3]:(e=65536+((e&1023)<<10|D.charCodeAt(++B)&1023),c[b>>2]|=(240|e>>18)<<C[b++&3],c[b>>2]|=(128|e>>12&63)<<C[b++&3]),c[b>>2]|=(128|e>>6&63)<<C[b++&3]),c[b>>2]|=(128|e&63)<<C[b++&3]);P+=b-M;M=b-64;B==N&&(c[b>>2]|=Q[b&3],++B);e=c[16];B>N&&56>b&&(c[15]=P<<3,O=!0);var w=m,k=n,l=p,f=q,x=r,y=t,z=u,g=v;for(b=16;64>b;++b)d=c[b-15],h=(d>>>7|d<<25)^(d>>>18|d<<14)^d>>>3,d=c[b-2],d=(d>>>17|d<<15)^(d>>>19|d<<13)^d>>>10,c[b]=c[b-16]+
|
||||||
|
h+c[b-7]+d<<0;H=k&l;for(b=0;64>b;b+=4)J?(A?(G=300032,d=c[0]-1413257819,g=d-150054599<<0,f=d+24177077<<0):(G=704751109,d=c[0]-210244248,g=d-1521486534<<0,f=d+143694565<<0),J=!1):(h=(w>>>2|w<<30)^(w>>>13|w<<19)^(w>>>22|w<<10),d=(x>>>6|x<<26)^(x>>>11|x<<21)^(x>>>25|x<<7),G=w&k,E=G^w&l^H,F=x&y^~x&z,d=g+d+F+L[b]+c[b],h+=E,g=f+d<<0,f=d+h<<0),h=(f>>>2|f<<30)^(f>>>13|f<<19)^(f>>>22|f<<10),d=(g>>>6|g<<26)^(g>>>11|g<<21)^(g>>>25|g<<7),H=f&w,E=H^f&k^G,F=g&x^~g&y,d=z+d+F+L[b+1]+c[b+1],h+=E,z=l+d<<0,l=d+h<<0,
|
||||||
|
h=(l>>>2|l<<30)^(l>>>13|l<<19)^(l>>>22|l<<10),d=(z>>>6|z<<26)^(z>>>11|z<<21)^(z>>>25|z<<7),G=l&f,E=G^l&w^H,F=z&g^~z&x,d=y+d+F+L[b+2]+c[b+2],h+=E,y=k+d<<0,k=d+h<<0,h=(k>>>2|k<<30)^(k>>>13|k<<19)^(k>>>22|k<<10),d=(y>>>6|y<<26)^(y>>>11|y<<21)^(y>>>25|y<<7),H=k&l,E=H^k&f^G,F=y&z^~y&g,d=x+d+F+L[b+3]+c[b+3],h+=E,x=w+d<<0,w=d+h<<0;m=m+w<<0;n=n+k<<0;p=p+l<<0;q=q+f<<0;r=r+x<<0;t=t+y<<0;u=u+z<<0;v=v+g<<0}while(!O);K=a[m>>28&15]+a[m>>24&15]+a[m>>20&15]+a[m>>16&15]+a[m>>12&15]+a[m>>8&15]+a[m>>4&15]+a[m&15]+a[n>>
|
||||||
|
28&15]+a[n>>24&15]+a[n>>20&15]+a[n>>16&15]+a[n>>12&15]+a[n>>8&15]+a[n>>4&15]+a[n&15]+a[p>>28&15]+a[p>>24&15]+a[p>>20&15]+a[p>>16&15]+a[p>>12&15]+a[p>>8&15]+a[p>>4&15]+a[p&15]+a[q>>28&15]+a[q>>24&15]+a[q>>20&15]+a[q>>16&15]+a[q>>12&15]+a[q>>8&15]+a[q>>4&15]+a[q&15]+a[r>>28&15]+a[r>>24&15]+a[r>>20&15]+a[r>>16&15]+a[r>>12&15]+a[r>>8&15]+a[r>>4&15]+a[r&15]+a[t>>28&15]+a[t>>24&15]+a[t>>20&15]+a[t>>16&15]+a[t>>12&15]+a[t>>8&15]+a[t>>4&15]+a[t&15]+a[u>>28&15]+a[u>>24&15]+a[u>>20&15]+a[u>>16&15]+a[u>>12&
|
||||||
|
15]+a[u>>8&15]+a[u>>4&15]+a[u&15];A||(K+=a[v>>28&15]+a[v>>24&15]+a[v>>20&15]+a[v>>16&15]+a[v>>12&15]+a[v>>8&15]+a[v>>4&15]+a[v&15]);return K};!I.JS_SHA256_TEST&&"object"==typeof module&&module.exports?(A.sha256=A,A.sha224=J,module.exports=A):I&&(I.sha256=A,I.sha224=J)})(this);
|
|
@ -0,0 +1,49 @@
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>OP Iframe</title>
|
||||||
|
<script src="{% static 'oidc_provider/js/sha256.min.js' %}"></script>
|
||||||
|
<script language="JavaScript" type="text/javascript">
|
||||||
|
window.addEventListener("message", receiveMessage, false);
|
||||||
|
|
||||||
|
function receiveMessage(e) {
|
||||||
|
var status;
|
||||||
|
try {
|
||||||
|
var clientId = e.data.split(' ')[0];
|
||||||
|
var sessionState = e.data.split(' ')[1];
|
||||||
|
var salt = sessionState.split('.')[1];
|
||||||
|
|
||||||
|
var browserState = getOpBrowserState();
|
||||||
|
|
||||||
|
var sessionStateCalculated = sha256(clientId + ' ' + e.origin + ' ' + browserState + ' ' + salt) + '.' + salt;
|
||||||
|
|
||||||
|
if (sessionState == sessionStateCalculated) {
|
||||||
|
status = 'unchanged';
|
||||||
|
} else {
|
||||||
|
status = 'changed';
|
||||||
|
}
|
||||||
|
} catch(err) {
|
||||||
|
status = 'error';
|
||||||
|
}
|
||||||
|
e.source.postMessage(status, e.origin);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getOpBrowserState() {
|
||||||
|
var theName = 'op_browser_state=';
|
||||||
|
var theCookie = document.cookie + ';';
|
||||||
|
var start = theCookie.indexOf(theName);
|
||||||
|
if (start != -1)
|
||||||
|
{
|
||||||
|
var end = theCookie.indexOf(';', start);
|
||||||
|
return unescape(theCookie.substring(start + theName.length, end));
|
||||||
|
}
|
||||||
|
throw new Error('We couldn\'t find the "op_browser_state" cookie.');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
OpenID Connect Session Management OP Iframe.
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,6 +1,10 @@
|
||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
from django.views.decorators.csrf import csrf_exempt
|
from django.views.decorators.csrf import csrf_exempt
|
||||||
from oidc_provider import views
|
|
||||||
|
from oidc_provider import (
|
||||||
|
settings,
|
||||||
|
views,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
@ -12,3 +16,8 @@ urlpatterns = [
|
||||||
url(r'^\.well-known/openid-configuration/?$', views.ProviderInfoView.as_view(), name='provider_info'),
|
url(r'^\.well-known/openid-configuration/?$', views.ProviderInfoView.as_view(), name='provider_info'),
|
||||||
url(r'^jwks/?$', views.JwksView.as_view(), name='jwks'),
|
url(r'^jwks/?$', views.JwksView.as_view(), name='jwks'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'):
|
||||||
|
urlpatterns += [
|
||||||
|
url(r'^check-session-iframe/?$', views.CheckSessionIframeView.as_view(), name='check-session-iframe'),
|
||||||
|
]
|
||||||
|
|
|
@ -6,6 +6,8 @@ from django.core.urlresolvers import reverse
|
||||||
from django.http import JsonResponse
|
from django.http import JsonResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.template.loader import render_to_string
|
from django.template.loader import render_to_string
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
from django.views.generic import View
|
from django.views.generic import View
|
||||||
from jwkest import long_to_base64
|
from jwkest import long_to_base64
|
||||||
|
@ -203,6 +205,9 @@ class ProviderInfoView(View):
|
||||||
dic['token_endpoint_auth_methods_supported'] = ['client_secret_post',
|
dic['token_endpoint_auth_methods_supported'] = ['client_secret_post',
|
||||||
'client_secret_basic']
|
'client_secret_basic']
|
||||||
|
|
||||||
|
if settings.get('OIDC_SESSION_MANAGEMENT_ENABLE'):
|
||||||
|
dic['check_session_iframe'] = site_url + reverse('oidc_provider:check-session-iframe')
|
||||||
|
|
||||||
response = JsonResponse(dic)
|
response = JsonResponse(dic)
|
||||||
response['Access-Control-Allow-Origin'] = '*'
|
response['Access-Control-Allow-Origin'] = '*'
|
||||||
|
|
||||||
|
@ -236,3 +241,13 @@ class LogoutView(View):
|
||||||
def get(self, request, *args, **kwargs):
|
def get(self, request, *args, **kwargs):
|
||||||
# We should actually verify if the requested redirect URI is safe
|
# We should actually verify if the requested redirect URI is safe
|
||||||
return logout(request, next_page=request.GET.get('post_logout_redirect_uri'))
|
return logout(request, next_page=request.GET.get('post_logout_redirect_uri'))
|
||||||
|
|
||||||
|
|
||||||
|
class CheckSessionIframeView(View):
|
||||||
|
|
||||||
|
@method_decorator(xframe_options_exempt)
|
||||||
|
def dispatch(self, request, *args, **kwargs):
|
||||||
|
return super(CheckSessionIframeView, self).dispatch(request, *args, **kwargs)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return render(request, 'oidc_provider/check_session_iframe.html', kwargs)
|
||||||
|
|
Loading…
Reference in a new issue