Skip to content

Commit

Permalink
Merge pull request #624 from OpenIDC/refactor_token_endpoint
Browse files Browse the repository at this point in the history
Synced implementation of token_endpoint
  • Loading branch information
tpazderka authored Mar 8, 2019
2 parents 9786ee0 + e5ff129 commit 8b3dfcb
Show file tree
Hide file tree
Showing 6 changed files with 486 additions and 327 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The format is based on the [KeepAChangeLog] project.
- [#605] Message.c_param dictionary values have to be a ParamDefinition namedtuple type
- [#56] Updated README, CLI help texts, pip requirements.txt and such for OP2,
making it into a stand-alone example easy for beginners to take on
- [#624] token_endpoint implementation and kwargs have been changed

### Added
- [#441] CookieDealer now accepts secure and httponly params
Expand All @@ -36,6 +37,7 @@ The format is based on the [KeepAChangeLog] project.
[#612]: https://github.com/OpenIDC/pyoidc/pull/612
[#618]: https://github.com/OpenIDC/pyoidc/pull/618
[#56]: https://github.com/OpenIDC/pyoidc/issues/56
[#624]: https://github.com/OpenIDC/pyoidc/pull/624

## 0.15.1 [2019-01-31]

Expand Down
31 changes: 0 additions & 31 deletions src/oic/extension/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
from oic.extension.message import TokenIntrospectionRequest
from oic.extension.message import TokenIntrospectionResponse
from oic.extension.message import TokenRevocationRequest
from oic.oauth2 import AccessTokenRequest
from oic.oauth2 import AccessTokenResponse
from oic.oauth2 import TokenErrorResponse
from oic.oauth2 import compact
Expand Down Expand Up @@ -662,36 +661,6 @@ def refresh_token_grant_type(self, areq):
atr = AccessTokenResponse(**by_schema(AccessTokenResponse, **at))
return Response(atr.to_json(), content="application/json")

def token_endpoint(self, authn="", **kwargs):
"""Provide clients their access tokens."""
logger.debug("- token -")
body = kwargs["request"]
logger.debug("body: %s" % body)

areq = AccessTokenRequest().deserialize(body, "urlencoded")

try:
self.client_authn(self, areq, authn)
except FailedAuthentication as err:
logger.error(err)
err = TokenErrorResponse(error="unauthorized_client",
error_description="%s" % err)
return Response(err.to_json(), content="application/json", status_code=401)

logger.debug("AccessTokenRequest: %s" % areq)

_grant_type = areq["grant_type"]
if _grant_type == "authorization_code":
return self.code_grant_type(areq)
elif _grant_type == 'client_credentials':
return self.client_credentials_grant_type(areq)
elif _grant_type == 'password':
return self.password_grant_type(areq)
elif _grant_type == 'refresh_token':
return self.refresh_token_grant_type(areq)
else:
raise UnSupported('grant_type: {}'.format(_grant_type))

@staticmethod
def token_access(endpoint, client_id, token_info):
# simple rules: if client_id in azp or aud it's allow to introspect
Expand Down
123 changes: 91 additions & 32 deletions src/oic/oauth2/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
from oic.oauth2.message import TokenErrorResponse
from oic.oauth2.message import add_non_standard
from oic.oauth2.message import by_schema
from oic.utils.authn.client import AuthnFailure
from oic.utils.authn.user import NoSuchAuthentication
from oic.utils.authn.user import TamperAllert
from oic.utils.authn.user import ToOld
Expand Down Expand Up @@ -153,6 +154,8 @@ def re_authenticate(areq, authn):

class Provider(object):
endp = [AuthorizationEndpoint, TokenEndpoint]
# Define the message class that in token_endpoint
atr_class = AccessTokenRequest

def __init__(self, name, sdb, cdb, authn_broker, authz, client_authn,
symkey=None, urlmap=None, iv=0, default_scope="",
Expand Down Expand Up @@ -768,51 +771,80 @@ def token_scope_check(self, areq, info):
"""Not implemented here."""
return None

def token_endpoint(self, authn="", **kwargs):
"""Provide clients with access tokens."""
_sdb = self.sdb
def token_endpoint(self, request='', authn='', dtype='urlencoded', **kwargs):
"""
Provide clients with access tokens.
:param authn: Auhentication info, comes from HTTP header.
:param request: The request.
:param dtype: deserialization method for the request.
"""
logger.debug("- token -")
body = kwargs["request"]
logger.debug("body: %s" % sanitize(body))
logger.debug("token_request: %s" % sanitize(request))

areq = AccessTokenRequest().deserialize(body, "urlencoded")
areq = self.atr_class().deserialize(request, dtype)

# Verify client authentication
try:
self.client_authn(self, areq, authn)
except FailedAuthentication as err:
client_id = self.client_authn(self, areq, authn)
except (FailedAuthentication, AuthnFailure) as err:
logger.error(err)
err = TokenErrorResponse(error="unauthorized_client",
error_description="%s" % err)
return Response(err.to_json(), content="application/json", status_code=401)
err = TokenErrorResponse(error="unauthorized_client", error_description="%s" % err)
return Unauthorized(err.to_json(), content="application/json")

logger.debug("AccessTokenRequest: %s" % sanitize(areq))

if areq["grant_type"] != "authorization_code":
error = TokenErrorResponse(error="invalid_request", error_description="Wrong grant type")
return Response(error.to_json(), content="application/json", status="401 Unauthorized")

# assert that the code is valid
_info = _sdb[areq["code"]]

resp = self.token_scope_check(areq, _info)
if resp:
return resp
# `code` is not mandatory for all requests
if 'code' in areq:
try:
_info = self.sdb[areq["code"]]
except KeyError:
logger.error('Code not present in SessionDB')
error = TokenErrorResponse(error="unauthorized_client",
error_description='Invalid code.')
return Unauthorized(error.to_json(), content="application/json")

resp = self.token_scope_check(areq, _info)
if resp:
return resp
# If redirect_uri was in the initial authorization request verify that they match
if "redirect_uri" in _info and areq["redirect_uri"] != _info["redirect_uri"]:
logger.error('Redirect_uri mismatch')
error = TokenErrorResponse(error="unauthorized_client",
error_description='Redirect_uris do not match.')
return Unauthorized(error.to_json(), content="application/json")
if 'state' in areq:
if _info['state'] != areq['state']:
logger.error('State value mismatch')
error = TokenErrorResponse(error="unauthorized_client",
error_description='State values do not match.')
return Unauthorized(error.to_json(), content="application/json")

# Propagate the client_id further
areq.setdefault('client_id', client_id)
grant_type = areq["grant_type"]
if grant_type == "authorization_code":
return self.code_grant_type(areq)
elif grant_type == "refresh_token":
return self.refresh_token_grant_type(areq)
elif grant_type == 'client_credentials':
return self.client_credentials_grant_type(areq)
elif grant_type == 'password':
return self.password_grant_type(areq)
else:
raise UnSupported('grant_type: {}'.format(grant_type))

# If redirect_uri was in the initial authorization request
# verify that the one given here is the correct one.
if "redirect_uri" in _info and areq["redirect_uri"] != _info["redirect_uri"]:
logger.error('Redirect_uri mismatch')
error = TokenErrorResponse(error="unauthorized_client")
return Unauthorized(error.to_json(), content="application/json")
def code_grant_type(self, areq):
"""
Token authorization using Code Grant.
RFC6749 section 4.1
"""
try:
_tinfo = _sdb.upgrade_to_token(areq["code"], issue_refresh=True)
_tinfo = self.sdb.upgrade_to_token(areq["code"], issue_refresh=True)
except AccessCodeUsed:
error = TokenErrorResponse(error="invalid_grant",
error_description="Access grant used")
return Response(error.to_json(), content="application/json",
status="401 Unauthorized")
error = TokenErrorResponse(error="invalid_grant", error_description="Access grant used")
return Unauthorized(error.to_json(), content="application/json")

logger.debug("_tinfo: %s" % sanitize(_tinfo))

Expand All @@ -822,6 +854,33 @@ def token_endpoint(self, authn="", **kwargs):

return Response(atr.to_json(), content="application/json", headers=OAUTH2_NOCACHE_HEADERS)

def refresh_token_grant_type(self, areq):
"""
Token refresh.
RFC6749 section 6
"""
# This is not implemented here, please see oic.extension.provider.
return error_response('unsupported_grant_type', descr='Unsupported grant_type')

def client_credentials_grant_type(self, areq):
"""
Token authorization using client credentials.
RFC6749 section 4.4
"""
# This is not implemented here, please see oic.extension.provider.
return error_response('unsupported_grant_type', descr='Unsupported grant_type')

def password_grant_type(self, areq):
"""
Token authorization using Resource owner password credentials.
RFC6749 section 4.3
"""
# This is not implemented here, please see oic.extension.provider.
return error_response('unsupported_grant_type', descr='Unsupported grant_type')

def verify_endpoint(self, request="", cookie=None, **kwargs):
_req = parse_qs(request)
try:
Expand Down
Loading

0 comments on commit 8b3dfcb

Please sign in to comment.