Thanks to visit codestin.com
Credit goes to github.com

Skip to content

OIDC: Raise error=invalid_request when nonce is mandatory #656

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Feb 28, 2019
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
23 changes: 0 additions & 23 deletions oauthlib/openid/connect/core/grant_types/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,28 +247,5 @@ def openid_authorization_validator(self, request):

return request_info

def openid_implicit_authorization_validator(self, request):
"""Additional validation when following the implicit flow.
"""
# Undefined in OpenID Connect, fall back to OAuth2 definition.
if request.response_type == 'token':
return {}

# Treat it as normal OAuth 2 auth code request if openid is not present
if not request.scopes or 'openid' not in request.scopes:
return {}

# REQUIRED. String value used to associate a Client session with an ID
# Token, and to mitigate replay attacks. The value is passed through
# unmodified from the Authentication Request to the ID Token.
# Sufficient entropy MUST be present in the nonce values used to
# prevent attackers from guessing values. For implementation notes, see
# Section 15.5.2.
if not request.nonce:
desc = 'Request is missing mandatory nonce parameter.'
raise InvalidRequestError(request=request, description=desc)

return {}


OpenIDConnectBase = GrantTypeBase
25 changes: 25 additions & 0 deletions oauthlib/openid/connect/core/grant_types/hybrid.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import logging

from oauthlib.oauth2.rfc6749.grant_types.authorization_code import AuthorizationCodeGrant as OAuth2AuthorizationCodeGrant
from oauthlib.oauth2.rfc6749.errors import InvalidRequestError

from .base import GrantTypeBase
from ..request_validator import RequestValidator
Expand All @@ -34,3 +35,27 @@ def __init__(self, request_validator=None, **kwargs):
self.register_code_modifier(self.add_token)
self.register_code_modifier(self.add_id_token)
self.register_token_modifier(self.add_id_token)

def openid_authorization_validator(self, request):
"""Additional validation when following the Authorization Code flow.
"""
request_info = super(HybridGrant, self).openid_authorization_validator(request)
if not request_info: # returns immediately if OAuth2.0
return request_info

# REQUIRED if the Response Type of the request is `code
# id_token` or `code id_token token` and OPTIONAL when the
# Response Type of the request is `code token`. It is a string
# value used to associate a Client session with an ID Token,
# and to mitigate replay attacks. The value is passed through
# unmodified from the Authentication Request to the ID
# Token. Sufficient entropy MUST be present in the `nonce`
# values used to prevent attackers from guessing values. For
# implementation notes, see Section 15.5.2.
if request.response_type in ["code id_token", "code id_token token"]:
if not request.nonce:
raise InvalidRequestError(
request=request,
description='Request is missing mandatory nonce parameter.'
)
return request_info
23 changes: 21 additions & 2 deletions oauthlib/openid/connect/core/grant_types/implicit.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .base import GrantTypeBase

from oauthlib.oauth2.rfc6749.grant_types.implicit import ImplicitGrant as OAuth2ImplicitGrant
from oauthlib.oauth2.rfc6749.errors import InvalidRequestError

log = logging.getLogger(__name__)

Expand All @@ -23,11 +24,29 @@ def __init__(self, request_validator=None, **kwargs):
self.register_response_type('id_token token')
self.custom_validators.post_auth.append(
self.openid_authorization_validator)
self.custom_validators.post_auth.append(
self.openid_implicit_authorization_validator)
self.register_token_modifier(self.add_id_token)

def add_id_token(self, token, token_handler, request):
if 'state' not in token:
token['state'] = request.state
return super(ImplicitGrant, self).add_id_token(token, token_handler, request)

def openid_authorization_validator(self, request):
"""Additional validation when following the implicit flow.
"""
request_info = super(ImplicitGrant, self).openid_authorization_validator(request)
if not request_info: # returns immediately if OAuth2.0
return request_info

# REQUIRED. String value used to associate a Client session with an ID
# Token, and to mitigate replay attacks. The value is passed through
# unmodified from the Authentication Request to the ID Token.
# Sufficient entropy MUST be present in the nonce values used to
# prevent attackers from guessing values. For implementation notes, see
# Section 15.5.2.
if not request.nonce:
raise InvalidRequestError(
request=request,
description='Request is missing mandatory nonce parameter.'
)
return request_info
14 changes: 14 additions & 0 deletions tests/openid/connect/core/grant_types/test_authorization_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ def setUp(self):
self.request.grant_type = 'authorization_code'
self.request.redirect_uri = 'https://a.b/cb'
self.request.state = 'abc'
self.request.nonce = None

self.mock_validator = mock.MagicMock()
self.mock_validator.authenticate_client.side_effect = self.set_client
Expand Down Expand Up @@ -147,3 +148,16 @@ def test_create_token_response(self):
self.assertIn('scope', token)
self.assertNotIn('id_token', token)
self.assertNotIn('openid', token['scope'])

@mock.patch('oauthlib.common.generate_token')
def test_optional_nonce(self, generate_token):
generate_token.return_value = 'abc'
self.request.nonce = 'xyz'
scope, info = self.auth.validate_authorization_request(self.request)

bearer = BearerToken(self.mock_validator)
self.request.response_mode = 'query'
h, b, s = self.auth.create_authorization_response(self.request, bearer)
self.assertURLEqual(h['Location'], self.url_query)
self.assertEqual(b, None)
self.assertEqual(s, 302)
80 changes: 79 additions & 1 deletion tests/openid/connect/core/grant_types/test_hybrid.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import, unicode_literals
from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant

import mock

from oauthlib.oauth2.rfc6749 import errors
from oauthlib.oauth2.rfc6749.tokens import BearerToken
from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant
from tests.oauth2.rfc6749.grant_types.test_authorization_code import \
AuthorizationCodeGrantTest
from .test_authorization_code import OpenIDAuthCodeTest


class OpenIDHybridInterferenceTest(AuthorizationCodeGrantTest):
Expand All @@ -12,3 +17,76 @@ class OpenIDHybridInterferenceTest(AuthorizationCodeGrantTest):
def setUp(self):
super(OpenIDHybridInterferenceTest, self).setUp()
self.auth = HybridGrant(request_validator=self.mock_validator)


class OpenIDHybridCodeTokenTest(OpenIDAuthCodeTest):

def setUp(self):
super(OpenIDHybridCodeTokenTest, self).setUp()
self.request.response_type = 'code token'
self.request.nonce = None
self.auth = HybridGrant(request_validator=self.mock_validator)
self.url_query = 'https://a.b/cb?code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc'
self.url_fragment = 'https://a.b/cb#code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc'

@mock.patch('oauthlib.common.generate_token')
def test_optional_nonce(self, generate_token):
generate_token.return_value = 'abc'
self.request.nonce = 'xyz'
scope, info = self.auth.validate_authorization_request(self.request)

bearer = BearerToken(self.mock_validator)
h, b, s = self.auth.create_authorization_response(self.request, bearer)
self.assertURLEqual(h['Location'], self.url_fragment, parse_fragment=True)
self.assertEqual(b, None)
self.assertEqual(s, 302)


class OpenIDHybridCodeIdTokenTest(OpenIDAuthCodeTest):

def setUp(self):
super(OpenIDHybridCodeIdTokenTest, self).setUp()
self.mock_validator.get_code_challenge.return_value = None
self.request.response_type = 'code id_token'
self.request.nonce = 'zxc'
self.auth = HybridGrant(request_validator=self.mock_validator)
token = 'MOCKED_TOKEN'
self.url_query = 'https://a.b/cb?code=abc&state=abc&id_token=%s' % token
self.url_fragment = 'https://a.b/cb#code=abc&state=abc&id_token=%s' % token

@mock.patch('oauthlib.common.generate_token')
def test_required_nonce(self, generate_token):
generate_token.return_value = 'abc'
self.request.nonce = None
self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request)

bearer = BearerToken(self.mock_validator)
h, b, s = self.auth.create_authorization_response(self.request, bearer)
self.assertIn('error=invalid_request', h['Location'])
self.assertEqual(b, None)
self.assertEqual(s, 302)


class OpenIDHybridCodeIdTokenTokenTest(OpenIDAuthCodeTest):

def setUp(self):
super(OpenIDHybridCodeIdTokenTokenTest, self).setUp()
self.mock_validator.get_code_challenge.return_value = None
self.request.response_type = 'code id_token token'
self.request.nonce = 'xyz'
self.auth = HybridGrant(request_validator=self.mock_validator)
token = 'MOCKED_TOKEN'
self.url_query = 'https://a.b/cb?code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc&id_token=%s' % token
self.url_fragment = 'https://a.b/cb#code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc&id_token=%s' % token

@mock.patch('oauthlib.common.generate_token')
def test_required_nonce(self, generate_token):
generate_token.return_value = 'abc'
self.request.nonce = None
self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request)

bearer = BearerToken(self.mock_validator)
h, b, s = self.auth.create_authorization_response(self.request, bearer)
self.assertIn('error=invalid_request', h['Location'])
self.assertEqual(b, None)
self.assertEqual(s, 302)
63 changes: 28 additions & 35 deletions tests/openid/connect/core/grant_types/test_implicit.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import mock

from oauthlib.common import Request
from oauthlib.oauth2.rfc6749 import errors
from oauthlib.oauth2.rfc6749.tokens import BearerToken
from oauthlib.openid.connect.core.grant_types.exceptions import OIDCNoPrompt
from oauthlib.openid.connect.core.grant_types.hybrid import HybridGrant
from oauthlib.openid.connect.core.grant_types.implicit import ImplicitGrant
from tests.oauth2.rfc6749.grant_types.test_implicit import ImplicitGrantTest
from tests.unittest import TestCase
from .test_authorization_code import get_id_token_mock, OpenIDAuthCodeTest
from .test_authorization_code import get_id_token_mock


class OpenIDImplicitInterferenceTest(ImplicitGrantTest):
Expand All @@ -30,8 +30,8 @@ def setUp(self):
self.request.client_id = 'abcdef'
self.request.response_type = 'id_token token'
self.request.redirect_uri = 'https://a.b/cb'
self.request.nonce = 'zxc'
self.request.state = 'abc'
self.request.nonce = 'xyz'

self.mock_validator = mock.MagicMock()
self.mock_validator.get_id_token.side_effect = get_id_token_mock
Expand Down Expand Up @@ -61,12 +61,6 @@ def test_authorization(self, generate_token):
self.assertEqual(b, None)
self.assertEqual(s, 302)

self.request.nonce = None
h, b, s = self.auth.create_authorization_response(self.request, bearer)
self.assertIn('error=invalid_request', h['Location'])
self.assertEqual(b, None)
self.assertEqual(s, 302)

@mock.patch('oauthlib.common.generate_token')
def test_no_prompt_authorization(self, generate_token):
generate_token.return_value = 'abc'
Expand Down Expand Up @@ -105,36 +99,35 @@ def test_no_prompt_authorization(self, generate_token):
h, b, s = self.auth.create_authorization_response(self.request, bearer)
self.assertIn('error=login_required', h['Location'])

@mock.patch('oauthlib.common.generate_token')
def test_required_nonce(self, generate_token):
generate_token.return_value = 'abc'
self.request.nonce = None
self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request)

class OpenIDHybridCodeTokenTest(OpenIDAuthCodeTest):

def setUp(self):
super(OpenIDHybridCodeTokenTest, self).setUp()
self.request.response_type = 'code token'
self.auth = HybridGrant(request_validator=self.mock_validator)
self.url_query = 'https://a.b/cb?code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc'
self.url_fragment = 'https://a.b/cb#code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc'

bearer = BearerToken(self.mock_validator)
h, b, s = self.auth.create_authorization_response(self.request, bearer)
self.assertIn('error=invalid_request', h['Location'])
self.assertEqual(b, None)
self.assertEqual(s, 302)

class OpenIDHybridCodeIdTokenTest(OpenIDAuthCodeTest):

class OpenIDImplicitNoAccessTokenTest(OpenIDImplicitTest):
def setUp(self):
super(OpenIDHybridCodeIdTokenTest, self).setUp()
self.mock_validator.get_code_challenge.return_value = None
self.request.response_type = 'code id_token'
self.auth = HybridGrant(request_validator=self.mock_validator)
super(OpenIDImplicitNoAccessTokenTest, self).setUp()
self.request.response_type = 'id_token'
token = 'MOCKED_TOKEN'
self.url_query = 'https://a.b/cb?code=abc&state=abc&id_token=%s' % token
self.url_fragment = 'https://a.b/cb#code=abc&state=abc&id_token=%s' % token

self.url_query = 'https://a.b/cb?state=abc&id_token=%s' % token
self.url_fragment = 'https://a.b/cb#state=abc&id_token=%s' % token

class OpenIDHybridCodeIdTokenTokenTest(OpenIDAuthCodeTest):
@mock.patch('oauthlib.common.generate_token')
def test_required_nonce(self, generate_token):
generate_token.return_value = 'abc'
self.request.nonce = None
self.assertRaises(errors.InvalidRequestError, self.auth.validate_authorization_request, self.request)

def setUp(self):
super(OpenIDHybridCodeIdTokenTokenTest, self).setUp()
self.mock_validator.get_code_challenge.return_value = None
self.request.response_type = 'code id_token token'
self.auth = HybridGrant(request_validator=self.mock_validator)
token = 'MOCKED_TOKEN'
self.url_query = 'https://a.b/cb?code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc&id_token=%s' % token
self.url_fragment = 'https://a.b/cb#code=abc&state=abc&token_type=Bearer&expires_in=3600&scope=hello+openid&access_token=abc&id_token=%s' % token
bearer = BearerToken(self.mock_validator)
h, b, s = self.auth.create_authorization_response(self.request, bearer)
self.assertIn('error=invalid_request', h['Location'])
self.assertEqual(b, None)
self.assertEqual(s, 302)