Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions social/backends/open_id.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,16 @@
from calendar import timegm

from jwt import InvalidTokenError, decode as jwt_decode

from openid.consumer.consumer import Consumer, SUCCESS, CANCEL, FAILURE
from openid.consumer.discover import DiscoveryFailure
from openid.extensions import sreg, ax, pape

from social.utils import url_add_parameters
from social.exceptions import AuthException, AuthFailed, AuthCanceled, \
AuthUnknownError, AuthMissingParameter, \
AuthTokenError
from social.backends.base import BaseAuth
from social.backends.oauth import BaseOAuth2

from social.exceptions import (
AuthException, AuthFailed, AuthCanceled, AuthUnknownError, AuthMissingParameter, AuthTokenError
)
from social.utils import url_add_parameters

# OpenID configuration
OLD_AX_ATTRS = [
Expand Down Expand Up @@ -278,6 +276,7 @@ class OpenIdConnectAuth(BaseOAuth2):
Currently only the code response type is supported.
"""
ID_TOKEN_ISSUER = None
ID_TOKEN_MAX_AGE = 600
DEFAULT_SCOPE = ['openid']
EXTRA_DATA = ['id_token', 'refresh_token', ('sub', 'id')]
# Set after access_token is retrieved
Expand Down Expand Up @@ -325,19 +324,35 @@ def validate_and_return_id_token(self, id_token):
http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation.
"""
client_id, _client_secret = self.get_key_and_secret()
decryption_key = self.setting('ID_TOKEN_DECRYPTION_KEY')

decode_kwargs = {
'algorithms': ['HS256'],
'audience': client_id,
'issuer': self.ID_TOKEN_ISSUER,
'key': self.setting('ID_TOKEN_DECRYPTION_KEY'),
'options': {
'verify_signature': True,
'verify_exp': True,
'verify_iat': True,
'verify_aud': True,
'verify_iss': True,
'require_exp': True,
'require_iat': True,
},
}
decode_kwargs.update(self.setting('ID_TOKEN_JWT_DECODE_KWARGS', {}))

try:
# Decode the JWT and raise an error if the secret is invalid or
# the response has expired.
id_token = jwt_decode(id_token, decryption_key, audience=client_id,
issuer=self.ID_TOKEN_ISSUER,
algorithms=['HS256'])
id_token = jwt_decode(id_token, **decode_kwargs)
except InvalidTokenError as err:
raise AuthTokenError(self, err)

# Verify the token was issued in the last 10 minutes
# Verify the token was issued within a specified amount of time
iat_leeway = self.setting('ID_TOKEN_MAX_AGE', self.ID_TOKEN_MAX_AGE)
utc_timestamp = timegm(datetime.datetime.utcnow().utctimetuple())
if id_token['iat'] < (utc_timestamp - 600):
if id_token['iat'] < (utc_timestamp - iat_leeway):
raise AuthTokenError(self, 'Incorrect id_token: iat')

# Validate the nonce to ensure the request was not modified
Expand Down
25 changes: 12 additions & 13 deletions social/tests/backends/open_id.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
# -*- coding: utf-8 -*-
from calendar import timegm

import sys
import json
import datetime
import json
import sys
from calendar import timegm

import requests
import jwt

import requests
from openid import oidutil


Expand Down Expand Up @@ -127,14 +125,15 @@ class OpenIdConnectTestMixin(object):
client_key = 'a-key'
client_secret = 'a-secret-key'
issuer = None # id_token issuer
id_token_max_age = 600 # seconds

def extra_settings(self):
settings = super(OpenIdConnectTestMixin, self).extra_settings()
settings.update({
'SOCIAL_AUTH_{0}_KEY'.format(self.name): self.client_key,
'SOCIAL_AUTH_{0}_SECRET'.format(self.name): self.client_secret,
'SOCIAL_AUTH_{0}_ID_TOKEN_DECRYPTION_KEY'.format(self.name):
self.client_secret
'SOCIAL_AUTH_{0}_ID_TOKEN_DECRYPTION_KEY'.format(self.name): self.client_secret,
'SOCIAL_AUTH_{0}_ID_TOKEN_MAX_AGE'.format(self.name): self.id_token_max_age,
})
return settings

Expand Down Expand Up @@ -197,11 +196,11 @@ def prepare_access_token_body(self, client_key=None, client_secret=None,
algorithm='HS256').decode('utf-8')
return json.dumps(body)

def authtoken_raised(self, expected_message, **access_token_kwargs):
def authtoken_raised(self, expected_message_regexp, **access_token_kwargs):
self.access_token_body = self.prepare_access_token_body(
**access_token_kwargs
)
with self.assertRaisesRegexp(AuthTokenError, expected_message):
with self.assertRaisesRegexp(AuthTokenError, expected_message_regexp):
self.do_login()

def test_invalid_secret(self):
Expand All @@ -225,10 +224,10 @@ def test_invalid_audience(self):
client_key='someone-else')

def test_invalid_issue_time(self):
expiration_datetime = datetime.datetime.utcnow() - \
datetime.timedelta(hours=1)
issue_datetime = datetime.datetime.utcnow() - \
datetime.timedelta(seconds=self.id_token_max_age + 1)
self.authtoken_raised('Token error: Incorrect id_token: iat',
issue_datetime=expiration_datetime)
issue_datetime=issue_datetime)

def test_invalid_nonce(self):
self.authtoken_raised(
Expand Down