3
3
"""
4
4
import json
5
5
import logging
6
- from urllib .parse import urlencode
6
+ from urllib .parse import urlencode , urlparse
7
7
8
8
from jwkest .jwk import rsa_load , RSAKey
9
9
from oic .oic .message import (AuthorizationRequest , AuthorizationErrorResponse , TokenErrorResponse ,
@@ -57,6 +57,7 @@ def _create_provider(self, endpoint_baseurl):
57
57
capabilities = {
58
58
"issuer" : self .base_url ,
59
59
"authorization_endpoint" : "{}/{}" .format (endpoint_baseurl , AuthorizationEndpoint .url ),
60
+ "jwks_uri" : "{}/jwks" .format (endpoint_baseurl ),
60
61
"response_types_supported" : response_types_supported ,
61
62
"id_token_signing_alg_values_supported" : [self .signing_key .alg ],
62
63
"response_modes_supported" : ["fragment" , "query" ],
@@ -102,7 +103,7 @@ def _init_authorization_state(self):
102
103
return AuthorizationState (HashBasedSubjectIdentifierFactory (sub_hash_salt ), authz_code_db , access_token_db ,
103
104
refresh_token_db , sub_db , ** token_lifetimes )
104
105
105
- def handle_authn_response (self , context , internal_resp ):
106
+ def handle_authn_response (self , context , internal_resp , extra_id_token_claims = None ):
106
107
"""
107
108
See super class method satosa.frontends.base.FrontendModule#handle_authn_response
108
109
:type context: satosa.context.Context
@@ -114,7 +115,7 @@ def handle_authn_response(self, context, internal_resp):
114
115
115
116
attributes = self .converter .from_internal ("openid" , internal_resp .attributes )
116
117
self .user_db [internal_resp .user_id ] = {k : v [0 ] for k , v in attributes .items ()}
117
- auth_resp = self .provider .authorize (auth_req , internal_resp .user_id )
118
+ auth_resp = self .provider .authorize (auth_req , internal_resp .user_id , extra_id_token_claims )
118
119
119
120
del context .state [self .name ]
120
121
http_response = auth_resp .request (auth_req ["redirect_uri" ], should_fragment_encode (auth_req ))
@@ -138,32 +139,46 @@ def register_endpoints(self, backend_names):
138
139
:rtype: list[(str, ((satosa.context.Context, Any) -> satosa.response.Response, Any))]
139
140
:raise ValueError: if more than one backend is configured
140
141
"""
142
+ backend_name = None
141
143
if len (backend_names ) != 1 :
142
144
# only supports one backend since there currently is no way to publish multiple authorization endpoints
143
145
# in configuration information and there is no other standard way of authorization_endpoint discovery
144
146
# similar to SAML entity discovery
145
- raise ValueError ("OpenID Connect frontend only supports one backend." )
146
- backend = backend_names [0 ]
147
- endpoint_baseurl = "{}/{}" .format (self .base_url , backend )
147
+ # this can be circumvented with a custom RequestMicroService which handles the routing based on something
148
+ # in the authentication request
149
+ logger .warn ("More than one backend is configured, make sure to provide a custom routing micro service to "
150
+ "determine which backend should be used per request." )
151
+ else :
152
+ backend_name = backend_names [0 ]
153
+
154
+ endpoint_baseurl = "{}/{}" .format (self .base_url , self .name )
148
155
self ._create_provider (endpoint_baseurl )
149
156
150
157
provider_config = ("^.well-known/openid-configuration$" , self .provider_config )
151
- jwks_uri = ("^jwks$" , self .jwks )
152
- authentication = ("^{}/{}" .format (backend , AuthorizationEndpoint .url ), self .handle_authn_request )
158
+ jwks_uri = ("^{}/jwks$" .format (self .name ), self .jwks )
159
+
160
+ if backend_name :
161
+ # if there is only one backend, include its name in the path so the default routing can work
162
+ auth_endpoint = "{}/{}/{}/{}" .format (self .base_url , backend_name , self .name , AuthorizationEndpoint .url )
163
+ self .provider .configuration_information ["authorization_endpoint" ] = auth_endpoint
164
+ auth_path = urlparse (auth_endpoint ).path .lstrip ("/" )
165
+ else :
166
+ auth_path = "{}/{}" .format (self .name , AuthorizationEndpoint .url )
167
+ authentication = ("^{}$" .format (auth_path ), self .handle_authn_request )
153
168
url_map = [provider_config , jwks_uri , authentication ]
154
169
155
170
if any ("code" in v for v in self .provider .configuration_information ["response_types_supported" ]):
156
171
self .provider .configuration_information ["token_endpoint" ] = "{}/{}" .format (endpoint_baseurl ,
157
172
TokenEndpoint .url )
158
- token_endpoint = ("^{}/{}" .format (backend , TokenEndpoint .url ), self .token_endpoint )
173
+ token_endpoint = ("^{}/{}" .format (self . name , TokenEndpoint .url ), self .token_endpoint )
159
174
url_map .append (token_endpoint )
160
175
161
176
self .provider .configuration_information ["userinfo_endpoint" ] = "{}/{}" .format (endpoint_baseurl ,
162
177
UserinfoEndpoint .url )
163
- userinfo_endpoint = ("^{}/{}" .format (backend , UserinfoEndpoint .url ), self .userinfo_endpoint )
178
+ userinfo_endpoint = ("^{}/{}" .format (self . name , UserinfoEndpoint .url ), self .userinfo_endpoint )
164
179
url_map .append (userinfo_endpoint )
165
180
if "registration_endpoint" in self .provider .configuration_information :
166
- client_registration = ("^{}/{}" .format (backend , RegistrationEndpoint .url ), self .client_registration )
181
+ client_registration = ("^{}/{}" .format (self . name , RegistrationEndpoint .url ), self .client_registration )
167
182
url_map .append (client_registration )
168
183
169
184
return url_map
@@ -247,8 +262,17 @@ def handle_authn_request(self, context):
247
262
client_id = authn_req ["client_id" ]
248
263
context .state [self .name ] = {"oidc_request" : request }
249
264
hash_type = oidc_subject_type_to_hash_type (self .provider .clients [client_id ].get ("subject_type" , "pairwise" ))
250
- internal_req = InternalRequest (hash_type , client_id , self .provider .clients [client_id ].get ("client_name" ))
265
+ client_name = self .provider .clients [client_id ].get ("client_name" )
266
+ if client_name :
267
+ # TODO should process client names for all languages, see OIDC Registration, Section 2.1
268
+ requester_name = [{"lang" : "en" , "text" : client_name }]
269
+ else :
270
+ requester_name = None
271
+ internal_req = InternalRequest (hash_type , client_id , requester_name )
251
272
273
+ internal_req .approved_attributes = self .converter .to_internal_filter ("openid" ,
274
+ self .provider .configuration_information [
275
+ "claims_supported" ])
252
276
return self .auth_req_callback_func (context , internal_req )
253
277
254
278
def jwks (self , context ):
0 commit comments