@@ -82,6 +82,7 @@ class SAMLBackend(BackendModule, SAMLBaseModule):
82
82
KEY_MIRROR_FORCE_AUTHN = 'mirror_force_authn'
83
83
KEY_MEMORIZE_IDP = 'memorize_idp'
84
84
KEY_USE_MEMORIZED_IDP_WHEN_FORCE_AUTHN = 'use_memorized_idp_when_force_authn'
85
+ KEY_DYNAMIC_REQUESTED_ATTRIBUTES = 'dynamic_requested_attributes'
85
86
86
87
VALUE_ACR_COMPARISON_DEFAULT = 'exact'
87
88
@@ -113,6 +114,9 @@ def __init__(self, outgoing, internal_attributes, config, base_url, name):
113
114
self .encryption_keys = []
114
115
self .outstanding_queries = {}
115
116
self .idp_blacklist_file = config .get ('idp_blacklist_file' , None )
117
+ self .requested_attributes = self .config .get (
118
+ SAMLBackend .KEY_DYNAMIC_REQUESTED_ATTRIBUTES
119
+ )
116
120
117
121
sp_keypairs = sp_config .getattr ('encryption_keypairs' , '' )
118
122
sp_key_file = sp_config .getattr ('key_file' , '' )
@@ -170,15 +174,22 @@ def start_auth(self, context, internal_req):
170
174
"""
171
175
172
176
entity_id = self .get_idp_entity_id (context )
177
+ requested_attributes = internal_req .get ("attributes" )
173
178
if entity_id is None :
174
179
# since context is not passed to disco_query
175
180
# keep the information in the state cookie
176
181
context .state [Context .KEY_FORCE_AUTHN ] = get_force_authn (
177
182
context , self .config , self .sp .config
178
183
)
184
+ if self .requested_attributes :
185
+ # We need the requested attributes, so store them in the cookie
186
+ context .state [Context .KEY_REQUESTED_ATTRIBUTES ] = \
187
+ requested_attributes
179
188
return self .disco_query (context )
180
189
181
- return self .authn_request (context , entity_id )
190
+ return self .authn_request (
191
+ context , entity_id , requested_attributes = requested_attributes
192
+ )
182
193
183
194
def disco_query (self , context ):
184
195
"""
@@ -232,13 +243,59 @@ def construct_requested_authn_context(self, entity_id):
232
243
233
244
return authn_context
234
245
235
- def authn_request (self , context , entity_id ):
246
+ def _get_requested_attributes (self , requested_attributes ):
247
+ if not requested_attributes :
248
+ return
249
+
250
+ attrs = self .converter .from_internal_filter (
251
+ self .attribute_profile , requested_attributes
252
+ )
253
+ requested_attrs = []
254
+ for attr in attrs :
255
+ # Internal attributes map to the attribute's friendly_name
256
+ for req_attr in self .requested_attributes :
257
+ if req_attr ['friendly_name' ] == attr :
258
+ requested_attrs .append (
259
+ dict (
260
+ friendly_name = attr ,
261
+ required = req_attr ['required' ]
262
+ )
263
+ )
264
+
265
+ return requested_attrs
266
+
267
+ def _get_authn_request_args (
268
+ self , context , entity_id , requested_attributes = None
269
+ ):
270
+ kwargs = {}
271
+ authn_context = self .construct_requested_authn_context (entity_id )
272
+ _ , response_binding = self .sp .config .getattr (
273
+ "endpoints" , "sp"
274
+ )["assertion_consumer_service" ][0 ]
275
+ kwargs ["binding" ] = response_binding
276
+
277
+ if authn_context :
278
+ kwargs ["requested_authn_context" ] = authn_context
279
+ if self .config .get (SAMLBackend .KEY_MIRROR_FORCE_AUTHN ):
280
+ kwargs ["force_authn" ] = get_force_authn (
281
+ context , self .config , self .sp .config
282
+ )
283
+ if self .requested_attributes :
284
+ requested_attributes = self ._get_requested_attributes (
285
+ requested_attributes
286
+ )
287
+ if requested_attributes :
288
+ kwargs ["requested_attributes" ] = requested_attributes
289
+ return kwargs
290
+
291
+ def authn_request (self , context , entity_id , requested_attributes = None ):
236
292
"""
237
293
Do an authorization request on idp with given entity id.
238
294
This is the start of the authorization.
239
295
240
296
:type context: satosa.context.Context
241
297
:type entity_id: str
298
+ :type requested_attributes: list
242
299
:rtype: satosa.response.Response
243
300
244
301
:param context: The current context
@@ -257,15 +314,6 @@ def authn_request(self, context, entity_id):
257
314
logger .debug (logline , exc_info = False )
258
315
raise SATOSAAuthenticationError (context .state , "Selected IdP is blacklisted for this backend" )
259
316
260
- kwargs = {}
261
- authn_context = self .construct_requested_authn_context (entity_id )
262
- if authn_context :
263
- kwargs ["requested_authn_context" ] = authn_context
264
- if self .config .get (SAMLBackend .KEY_MIRROR_FORCE_AUTHN ):
265
- kwargs ["force_authn" ] = get_force_authn (
266
- context , self .config , self .sp .config
267
- )
268
-
269
317
try :
270
318
binding , destination = self .sp .pick_binding (
271
319
"single_sign_on_service" , None , "idpsso" , entity_id = entity_id
@@ -274,10 +322,10 @@ def authn_request(self, context, entity_id):
274
322
logline = lu .LOG_FMT .format (id = lu .get_session_id (context .state ), message = msg )
275
323
logger .debug (logline )
276
324
277
- acs_endp , response_binding = self .sp .config .getattr ("endpoints" , "sp" )["assertion_consumer_service" ][0 ]
278
- req_id , req = self .sp .create_authn_request (
279
- destination , binding = response_binding , ** kwargs
325
+ kwargs = self ._get_authn_request_args (
326
+ context , entity_id , requested_attributes = requested_attributes
280
327
)
328
+ req_id , req = self .sp .create_authn_request (destination , ** kwargs )
281
329
relay_state = util .rndstr ()
282
330
ht_args = self .sp .apply_binding (binding , "%s" % req , destination , relay_state = relay_state )
283
331
msg = "ht_args: {}" .format (ht_args )
@@ -363,6 +411,9 @@ def disco_response(self, context):
363
411
"""
364
412
info = context .request
365
413
state = context .state
414
+ requested_attributes = state .pop (
415
+ Context .KEY_REQUESTED_ATTRIBUTES , None
416
+ )
366
417
367
418
try :
368
419
entity_id = info ["entityID" ]
@@ -372,7 +423,11 @@ def disco_response(self, context):
372
423
logger .debug (logline , exc_info = True )
373
424
raise SATOSAAuthenticationError (state , "No IDP chosen" ) from err
374
425
375
- return self .authn_request (context , entity_id )
426
+ return self .authn_request (
427
+ context ,
428
+ entity_id ,
429
+ requested_attributes = requested_attributes
430
+ )
376
431
377
432
def _translate_response (self , response , state ):
378
433
"""
0 commit comments