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

Skip to content

Commit f74922b

Browse files
authored
Merge branch 'master' into docs-flows-hooks
2 parents 3de7007 + f516c16 commit f74922b

File tree

12 files changed

+262
-54
lines changed

12 files changed

+262
-54
lines changed

CHANGELOG.rst

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
Changelog
22
=========
33

4-
3.0.2 (TBD)
4+
3.0.2 (2019-07-04)
55
------------------
6-
* #650 Fixed space encoding in base string URI used in the signature base string.
6+
* #650: Fixed space encoding in base string URI used in the signature base string.
77
* #652: Fixed OIDC /token response which wrongly returned "&state=None"
8+
* #654: Doc: The value `state` must not be stored by the AS, only returned in /authorize response.
9+
* #656: Fixed OIDC "nonce" checks: raise errors when it's mandatory
810

911
3.0.1 (2019-01-24)
1012
------------------

docs/oauth2/oidc/validator.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Into
2020
from oauthlib.openid import RequestValidator
2121
2222
Then, you have to implement the new RequestValidator methods as shown below.
23+
Note that a new UserInfo endpoint is defined and need a new controller into your webserver.
2324

2425
RequestValidator Extension
2526
----------------------------------------------------

oauthlib/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
from logging import NullHandler
1313

1414
__author__ = 'The OAuthlib Community'
15-
__version__ = '3.0.2-dev'
15+
__version__ = '3.1.0-dev'
1616

1717
logging.getLogger('oauthlib').addHandler(NullHandler())
1818

oauthlib/oauth1/rfc5849/endpoints/base.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
from oauthlib.common import CaseInsensitiveDict, Request, generate_token
1414

15-
from .. import (CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC, SIGNATURE_RSA,
15+
from .. import (CONTENT_TYPE_FORM_URLENCODED, SIGNATURE_HMAC_SHA1, SIGNATURE_HMAC_SHA256, SIGNATURE_RSA,
1616
SIGNATURE_TYPE_AUTH_HEADER, SIGNATURE_TYPE_BODY,
1717
SIGNATURE_TYPE_QUERY, errors, signature, utils)
1818

@@ -204,9 +204,12 @@ def _check_signature(self, request, is_token_request=False):
204204
resource_owner_secret = self.request_validator.get_access_token_secret(
205205
request.client_key, request.resource_owner_key, request)
206206

207-
if request.signature_method == SIGNATURE_HMAC:
207+
if request.signature_method == SIGNATURE_HMAC_SHA1:
208208
valid_signature = signature.verify_hmac_sha1(request,
209209
client_secret, resource_owner_secret)
210+
elif request.signature_method == SIGNATURE_HMAC_SHA256:
211+
valid_signature = signature.verify_hmac_sha256(request,
212+
client_secret, resource_owner_secret)
210213
else:
211214
valid_signature = signature.verify_plaintext(request,
212215
client_secret, resource_owner_secret)

oauthlib/oauth1/rfc5849/signature.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,36 @@ def verify_hmac_sha1(request, client_secret=None,
661661
return match
662662

663663

664+
def verify_hmac_sha256(request, client_secret=None,
665+
resource_owner_secret=None):
666+
"""Verify a HMAC-SHA256 signature.
667+
668+
Per `section 3.4`_ of the spec.
669+
670+
.. _`section 3.4`: https://tools.ietf.org/html/rfc5849#section-3.4
671+
672+
To satisfy `RFC2616 section 5.2`_ item 1, the request argument's uri
673+
attribute MUST be an absolute URI whose netloc part identifies the
674+
origin server or gateway on which the resource resides. Any Host
675+
item of the request argument's headers dict attribute will be
676+
ignored.
677+
678+
.. _`RFC2616 section 5.2`: https://tools.ietf.org/html/rfc2616#section-5.2
679+
680+
"""
681+
norm_params = normalize_parameters(request.params)
682+
bs_uri = base_string_uri(request.uri)
683+
sig_base_str = signature_base_string(request.http_method, bs_uri,
684+
norm_params)
685+
signature = sign_hmac_sha256(sig_base_str, client_secret,
686+
resource_owner_secret)
687+
match = safe_string_equals(signature, request.signature)
688+
if not match:
689+
log.debug('Verify HMAC-SHA256 failed: signature base string: %s',
690+
sig_base_str)
691+
return match
692+
693+
664694
def _prepare_key_plus(alg, keystr):
665695
if isinstance(keystr, bytes):
666696
keystr = keystr.decode('utf-8')

oauthlib/oauth2/rfc6749/__init__.py

Lines changed: 2 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,56 +11,10 @@
1111
import functools
1212
import logging
1313

14+
from .endpoints.base import BaseEndpoint
15+
from .endpoints.base import catch_errors_and_unavailability
1416
from .errors import TemporarilyUnavailableError, ServerError
1517
from .errors import FatalClientError, OAuth2Error
1618

1719

1820
log = logging.getLogger(__name__)
19-
20-
21-
class BaseEndpoint(object):
22-
23-
def __init__(self):
24-
self._available = True
25-
self._catch_errors = False
26-
27-
@property
28-
def available(self):
29-
return self._available
30-
31-
@available.setter
32-
def available(self, available):
33-
self._available = available
34-
35-
@property
36-
def catch_errors(self):
37-
return self._catch_errors
38-
39-
@catch_errors.setter
40-
def catch_errors(self, catch_errors):
41-
self._catch_errors = catch_errors
42-
43-
44-
def catch_errors_and_unavailability(f):
45-
@functools.wraps(f)
46-
def wrapper(endpoint, uri, *args, **kwargs):
47-
if not endpoint.available:
48-
e = TemporarilyUnavailableError()
49-
log.info('Endpoint unavailable, ignoring request %s.' % uri)
50-
return {}, e.json, 503
51-
52-
if endpoint.catch_errors:
53-
try:
54-
return f(endpoint, uri, *args, **kwargs)
55-
except OAuth2Error:
56-
raise
57-
except FatalClientError:
58-
raise
59-
except Exception as e:
60-
error = ServerError()
61-
log.warning(
62-
'Exception caught while processing request, %s.' % e)
63-
return {}, error.json, 500
64-
else:
65-
return f(endpoint, uri, *args, **kwargs)
66-
return wrapper

oauthlib/openid/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
from __future__ import absolute_import, unicode_literals
88

99
from .connect.core.endpoints import Server
10+
from .connect.core.endpoints import UserInfoEndpoint
1011
from .connect.core.request_validator import RequestValidator

oauthlib/openid/connect/core/endpoints/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
from __future__ import absolute_import, unicode_literals
1010

1111
from .pre_configured import Server
12+
from .userinfo import UserInfoEndpoint

oauthlib/openid/connect/core/endpoints/pre_configured.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@
3434
AuthorizationTokenGrantDispatcher
3535
)
3636
from ..tokens import JWTToken
37+
from .userinfo import UserInfoEndpoint
3738

3839

3940
class Server(AuthorizationEndpoint, IntrospectEndpoint, TokenEndpoint,
40-
ResourceEndpoint, RevocationEndpoint):
41+
ResourceEndpoint, RevocationEndpoint, UserInfoEndpoint):
4142

4243
"""An all-in-one endpoint featuring all four major grant types."""
4344

@@ -105,3 +106,4 @@ def __init__(self, request_validator, token_expires_in=None,
105106
token_types={'Bearer': bearer, 'JWT': jwt})
106107
RevocationEndpoint.__init__(self, request_validator)
107108
IntrospectEndpoint.__init__(self, request_validator)
109+
UserInfoEndpoint.__init__(self, request_validator)
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
"""
2+
oauthlib.openid.connect.core.endpoints.userinfo
3+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4+
5+
This module is an implementation of userinfo endpoint.
6+
"""
7+
from __future__ import absolute_import, unicode_literals
8+
9+
import json
10+
import logging
11+
12+
from oauthlib.common import Request
13+
from oauthlib.common import unicode_type
14+
from oauthlib.oauth2.rfc6749.endpoints.base import BaseEndpoint
15+
from oauthlib.oauth2.rfc6749.endpoints.base import catch_errors_and_unavailability
16+
from oauthlib.oauth2.rfc6749.tokens import BearerToken
17+
from oauthlib.oauth2.rfc6749 import errors
18+
19+
20+
log = logging.getLogger(__name__)
21+
22+
23+
class UserInfoEndpoint(BaseEndpoint):
24+
"""Authorizes access to userinfo resource.
25+
"""
26+
def __init__(self, request_validator):
27+
self.bearer = BearerToken(request_validator, None, None, None)
28+
self.request_validator = request_validator
29+
BaseEndpoint.__init__(self)
30+
31+
@catch_errors_and_unavailability
32+
def create_userinfo_response(self, uri, http_method='GET', body=None, headers=None):
33+
"""Validate BearerToken and return userinfo from RequestValidator
34+
35+
The UserInfo Endpoint MUST return a
36+
content-type header to indicate which format is being returned. The
37+
content-type of the HTTP response MUST be application/json if the
38+
response body is a text JSON object; the response body SHOULD be encoded
39+
using UTF-8.
40+
"""
41+
request = Request(uri, http_method, body, headers)
42+
request.scopes = ["openid"]
43+
self.validate_userinfo_request(request)
44+
45+
claims = self.request_validator.get_userinfo_claims(request)
46+
if claims is None:
47+
log.error('Userinfo MUST have claims for %r.', request)
48+
raise errors.ServerError(status_code=500)
49+
50+
if isinstance(claims, dict):
51+
resp_headers = {
52+
'Content-Type': 'application/json'
53+
}
54+
if "sub" not in claims:
55+
log.error('Userinfo MUST have "sub" for %r.', request)
56+
raise errors.ServerError(status_code=500)
57+
body = json.dumps(claims)
58+
elif isinstance(claims, unicode_type):
59+
resp_headers = {
60+
'Content-Type': 'application/jwt'
61+
}
62+
body = claims
63+
else:
64+
log.error('Userinfo return unknown response for %r.', request)
65+
raise errors.ServerError(status_code=500)
66+
log.debug('Userinfo access valid for %r.', request)
67+
return resp_headers, body, 200
68+
69+
def validate_userinfo_request(self, request):
70+
"""Ensure the request is valid.
71+
72+
5.3.1. UserInfo Request
73+
The Client sends the UserInfo Request using either HTTP GET or HTTP
74+
POST. The Access Token obtained from an OpenID Connect Authentication
75+
Request MUST be sent as a Bearer Token, per Section 2 of OAuth 2.0
76+
Bearer Token Usage [RFC6750].
77+
78+
It is RECOMMENDED that the request use the HTTP GET method and the
79+
Access Token be sent using the Authorization header field.
80+
81+
The following is a non-normative example of a UserInfo Request:
82+
83+
GET /userinfo HTTP/1.1
84+
Host: server.example.com
85+
Authorization: Bearer SlAV32hkKG
86+
87+
5.3.3. UserInfo Error Response
88+
When an error condition occurs, the UserInfo Endpoint returns an Error
89+
Response as defined in Section 3 of OAuth 2.0 Bearer Token Usage
90+
[RFC6750]. (HTTP errors unrelated to RFC 6750 are returned to the User
91+
Agent using the appropriate HTTP status code.)
92+
93+
The following is a non-normative example of a UserInfo Error Response:
94+
95+
HTTP/1.1 401 Unauthorized
96+
WWW-Authenticate: Bearer error="invalid_token",
97+
error_description="The Access Token expired"
98+
"""
99+
if not self.bearer.validate_request(request):
100+
raise errors.InvalidTokenError()
101+
if "openid" not in request.scopes:
102+
raise errors.InsufficientScopeError()

0 commit comments

Comments
 (0)