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

Id assurance #87

Merged
merged 41 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
32476b3
More appropriate function name.
rohe Sep 10, 2023
1267632
There are occasions when it's useful to know under which umbrella an …
rohe Sep 11, 2023
1b7ae68
Allow kwargs to flow through.
rohe Sep 11, 2023
178f575
Remove having a default for always adding keys.
rohe Sep 16, 2023
f8ff973
Generalize token handling in client authentication
rohe Sep 23, 2023
32fc941
get_jwks was undefined.
rohe Sep 25, 2023
6bda025
Fixed a bug in the dump/load system
rohe Oct 12, 2023
c905eac
Merge branch 'id_assurance' of https://github.com/IdentityPython/idpy…
rohe Oct 12, 2023
16d7da0
Fixed bug in AbstractFileSystem delete method.
rohe Oct 13, 2023
5bd1021
Added more logging and fixed bugs in flask_rp/ciews.py
rohe Oct 19, 2023
604e3b1
Need the httpc_params when doing a http call.
rohe Oct 28, 2023
ceba7be
More resilient to errors.
rohe Nov 8, 2023
bff6b23
Is extremely confusing when an exception is rather the rule rather th…
rohe Nov 9, 2023
d752797
Accept a list as a response
rohe Nov 11, 2023
578e9f4
More debug messages
rohe Nov 19, 2023
b9ecbdf
Updated version in pyproject.toml.
rohe Nov 21, 2023
8ba378c
Updated version in pyproject.toml.
rohe Nov 21, 2023
4ee20f7
A default key_conf will only be applied if the Server is not part of …
rohe Nov 22, 2023
30ccbb7
This general class should be here and not in fedservice.
rohe Nov 22, 2023
59639b0
Added config defaults.
rohe Nov 23, 2023
b531073
More appropriate function name.
rohe Sep 10, 2023
736f411
There are occasions when it's useful to know under which umbrella an …
rohe Sep 11, 2023
4bf42e6
Allow kwargs to flow through.
rohe Sep 11, 2023
6dd5151
Remove having a default for always adding keys.
rohe Sep 16, 2023
58ccbbd
Generalize token handling in client authentication
rohe Sep 23, 2023
d66ad85
Fixed a bug in the dump/load system
rohe Oct 12, 2023
eff4508
Fixed bug in AbstractFileSystem delete method.
rohe Oct 13, 2023
92ca67c
Added more logging and fixed bugs in flask_rp/ciews.py
rohe Oct 19, 2023
3843f7d
Need the httpc_params when doing a http call.
rohe Oct 28, 2023
1f8a7c1
More resilient to errors.
rohe Nov 8, 2023
49a0f60
Is extremely confusing when an exception is rather the rule rather th…
rohe Nov 9, 2023
8d6470e
Accept a list as a response
rohe Nov 11, 2023
0ae4c39
More debug messages
rohe Nov 19, 2023
55d3289
Updated version in pyproject.toml.
rohe Nov 21, 2023
c80f986
Updated version in pyproject.toml.
rohe Nov 21, 2023
dde5433
A default key_conf will only be applied if the Server is not part of …
rohe Nov 22, 2023
5a9f287
This general class should be here and not in fedservice.
rohe Nov 22, 2023
92b5461
Added config defaults.
rohe Nov 23, 2023
61ccb6d
Fixed spelling error
rohe Nov 23, 2023
e8ab2f9
Start of adding persistence layer.
rohe Nov 29, 2023
1be342b
Simplified the try/except process.
rohe Dec 17, 2023
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
7 changes: 1 addition & 6 deletions example/flask_op/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,8 @@
"request_uri_parameter_supported": true,
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"id_token token",
"code id_token token",
"none"
"code id_token"
],
"response_modes_supported": [
"query",
Expand Down
2 changes: 1 addition & 1 deletion example/flask_op/private/cookie_jwks.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"keys": [{"kty": "oct", "use": "enc", "kid": "enc", "k": "gVdGvOn9TFG38gzqs2uO-pQB40qxJbfm"}, {"kty": "oct", "use": "sig", "kid": "sig", "k": "HlW_dFC6aquKPZ6zGtq2dZEBcntP9uHd"}]}
{"keys": [{"kty": "oct", "use": "enc", "kid": "enc", "k": "GCizp3ewVRV0VZEef3VQwFve7n2QwAFI"}, {"kty": "oct", "use": "sig", "kid": "sig", "k": "QC2JxpVJXPDMpYv_h76jIrt_lA1P4KSu"}]}
2 changes: 1 addition & 1 deletion example/flask_rp/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def init_oidc_rp_handler(app):
if _rp_conf.key_conf:
_kj = init_key_jar(**_rp_conf.key_conf)
_path = _rp_conf.key_conf['public_path']
# removes ./ and / from the begin of the string
# removes ./ and / from the begining of the string
_path = re.sub('^(.)/', '', _path)
else:
_kj = KeyJar()
Expand Down
38 changes: 19 additions & 19 deletions example/flask_rp/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,26 @@
},
"clients": {
"": {
"client_preferences": {
"httpc_params": {
"verify": false
},
"client_type": "oidc",
"capabilities": {
"application_name": "rphandler",
"metadata": {
"application_type": "web",
"contacts": [
"[email protected]"
],
"response_types": [
"code"
]
},
"usage": {
"scope": [
"openid",
"profile",
"email",
"address",
"phone"
]
},
"application_type": "web",
"contacts": [
"[email protected]"
],
"response_types_supported": [
"code"
],
"scopes_supported": [
"openid",
"profile",
"email",
"address",
"phone"
],
"token_endpoint_auth_methods": [
"client_secret_basic",
"client_secret_post"
Expand Down
2 changes: 1 addition & 1 deletion example/flask_rp/templates/opbyuid.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ <h3>By entering your unique identifier:</h3>
<input type="text" id="uid" name="uid" class="form-control" placeholder="UID" autofocus>
<h3>an issuer ID</h3>
<input type="text" id="dyn_iss" name="dyn_iss" class="form-control">
<h3><em>Or</em> you can chose one of the preconfigured OpenID Connect Providers</h3>
<h3><em>Or</em> you can choose one of the preconfigured OpenID Connect Providers</h3>
<select name="static_iss">
<option value=""></option>
{% for op in providers %}
Expand Down
13 changes: 4 additions & 9 deletions example/flask_rp/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def rp():
except Exception as err:
return make_response('Something went wrong:{}'.format(err), 400)
else:
response = redirect(result['url'], 303)
response = redirect(result, 303)
return response
else:
_providers = current_app.rp_config.clients.keys()
Expand Down Expand Up @@ -106,7 +106,7 @@ def finalize(op_identifier, request_args):
session['state'] = request_args.get('state')

if session['state']:
iss = _context.state.get_iss(session['state'])
iss = _context.cstate.get_set(session['state'], claim=["iss"])['iss']
else:
return make_response('Unknown state', 400)

Expand Down Expand Up @@ -158,14 +158,9 @@ def get_op_identifier_by_cb_uri(url: str):
uri = splitquery(url)[0]
for k, v in current_app.rph.issuer2rp.items():
for endpoint in v.get_callback_uris():
_endps = v.get_metadata_value(endpoint)
if _endps is None:
continue
elif isinstance(_endps,str):
if _endps == uri:
for val in v.context.claims.get_preference(endpoint):
if val == uri:
return k
elif uri in _endps:
return k
return None

@oidc_rp_views.route('/authz_cb/<op_identifier>')
Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ build-backend = "setuptools.build_meta"

[metadata]
name = "idpyoidc"
version = "2.1.0"
version = "3.0.0"
author = "Roland Hedberg"
author_email = "[email protected]"
description = "Everything OAuth2 and OIDC"
long_description = "file: README.md"
long_description_content_type = "text/markdown"
url = "https://github.com/IdentityPython/oidc-op"
url = "https://github.com/IdentityPython/idpy-oidc"
license = "Apache-2.0"
classifiers =[
"Programming Language :: Python :: 3",
Expand All @@ -31,7 +31,7 @@ line-length = 100

[tool.isort]
force_single_line = true
known_first_party = "oidcop"
known_first_party = "idpyoidc"
include_trailing_comma = true
force_grid_wrap = 0
use_parentheses = true
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def run_tests(self):
"Programming Language :: Python :: 3.11",
"Topic :: Software Development :: Libraries :: Python Modules"],
install_requires=[
"cryptojwt>=1.8.1",
"cryptojwt>=1.8.3",
"pyOpenSSL",
"filelock>=3.0.12",
'pyyaml>=5.1.2',
Expand Down
2 changes: 1 addition & 1 deletion src/idpyoidc/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
__author__ = "Roland Hedberg"
__version__ = "2.2.0"
__version__ = "3.0.0"

VERIFIED_CLAIM_PREFIX = "__verified"

Expand Down
2 changes: 1 addition & 1 deletion src/idpyoidc/claims.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ def add_extra_keys(self, keyjar, id):
return None

def get_jwks(self, keyjar):
return None
return keyjar.export_jwks()

def handle_keys(self, configuration: dict, keyjar: Optional[KeyJar] = None):
_jwks = _jwks_uri = None
Expand Down
69 changes: 54 additions & 15 deletions src/idpyoidc/client/client_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@

from idpyoidc.defaults import DEF_SIGN_ALG
from idpyoidc.defaults import JWT_BEARER
from idpyoidc.message.oauth2 import SINGLE_OPTIONAL_STRING
from idpyoidc.message import Message
from idpyoidc.message.oauth2 import AccessTokenRequest
from idpyoidc.message.oauth2 import SINGLE_OPTIONAL_STRING
from idpyoidc.message.oidc import AuthnToken
from idpyoidc.time_util import utc_time_sans_frac
from idpyoidc.util import rndstr

from .util import sanitize
from ..message import VREQUIRED
from ..util import instantiate
from .util import sanitize

# from idpyoidc.oidc.backchannel_authentication import ClientNotificationAuthn

Expand All @@ -29,6 +29,7 @@

__author__ = "roland hedberg"

DEFAULT_ACCESS_TOKEN_TYPE = "Bearer"

class AuthnFailure(Exception):
"""Unspecified Authentication failure"""
Expand Down Expand Up @@ -137,8 +138,8 @@ def _with_or_without_client_id(request, service):
:param service: A :py:class:`idpyoidc.client.service.Service` instance
"""
if (
isinstance(request, AccessTokenRequest)
and request["grant_type"] == "authorization_code"
isinstance(request, AccessTokenRequest)
and request["grant_type"] == "authorization_code"
):
if "client_id" not in request:
try:
Expand Down Expand Up @@ -228,7 +229,7 @@ def modify_request(self, request, service, **kwargs):
if not request["client_secret"]:
raise AuthnFailure("Missing client secret")

# Set the client_id in the the request
# Set the client_id in the request
request["client_id"] = _context.get_client_id()

def construct(self, request, service=None, http_args=None, **kwargs):
Expand Down Expand Up @@ -276,10 +277,42 @@ def find_token(request, token_type, service, **kwargs):
except KeyError:
# Get the latest acquired token.
_state = kwargs.get("state", kwargs.get("key"))
_arg = service.upstream_get("context").cstate.get_set(_state, claim=[token_type])
_arg = service.upstream_get("context").cstate.get_set(_state, claim=[token_type,
"token_type"])
return _arg.get("access_token")


def find_token_info(request: Union[Message, dict], token_type: str, service, **kwargs) -> dict:
"""
Token acquired by a previous run service.

:param token_type:
:param kwargs:
:return:
"""

if request is not None:
_token = request.get(token_type, None)
if _token:
del request[token_type]
# Required under certain circumstances :-) not under other
request.c_param[token_type] = SINGLE_OPTIONAL_STRING
return {token_type: _token, "token_type": DEFAULT_ACCESS_TOKEN_TYPE}

_state = kwargs.get("state", kwargs.get("key"))
if _state:
_token_info = service.upstream_get("context").cstate.get_set(
_state, claim=[token_type, "token_type"])
else:
_token_info = {"token_type": DEFAULT_ACCESS_TOKEN_TYPE}

_token = kwargs.get("access_token", None)
if _token:
return {token_type: _token, "token_type": _token_info["token_type"]}
else:
return _token_info


class BearerHeader(ClientAuthnMethod):
"""The bearer header authentication method."""

Expand All @@ -296,18 +329,20 @@ def construct(self, request=None, service=None, http_args=None, **kwargs):
"""

if service.service_name == "refresh_token":
_acc_token = find_token(request, "refresh_token", service, **kwargs)
_token_type = "refresh_token"
elif service.service_name == "token_exchange":
_acc_token = find_token(request, "subject_token", service, **kwargs)
_token_type = "subject_token"
else:
_acc_token = find_token(request, "access_token", service, **kwargs)
_token_type = "access_token"

if not _acc_token:
_token_info = find_token_info(request, _token_type, service, **kwargs)

if not _token_info:
raise KeyError("No bearer token available")

# The authorization value starts with 'Bearer' when bearer tokens
# are used
_bearer = "Bearer {}".format(_acc_token)
# The authorization value starts with the token_type
# if _token_info["token_type"].to_lower() != "bearer":
_bearer = f"{_token_info['token_type']} {_token_info[_token_type]}"

# Add 'Authorization' to the headers
if http_args is None:
Expand Down Expand Up @@ -502,9 +537,11 @@ def _construct_client_assertion(self, service, **kwargs):
except KeyError:
_args = {}

_client_id = kwargs.get("client_id", _entity.client_id)

# construct the signed JWT with the assertions and add
# it as value to the 'client_assertion' claim of the request
return assertion_jwt(_entity.client_id, signing_key, audience, algorithm, **_args)
return assertion_jwt(_client_id, signing_key, audience, algorithm, **_args)

def modify_request(self, request, service, **kwargs):
"""
Expand Down Expand Up @@ -643,6 +680,8 @@ def single_authn_setup(name, spec):
else:
if spec is None:
cls = get_client_authn_class(name)
if cls is None:
cls = importer(name)
elif isinstance(spec, str):
cls = importer(spec)
else:
Expand Down
3 changes: 3 additions & 0 deletions src/idpyoidc/client/current.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,6 @@ def create_state(self, **kwargs):
_key = self.create_key()
self._db[_key] = kwargs
return _key

def keys(self):
return self._db.keys()
2 changes: 1 addition & 1 deletion src/idpyoidc/client/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,4 +216,4 @@ def import_keys(self, keyspec):
return _keyjar

def get_callback_uris(self):
return self.context.claims.callback_uri
return self.context.claims.get_preference("callback_uris")
19 changes: 11 additions & 8 deletions src/idpyoidc/client/oauth2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,10 @@ def get_response(
:param kwargs:
:return:
"""
_data = kwargs.get("data")
if _data and not body:
body = _data

try:
resp = self.httpc(method, url, data=body, headers=headers, **self.httpc_params)
except Exception as err:
Expand All @@ -168,6 +172,8 @@ def get_response(

if 300 <= resp.status_code < 400:
return {"http_response": resp}
elif resp.status_code >= 400:
logger.error(f"HTTP error: {resp}")

if resp.status_code < 300:
if "keyjar" not in kwargs:
Expand Down Expand Up @@ -213,14 +219,10 @@ def service_request(

logger.debug(REQUEST_INFO.format(url, method, body, headers))

try:
response = service.get_response_ext(
url, method, body, response_body_type, headers, **kwargs
)
except AttributeError:
response = self.get_response(
service, url, method, body, response_body_type, headers, **kwargs
)
_get_response_func = getattr(self, "get_response_ext", getattr(self, "get_response"))
response = _get_response_func(
service, url, method, body, response_body_type, headers, **kwargs
)

if "error" in response:
pass
Expand Down Expand Up @@ -333,6 +335,7 @@ def dynamic_provider_info_discovery(client: Client, behaviour_args: Optional[dic
except KeyError:
pass

logger.debug(f"{service}")
response = client.do_request(service, behaviour_args=behaviour_args)
if is_error_message(response):
raise OidcServiceError(response["error"])
4 changes: 2 additions & 2 deletions src/idpyoidc/client/oauth2/access_token.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ class AccessToken(Service):
"token_endpoint_auth_signing_alg": get_signing_algs,
}

def __init__(self, upstream_get, conf=None):
Service.__init__(self, upstream_get, conf=conf)
def __init__(self, upstream_get, conf=None, **kwargs):
Service.__init__(self, upstream_get, conf=conf, **kwargs)
self.pre_construct.append(self.oauth_pre_construct)

def update_service_context(self, resp, key: Optional[str] = "", **kwargs):
Expand Down
Loading
Loading