Skip to content

Commit

Permalink
Synced implementation of token_endpoint
Browse files Browse the repository at this point in the history
All three providers (oauth2, oic and extension) now share common code.
  • Loading branch information
tpazderka committed Mar 2, 2019
1 parent 9ce79b4 commit b7a7631
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 141 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The format is based on the [KeepAChangeLog] project.
- [#612] Dropped python 3.4 support
- [#588] Switch to defusedxml for XML parsing
- [#605] Message.c_param dictionary values have to be a ParamDefinition namedtuple type
- [#624] token_endpoint implementation and kwargs have been changed

### Added
- [#441] CookieDealer now accepts secure and httponly params
Expand All @@ -33,6 +34,7 @@ The format is based on the [KeepAChangeLog] project.
[#441]: https://github.com/OpenIDC/pyoidc/issues/441
[#612]: https://github.com/OpenIDC/pyoidc/pull/612
[#618]: https://github.com/OpenIDC/pyoidc/pull/618
[#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
111 changes: 80 additions & 31 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 @@ -768,51 +769,75 @@ 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 = AccessTokenRequest().deserialize(request, dtype)

# Verify client authentication
try:
self.client_authn(self, areq, authn)
except FailedAuthentication as err:
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")
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")
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")
return Unauthorized(error.to_json(), content="application/json")

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 +847,30 @@ 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
"""
raise NotImplementedError('See oic.extension.provider.')

def client_credentials_grant_type(self, areq):
"""
Token authorization using client credentials.
RFC6749 section 4.4
"""
raise NotImplementedError('See oic.extension.provider.')

def password_grant_type(self, areq):
"""
Token authorization using Resource owner password credentials.
RFC6749 section 4.3
"""
raise NotImplementedError('See oic.extension.provider.')

def verify_endpoint(self, request="", cookie=None, **kwargs):
_req = parse_qs(request)
try:
Expand Down
105 changes: 32 additions & 73 deletions src/oic/oic/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@
from oic.oic import claims_match
from oic.oic import scope2claims
from oic.oic.message import SCOPE2CLAIMS
from oic.oic.message import AccessTokenRequest
from oic.oic.message import AccessTokenResponse
from oic.oic.message import AuthorizationRequest
from oic.oic.message import AuthorizationResponse
Expand All @@ -67,10 +66,8 @@
from oic.oic.message import OpenIDRequest
from oic.oic.message import OpenIDSchema
from oic.oic.message import ProviderConfigurationResponse
from oic.oic.message import RefreshAccessTokenRequest
from oic.oic.message import RegistrationRequest
from oic.oic.message import RegistrationResponse
from oic.oic.message import TokenErrorResponse
from oic.utils import sort_sign_alg
from oic.utils.http_util import OAUTH2_NOCACHE_HEADERS
from oic.utils.http_util import BadRequest
Expand Down Expand Up @@ -961,17 +958,19 @@ def sign_encrypt_id_token(self, sinfo, client_info, areq, code=None,

return id_token

def _access_token_endpoint(self, req, **kwargs):
def code_grant_type(self, areq, **kwargs):
"""
Token authorization using Code Grant.
RFC6749 section 4.1
"""
_sdb = self.sdb
_log_debug = logger.debug

client_info = self.cdb[str(req["client_id"])]

assert req["grant_type"] == "authorization_code"
client_info = self.cdb[str(areq["client_id"])]

try:
_access_code = req["code"].replace(' ', '+')
_access_code = areq["code"].replace(' ', '+')
except KeyError: # Missing code parameter - absolutely fatal
return error_response('invalid_request', descr='Missing code')

Expand All @@ -988,9 +987,9 @@ def _access_token_endpoint(self, req, **kwargs):
# If redirect_uri was in the initial authorization request
# verify that the one given here is the correct one.
if "redirect_uri" in _info:
if 'redirect_uri' not in req:
if 'redirect_uri' not in areq:
return error_response('invalid_request', descr='Missing redirect_uri')
if req["redirect_uri"] != _info["redirect_uri"]:
if areq["redirect_uri"] != _info["redirect_uri"]:
return error_response("invalid_request", descr="redirect_uri mismatch")

_log_debug("All checks OK")
Expand All @@ -1004,8 +1003,7 @@ def _access_token_endpoint(self, req, **kwargs):
issue_refresh = True

try:
_tinfo = _sdb.upgrade_to_token(_access_code,
issue_refresh=issue_refresh)
_tinfo = _sdb.upgrade_to_token(_access_code, issue_refresh=issue_refresh)
except AccessCodeUsed as err:
logger.error("%s" % err)
# Should revoke the token issued to this access code
Expand All @@ -1015,8 +1013,7 @@ def _access_token_endpoint(self, req, **kwargs):
if "openid" in _info["scope"]:
userinfo = self.userinfo_in_id_token_claims(_info)
try:
_idtoken = self.sign_encrypt_id_token(
_info, client_info, req, user_info=userinfo)
_idtoken = self.sign_encrypt_id_token(_info, client_info, areq, user_info=userinfo)
except (JWEException, NoSuitableSigningKeys) as err:
logger.warning(str(err))
return error_response("invalid_request", descr="Could not sign/encrypt id_token")
Expand All @@ -1034,15 +1031,19 @@ def _access_token_endpoint(self, req, **kwargs):

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

def _refresh_access_token_endpoint(self, req, **kwargs):
def refresh_token_grant_type(self, areq):
"""
Token refresh.
RFC6749 section 6
"""
_sdb = self.sdb
_log_debug = logger.debug

client_id = str(req['client_id'])
client_id = str(areq['client_id'])
client_info = self.cdb[client_id]

assert req["grant_type"] == "refresh_token"
rtoken = req["refresh_token"]
rtoken = areq["refresh_token"]
try:
_info = _sdb.refresh_token(rtoken, client_id=client_id)
except ExpiredToken:
Expand All @@ -1051,8 +1052,7 @@ def _refresh_access_token_endpoint(self, req, **kwargs):
if "openid" in _info["scope"] and "authn_event" in _info:
userinfo = self.userinfo_in_id_token_claims(_info)
try:
_idtoken = self.sign_encrypt_id_token(
_info, client_info, req, user_info=userinfo)
_idtoken = self.sign_encrypt_id_token(_info, client_info, areq, user_info=userinfo)
except (JWEException, NoSuitableSigningKeys) as err:
logger.warning(str(err))
return error_response("invalid_request", descr="Could not sign/encrypt id_token")
Expand All @@ -1068,64 +1068,23 @@ def _refresh_access_token_endpoint(self, req, **kwargs):

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

def token_endpoint(self, request="", authn=None, dtype='urlencoded',
**kwargs):
def client_credentials_grant_type(self, areq):
"""
Give clients their access tokens.
Token authorization using client credentials.
:param request: The request
:param authn: Authentication info, comes from HTTP header
:returns:
RFC6749 section 4.4
"""
logger.debug("- token -")
logger.info("token_request: %s" % sanitize(request))

req = AccessTokenRequest().deserialize(request, dtype)

if 'state' in req:
try:
state = self.sdb[req['code']]['state']
except KeyError:
logger.error('Code not present in SessionDB')
err = TokenErrorResponse(error="unauthorized_client")
return Unauthorized(err.to_json(), content="application/json")

if state != req['state']:
logger.error('State value mismatch')
err = TokenErrorResponse(error="unauthorized_client")
return Unauthorized(err.to_json(), content="application/json")

if "refresh_token" in req:
req = RefreshAccessTokenRequest().deserialize(request, dtype)
# Not supported in OpenID Connect
return error_response('invalid_request', descr='Unsupported grant_type')

logger.debug("%s: %s" % (req.__class__.__name__, sanitize(req)))

try:
client_id = self.client_authn(self, req, authn)
msg = ''
except Exception as err:
msg = "Failed to verify client due to: {}".format(err)
logger.error(msg)
client_id = ""

if not client_id:
logger.error('No client_id, authentication failed')
error = TokenErrorResponse(error="unauthorized_client",
error_description=msg)
return Unauthorized(error.to_json(), content="application/json")

if "client_id" not in req: # Optional for access token request
req["client_id"] = client_id

if isinstance(req, AccessTokenRequest):
try:
return self._access_token_endpoint(req, **kwargs)
except JWEException as err:
return error_response("invalid_request",
descr="%s" % err)
def password_grant_type(self, areq):
"""
Token authorization using Resource owner password credentials.
else:
return self._refresh_access_token_endpoint(req, **kwargs)
RFC6749 section 4.3
"""
# Not supported in OpenID Connect
return error_response('invalid_request', descr='Unsupported grant_type')

def _collect_user_info(self, session, userinfo_claims=None):
"""
Expand Down
Loading

0 comments on commit b7a7631

Please sign in to comment.