Skip to content
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

Synced implementation of token_endpoint #624

Merged
merged 2 commits into from
Mar 8, 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
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
tpazderka marked this conversation as resolved.
Show resolved Hide resolved

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