Commit 2b4817b1 by Clinton Blackburn

Added OpenID Connect discovery endpoint

Although we are phasing out our support of OIDC, this particular feature will allow us to eliminate many of the settings we
share across services. Instead of reading various endpoints and secret keys from settings or hardcoded values, services
with the proper authentication backend can simply read (and cache) the information from this endpoint.

ECOM-3629
parent 0df079a9
...@@ -848,8 +848,8 @@ CREDIT_HELP_LINK_URL = ENV_TOKENS.get('CREDIT_HELP_LINK_URL', CREDIT_HELP_LINK_U ...@@ -848,8 +848,8 @@ CREDIT_HELP_LINK_URL = ENV_TOKENS.get('CREDIT_HELP_LINK_URL', CREDIT_HELP_LINK_U
#### JWT configuration #### #### JWT configuration ####
JWT_AUTH.update(ENV_TOKENS.get('JWT_AUTH', {})) JWT_AUTH.update(ENV_TOKENS.get('JWT_AUTH', {}))
PUBLIC_RSA_KEY = ENV_TOKENS.get('PUBLIC_RSA_KEY', PUBLIC_RSA_KEY) JWT_PRIVATE_SIGNING_KEY = ENV_TOKENS.get('JWT_PRIVATE_SIGNING_KEY', JWT_PRIVATE_SIGNING_KEY)
PRIVATE_RSA_KEY = ENV_TOKENS.get('PRIVATE_RSA_KEY', PRIVATE_RSA_KEY) JWT_EXPIRED_PRIVATE_SIGNING_KEYS = ENV_TOKENS.get('JWT_EXPIRED_PRIVATE_SIGNING_KEYS', JWT_EXPIRED_PRIVATE_SIGNING_KEYS)
################# PROCTORING CONFIGURATION ################## ################# PROCTORING CONFIGURATION ##################
......
...@@ -2975,8 +2975,8 @@ LTI_AGGREGATE_SCORE_PASSBACK_DELAY = 15 * 60 ...@@ -2975,8 +2975,8 @@ LTI_AGGREGATE_SCORE_PASSBACK_DELAY = 15 * 60
# For help generating a key pair import and run `openedx.core.lib.rsa_key_utils.generate_rsa_key_pair()` # For help generating a key pair import and run `openedx.core.lib.rsa_key_utils.generate_rsa_key_pair()`
PUBLIC_RSA_KEY = None JWT_PRIVATE_SIGNING_KEY = None
PRIVATE_RSA_KEY = None JWT_EXPIRED_PRIVATE_SIGNING_KEYS = []
# Credit notifications settings # Credit notifications settings
NOTIFICATION_EMAIL_CSS = "templates/credit_notifications/credit_notification.css" NOTIFICATION_EMAIL_CSS = "templates/credit_notifications/credit_notification.css"
......
...@@ -225,18 +225,7 @@ CORS_ORIGIN_WHITELIST = () ...@@ -225,18 +225,7 @@ CORS_ORIGIN_WHITELIST = ()
CORS_ORIGIN_ALLOW_ALL = True CORS_ORIGIN_ALLOW_ALL = True
# JWT settings for devstack # JWT settings for devstack
PUBLIC_RSA_KEY = """\ JWT_PRIVATE_SIGNING_KEY = """\
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCujf5oZBGK4MafMRGY9
+zdRRI9YDm1r+81coDCysSrwkhTkFIwP2dmS6lYvJuQ5wifuQa3WFv1Kh9Nr2XRJ
1m9OL3/JpmMyTi/YuwD7tIf65tab1SOSRYkoxOKRuuvZuXQG9nWbXrGDncnwuWxf
eymwWaIrAhALUS5+nDa7dauj8VngsWauMrEA/MWShEzsR53wGKlciEZA1r/AfQ55
XS42GvBobhhy9SeZ3B6LHiaAEywpwFmKPssuoHSNhbPa49LW3gXJ6CsFGRDcBFKd
xJ/l8O847Q7kg1lvckpLsKyu5167NK9Qj1X/O3SwVBL3cxx1HpQ6+q3SGLZ4ngow
hwIDAQAB
-----END PUBLIC KEY-----"""
PRIVATE_RSA_KEY = """\
-----BEGIN PRIVATE KEY----- -----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCkK6N/mhkEYrgx MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCkK6N/mhkEYrgx
p8xEZj37N1FEj1gObWv7zVygMLKxKvCSFOQUjA/Z2ZLqVi8m5DnCJ+5BrdYW/UqH p8xEZj37N1FEj1gObWv7zVygMLKxKvCSFOQUjA/Z2ZLqVi8m5DnCJ+5BrdYW/UqH
......
...@@ -3,25 +3,31 @@ Tests for Blocks Views ...@@ -3,25 +3,31 @@ Tests for Blocks Views
""" """
import json import json
import unittest
import ddt import ddt
import httpretty
from Crypto.PublicKey import RSA
from django.conf import settings from django.conf import settings
from django.test import RequestFactory, TestCase
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
import httpretty from django.test import RequestFactory, TestCase, override_settings
from oauth2_provider import models as dot_models from oauth2_provider import models as dot_models
from provider import constants from provider import constants
import unittest
from student.tests.factories import UserFactory from student.tests.factories import UserFactory
from third_party_auth.tests.utils import ThirdPartyOAuthTestMixin, ThirdPartyOAuthTestMixinGoogle from third_party_auth.tests.utils import ThirdPartyOAuthTestMixin, ThirdPartyOAuthTestMixinGoogle
from .constants import DUMMY_REDIRECT_URL
from . import mixins from . import mixins
from .constants import DUMMY_REDIRECT_URL
from .. import adapters from .. import adapters
from .. import models from .. import models
if settings.FEATURES.get("ENABLE_OAUTH2_PROVIDER"): # NOTE (CCB): We use this feature flag in a roundabout way to determine if the oauth_dispatch app is installed
# in the current service--LMS or Studio. Normally we would check if settings.ROOT_URLCONF == 'lms.urls'; however,
# simply importing the views will results in an error due to the requisite apps not being installed (in Studio). Thus,
# we are left with this hack, of checking the feature flag which will never be True for Studio.
OAUTH_PROVIDER_ENABLED = settings.FEATURES.get('ENABLE_OAUTH2_PROVIDER')
if OAUTH_PROVIDER_ENABLED:
from .. import views from .. import views
...@@ -62,7 +68,7 @@ class AccessTokenLoginMixin(object): ...@@ -62,7 +68,7 @@ class AccessTokenLoginMixin(object):
self.assertEqual(self.login_with_access_token(access_token=access_token).status_code, 401) self.assertEqual(self.login_with_access_token(access_token=access_token).status_code, 401)
@unittest.skipUnless(settings.FEATURES.get("ENABLE_OAUTH2_PROVIDER"), "OAuth2 not enabled") @unittest.skipUnless(OAUTH_PROVIDER_ENABLED, 'OAuth2 not enabled')
class _DispatchingViewTestCase(TestCase): class _DispatchingViewTestCase(TestCase):
""" """
Base class for tests that exercise DispatchingViews. Base class for tests that exercise DispatchingViews.
...@@ -117,6 +123,7 @@ class TestAccessTokenView(AccessTokenLoginMixin, mixins.AccessTokenMixin, _Dispa ...@@ -117,6 +123,7 @@ class TestAccessTokenView(AccessTokenLoginMixin, mixins.AccessTokenMixin, _Dispa
""" """
Test class for AccessTokenView Test class for AccessTokenView
""" """
def setUp(self): def setUp(self):
super(TestAccessTokenView, self).setUp() super(TestAccessTokenView, self).setUp()
self.url = reverse('access_token') self.url = reverse('access_token')
...@@ -235,6 +242,7 @@ class TestAccessTokenExchangeView(ThirdPartyOAuthTestMixinGoogle, ThirdPartyOAut ...@@ -235,6 +242,7 @@ class TestAccessTokenExchangeView(ThirdPartyOAuthTestMixinGoogle, ThirdPartyOAut
""" """
Test class for AccessTokenExchangeView Test class for AccessTokenExchangeView
""" """
def setUp(self): def setUp(self):
self.url = reverse('exchange_access_token', kwargs={'backend': 'google-oauth2'}) self.url = reverse('exchange_access_token', kwargs={'backend': 'google-oauth2'})
self.view_class = views.AccessTokenExchangeView self.view_class = views.AccessTokenExchangeView
...@@ -385,7 +393,7 @@ class TestAuthorizationView(_DispatchingViewTestCase): ...@@ -385,7 +393,7 @@ class TestAuthorizationView(_DispatchingViewTestCase):
return response.redirect_chain[-1][0] return response.redirect_chain[-1][0]
@unittest.skipUnless(settings.FEATURES.get("ENABLE_OAUTH2_PROVIDER"), "OAuth2 not enabled") @unittest.skipUnless(OAUTH_PROVIDER_ENABLED, 'OAuth2 not enabled')
class TestViewDispatch(TestCase): class TestViewDispatch(TestCase):
""" """
Test that the DispatchingView dispatches the right way. Test that the DispatchingView dispatches the right way.
...@@ -479,6 +487,7 @@ class TestRevokeTokenView(AccessTokenLoginMixin, _DispatchingViewTestCase): # p ...@@ -479,6 +487,7 @@ class TestRevokeTokenView(AccessTokenLoginMixin, _DispatchingViewTestCase): # p
""" """
Test class for RevokeTokenView Test class for RevokeTokenView
""" """
def setUp(self): def setUp(self):
self.revoke_token_url = reverse('revoke_token') self.revoke_token_url = reverse('revoke_token')
self.access_token_url = reverse('access_token') self.access_token_url = reverse('access_token')
...@@ -554,3 +563,104 @@ class TestRevokeTokenView(AccessTokenLoginMixin, _DispatchingViewTestCase): # p ...@@ -554,3 +563,104 @@ class TestRevokeTokenView(AccessTokenLoginMixin, _DispatchingViewTestCase): # p
Tests invalidation/revoke of user access token for django-oauth-toolkit Tests invalidation/revoke of user access token for django-oauth-toolkit
""" """
self.verify_revoke_token(self.access_token) self.verify_revoke_token(self.access_token)
@unittest.skipUnless(OAUTH_PROVIDER_ENABLED, 'OAuth2 not enabled')
class JwksViewTests(TestCase):
def test_serialize_rsa_key(self):
key = """\
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCkK6N/mhkEYrgx
p8xEZj37N1FEj1gObWv7zVygMLKxKvCSFOQUjA/Z2ZLqVi8m5DnCJ+5BrdYW/UqH
02vZdEnWb04vf8mmYzJOL9i7APu0h/rm1pvVI5JFiSjE4pG669m5dAb2dZtesYOd
yfC5bF97KbBZoisCEAtRLn6cNrt1q6PxWeCxZq4ysQD8xZKETOxHnfAYqVyIRkDW
v8B9DnldLjYa8GhuGHL1J5ncHoseJoATLCnAWYo+yy6gdI2Fs9rj0tbeBcnoKwUZ
ENwEUp3En+Xw7zjtDuSDWW9ySkuwrK7nXrs0r1CPVf87dLBUEvdzHHUelDr6rdIY
tnieCjCHAgMBAAECggEBAJvTiAdQPzq4cVlAilTKLz7KTOsknFJlbj+9t5OdZZ9g
wKQIDE2sfEcti5O+Zlcl/eTaff39gN6lYR73gMEQ7h0J3U6cnsy+DzvDkpY94qyC
/ZYqUhPHBcnW3Mm0vNqNj0XGae15yBXjrKgSy9lUknSXJ3qMwQHeNL/DwA2KrfiL
g0iVjk32dvSSHWcBh0M+Qy1WyZU0cf9VWzx+Q1YLj9eUCHteStVubB610XV3JUZt
UTWiUCffpo2okHsTBuKPVXK/5BL+BpGplcxRSlnSbMaI611kN3iKlO8KGISXHBz7
nOPdkfZC9poEXt5SshtINuGGCCc8hDxpg1otYqCLaYECgYEA1MSCPs3pBkEagchV
g0rxYmDUC8QkeIOBuZFjhkdoUgZ6rFntyRZd1NbCUi3YBbV1YC12ZGohqWUWom1S
AtNbQ2ZTbqEnDKWbNvLBRwkdp/9cKBce85lCCD6+U2o2Ha8C0+hKeLBn8un1y0zY
1AQTqLAz9ItNr0aDPb89cs5voWcCgYEAxYdC8vR3t8iYMUnK6LWYDrKSt7YiorvF
qXIMANcXQrnO0ptC0B56qrUCgKHNrtPi5bGpNBJ0oKMfbmGfwX+ca8sCUlLvq/O8
S2WZwSJuaHH4lEBi8ErtY++8F4B4l3ENCT84Hyy5jiMpbpkHEnh/1GNcvvmyI8ud
3jzovCNZ4+ECgYEA0r+Oz0zAOzyzV8gqw7Cw5iRJBRqUkXaZQUj8jt4eO9lFG4C8
IolwCclrk2Drb8Qsbka51X62twZ1ZA/qwve9l0Y88ADaIBHNa6EKxyUFZglvrBoy
w1GT8XzMou06iy52G5YkZeU+IYOSvnvw7hjXrChUXi65lRrAFqJd6GEIe5MCgYA/
0LxDa9HFsWvh+JoyZoCytuSJr7Eu7AUnAi54kwTzzL3R8tE6Fa7BuesODbg6tD/I
v4YPyaqePzUnXyjSxdyOQq8EU8EUx5Dctv1elTYgTjnmA4szYLGjKM+WtC3Bl4eD
pkYGZFeqYRfAoHXVdNKvlk5fcKIpyF2/b+Qs7CrdYQKBgQCc/t+JxC9OpI+LhQtB
tEtwvklxuaBtoEEKJ76P9vrK1semHQ34M1XyNmvPCXUyKEI38MWtgCCXcdmg5syO
PBXdDINx+wKlW7LPgaiRL0Mi9G2aBpdFNI99CWVgCr88xqgSE24KsOxViMwmi0XB
Ld/IRK0DgpGP5EJRwpKsDYe/UQ==
-----END PRIVATE KEY-----"""
# pylint: disable=line-too-long
expected = {
'kty': 'RSA',
'use': 'sig',
'alg': 'RS512',
'n': 'pCujf5oZBGK4MafMRGY9-zdRRI9YDm1r-81coDCysSrwkhTkFIwP2dmS6lYvJuQ5wifuQa3WFv1Kh9Nr2XRJ1m9OL3_JpmMyTi_YuwD7tIf65tab1SOSRYkoxOKRuuvZuXQG9nWbXrGDncnwuWxfeymwWaIrAhALUS5-nDa7dauj8VngsWauMrEA_MWShEzsR53wGKlciEZA1r_AfQ55XS42GvBobhhy9SeZ3B6LHiaAEywpwFmKPssuoHSNhbPa49LW3gXJ6CsFGRDcBFKdxJ_l8O847Q7kg1lvckpLsKyu5167NK9Qj1X_O3SwVBL3cxx1HpQ6-q3SGLZ4ngowhw',
'e': 'AQAB',
'kid': '6e80b9d2e5075ae8bb5d1dd762ebc62e'
}
self.assertEqual(views.JwksView.serialize_rsa_key(key), expected)
def test_get(self):
JWT_PRIVATE_SIGNING_KEY = RSA.generate(2048).exportKey('PEM')
JWT_EXPIRED_PRIVATE_SIGNING_KEYS = [RSA.generate(2048).exportKey('PEM'), RSA.generate(2048).exportKey('PEM')]
secret_keys = [JWT_PRIVATE_SIGNING_KEY] + JWT_EXPIRED_PRIVATE_SIGNING_KEYS
with override_settings(JWT_PRIVATE_SIGNING_KEY=JWT_PRIVATE_SIGNING_KEY,
JWT_EXPIRED_PRIVATE_SIGNING_KEYS=JWT_EXPIRED_PRIVATE_SIGNING_KEYS):
response = self.client.get(reverse('jwks'))
self.assertEqual(response.status_code, 200)
actual = json.loads(response.content)
expected = {
'keys': [views.JwksView.serialize_rsa_key(key) for key in secret_keys],
}
self.assertEqual(actual, expected)
@override_settings(JWT_PRIVATE_SIGNING_KEY=None, JWT_EXPIRED_PRIVATE_SIGNING_KEYS=[])
def test_get_without_keys(self):
""" The view should return an empty list if no keys are configured. """
response = self.client.get(reverse('jwks'))
self.assertEqual(response.status_code, 200)
actual = json.loads(response.content)
self.assertEqual(actual, {'keys': []})
@unittest.skipUnless(OAUTH_PROVIDER_ENABLED, 'OAuth2 not enabled')
class ProviderInfoViewTests(TestCase):
DOMAIN = 'testserver.fake'
def build_url(self, path):
return 'http://{domain}{path}'.format(domain=self.DOMAIN, path=path)
def test_get(self):
issuer = 'test-issuer'
self.client = self.client_class(SERVER_NAME=self.DOMAIN)
expected = {
'issuer': issuer,
'authorization_endpoint': self.build_url(reverse('authorize')),
'token_endpoint': self.build_url(reverse('access_token')),
'end_session_endpoint': self.build_url(reverse('logout')),
'token_endpoint_auth_methods_supported': ['client_secret_post'],
'access_token_signing_alg_values_supported': ['RS512', 'HS256'],
'scopes_supported': ['openid', 'profile', 'email'],
'claims_supported': ['sub', 'iss', 'name', 'given_name', 'family_name', 'email'],
'jwks_uri': self.build_url(reverse('jwks')),
}
with override_settings(JWT_AUTH={'JWT_ISSUER': issuer}):
response = self.client.get(reverse('openid-config'))
self.assertEqual(response.status_code, 200)
actual = json.loads(response.content)
self.assertEqual(actual, expected)
...@@ -13,7 +13,9 @@ urlpatterns = patterns( ...@@ -13,7 +13,9 @@ urlpatterns = patterns(
'', '',
url(r'^authorize/?$', csrf_exempt(views.AuthorizationView.as_view()), name='authorize'), url(r'^authorize/?$', csrf_exempt(views.AuthorizationView.as_view()), name='authorize'),
url(r'^access_token/?$', csrf_exempt(views.AccessTokenView.as_view()), name='access_token'), url(r'^access_token/?$', csrf_exempt(views.AccessTokenView.as_view()), name='access_token'),
url(r'^revoke_token/?$', csrf_exempt(views.RevokeTokenView.as_view()), name="revoke_token"), url(r'^revoke_token/?$', csrf_exempt(views.RevokeTokenView.as_view()), name='revoke_token'),
url(r'^\.well-known/openid-configuration/?$', views.ProviderInfoView.as_view(), name='openid-config'),
url(r'^jwks\.json$', views.JwksView.as_view(), name='jwks')
) )
if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'): if settings.FEATURES.get('ENABLE_THIRD_PARTY_AUTH'):
......
...@@ -5,15 +5,20 @@ django-oauth-toolkit as appropriate. ...@@ -5,15 +5,20 @@ django-oauth-toolkit as appropriate.
from __future__ import unicode_literals from __future__ import unicode_literals
import hashlib
import json import json
from Crypto.PublicKey import RSA
from django.conf import settings
from django.core.urlresolvers import reverse
from django.http import JsonResponse
from django.views.generic import View from django.views.generic import View
from edx_oauth2_provider import views as dop_views # django-oauth2-provider views from edx_oauth2_provider import views as dop_views # django-oauth2-provider views
from jwkest.jwk import RSAKey
from oauth2_provider import models as dot_models, views as dot_views # django-oauth-toolkit from oauth2_provider import models as dot_models, views as dot_views # django-oauth-toolkit
from openedx.core.djangoapps.auth_exchange import views as auth_exchange_views from openedx.core.djangoapps.auth_exchange import views as auth_exchange_views
from openedx.core.lib.token_utils import JwtBuilder from openedx.core.lib.token_utils import JwtBuilder
from . import adapters from . import adapters
...@@ -132,3 +137,45 @@ class RevokeTokenView(_DispatchingView): ...@@ -132,3 +137,45 @@ class RevokeTokenView(_DispatchingView):
Dispatch to the RevokeTokenView of django-oauth-toolkit Dispatch to the RevokeTokenView of django-oauth-toolkit
""" """
dot_view = dot_views.RevokeTokenView dot_view = dot_views.RevokeTokenView
class ProviderInfoView(View):
def get(self, request, *args, **kwargs):
data = {
'issuer': settings.JWT_AUTH['JWT_ISSUER'],
'authorization_endpoint': request.build_absolute_uri(reverse('authorize')),
'token_endpoint': request.build_absolute_uri(reverse('access_token')),
'end_session_endpoint': request.build_absolute_uri(reverse('logout')),
'token_endpoint_auth_methods_supported': ['client_secret_post'],
# NOTE (CCB): This is not part of the OpenID Connect standard. It is added here since we
# use JWS for our access tokens.
'access_token_signing_alg_values_supported': ['RS512', 'HS256'],
'scopes_supported': ['openid', 'profile', 'email'],
'claims_supported': ['sub', 'iss', 'name', 'given_name', 'family_name', 'email'],
'jwks_uri': request.build_absolute_uri(reverse('jwks')),
}
response = JsonResponse(data)
return response
class JwksView(View):
@staticmethod
def serialize_rsa_key(key):
kid = hashlib.md5(key.encode('utf-8')).hexdigest()
key = RSAKey(kid=kid, key=RSA.importKey(key), use='sig', alg='RS512')
return key.serialize(private=False)
def get(self, request, *args, **kwargs):
secret_keys = []
if settings.JWT_PRIVATE_SIGNING_KEY:
secret_keys.append(settings.JWT_PRIVATE_SIGNING_KEY)
# NOTE: We provide the expired keys in case there are unexpired access tokens
# that need to have their signatures verified.
if settings.JWT_EXPIRED_PRIVATE_SIGNING_KEYS:
secret_keys += settings.JWT_EXPIRED_PRIVATE_SIGNING_KEYS
return JsonResponse({
'keys': [self.serialize_rsa_key(key) for key in secret_keys if key],
})
"""Utilities for working with ID tokens.""" """Utilities for working with ID tokens."""
import json
from time import time from time import time
from cryptography.hazmat.backends import default_backend from Cryptodome.PublicKey import RSA
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from django.conf import settings from django.conf import settings
from django.utils.functional import cached_property from django.utils.functional import cached_property
import jwt from jwkest.jwk import KEYS, RSAKey
from jwkest.jws import JWS
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
from student.models import UserProfile, anonymous_id_for_user from student.models import UserProfile, anonymous_id_for_user
...@@ -27,6 +28,7 @@ class JwtBuilder(object): ...@@ -27,6 +28,7 @@ class JwtBuilder(object):
asymmetric (Boolean): Whether the JWT should be signed with this app's private key. asymmetric (Boolean): Whether the JWT should be signed with this app's private key.
secret (string): Overrides configured JWT secret (signing) key. Unused if an asymmetric signature is requested. secret (string): Overrides configured JWT secret (signing) key. Unused if an asymmetric signature is requested.
""" """
def __init__(self, user, asymmetric=False, secret=None): def __init__(self, user, asymmetric=False, secret=None):
self.user = user self.user = user
self.asymmetric = asymmetric self.asymmetric = asymmetric
...@@ -50,6 +52,7 @@ class JwtBuilder(object): ...@@ -50,6 +52,7 @@ class JwtBuilder(object):
now = int(time()) now = int(time())
expires_in = expires_in or self.jwt_auth['JWT_EXPIRATION'] expires_in = expires_in or self.jwt_auth['JWT_EXPIRATION']
payload = { payload = {
# TODO Consider getting rid of this claim since we don't use it.
'aud': aud if aud else self.jwt_auth['JWT_AUDIENCE'], 'aud': aud if aud else self.jwt_auth['JWT_AUDIENCE'],
'exp': now + expires_in, 'exp': now + expires_in,
'iat': now, 'iat': now,
...@@ -100,11 +103,16 @@ class JwtBuilder(object): ...@@ -100,11 +103,16 @@ class JwtBuilder(object):
def encode(self, payload): def encode(self, payload):
"""Encode the provided payload.""" """Encode the provided payload."""
keys = KEYS()
if self.asymmetric: if self.asymmetric:
secret = load_pem_private_key(settings.PRIVATE_RSA_KEY, None, default_backend()) keys.add(RSAKey(key=RSA.importKey(settings.JWT_PRIVATE_SIGNING_KEY)))
algorithm = 'RS512' algorithm = 'RS512'
else: else:
secret = self.secret if self.secret else self.jwt_auth['JWT_SECRET_KEY'] key = self.secret if self.secret else self.jwt_auth['JWT_SECRET_KEY']
keys.add({'key': key, 'kty': 'oct'})
algorithm = self.jwt_auth['JWT_ALGORITHM'] algorithm = self.jwt_auth['JWT_ALGORITHM']
return jwt.encode(payload, secret, algorithm=algorithm) data = json.dumps(payload)
jws = JWS(data, alg=algorithm)
return jws.sign_compact(keys=keys)
...@@ -84,6 +84,8 @@ polib==1.0.3 ...@@ -84,6 +84,8 @@ polib==1.0.3
pycrypto>=2.6 pycrypto>=2.6
pygments==2.0.1 pygments==2.0.1
pygraphviz==1.1 pygraphviz==1.1
pyjwkest==1.3.2
# TODO Replace PyJWT usage with pyjwkest
PyJWT==1.4.0 PyJWT==1.4.0
pymongo==2.9.1 pymongo==2.9.1
python-memcached==1.48 python-memcached==1.48
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment