@@ -82,6 +82,7 @@ class SAMLBackend(BackendModule, SAMLBaseModule):
8282 KEY_MIRROR_FORCE_AUTHN = 'mirror_force_authn'
8383 KEY_MEMORIZE_IDP = 'memorize_idp'
8484 KEY_USE_MEMORIZED_IDP_WHEN_FORCE_AUTHN = 'use_memorized_idp_when_force_authn'
85+ KEY_DYNAMIC_REQUESTED_ATTRIBUTES = 'dynamic_requested_attributes'
8586
8687 VALUE_ACR_COMPARISON_DEFAULT = 'exact'
8788
@@ -113,6 +114,9 @@ def __init__(self, outgoing, internal_attributes, config, base_url, name):
113114 self .encryption_keys = []
114115 self .outstanding_queries = {}
115116 self .idp_blacklist_file = config .get ('idp_blacklist_file' , None )
117+ self .requested_attributes = self .config .get (
118+ SAMLBackend .KEY_DYNAMIC_REQUESTED_ATTRIBUTES
119+ )
116120
117121 sp_keypairs = sp_config .getattr ('encryption_keypairs' , '' )
118122 sp_key_file = sp_config .getattr ('key_file' , '' )
@@ -170,15 +174,22 @@ def start_auth(self, context, internal_req):
170174 """
171175
172176 entity_id = self .get_idp_entity_id (context )
177+ requested_attributes = internal_req .get ("attributes" )
173178 if entity_id is None :
174179 # since context is not passed to disco_query
175180 # keep the information in the state cookie
176181 context .state [Context .KEY_FORCE_AUTHN ] = get_force_authn (
177182 context , self .config , self .sp .config
178183 )
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
179188 return self .disco_query (context )
180189
181- return self .authn_request (context , entity_id )
190+ return self .authn_request (
191+ context , entity_id , requested_attributes = requested_attributes
192+ )
182193
183194 def disco_query (self , context ):
184195 """
@@ -232,13 +243,59 @@ def construct_requested_authn_context(self, entity_id):
232243
233244 return authn_context
234245
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 ):
236292 """
237293 Do an authorization request on idp with given entity id.
238294 This is the start of the authorization.
239295
240296 :type context: satosa.context.Context
241297 :type entity_id: str
298+ :type requested_attributes: list
242299 :rtype: satosa.response.Response
243300
244301 :param context: The current context
@@ -257,15 +314,6 @@ def authn_request(self, context, entity_id):
257314 logger .debug (logline , exc_info = False )
258315 raise SATOSAAuthenticationError (context .state , "Selected IdP is blacklisted for this backend" )
259316
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-
269317 try :
270318 binding , destination = self .sp .pick_binding (
271319 "single_sign_on_service" , None , "idpsso" , entity_id = entity_id
@@ -274,10 +322,10 @@ def authn_request(self, context, entity_id):
274322 logline = lu .LOG_FMT .format (id = lu .get_session_id (context .state ), message = msg )
275323 logger .debug (logline )
276324
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
280327 )
328+ req_id , req = self .sp .create_authn_request (destination , ** kwargs )
281329 relay_state = util .rndstr ()
282330 ht_args = self .sp .apply_binding (binding , "%s" % req , destination , relay_state = relay_state )
283331 msg = "ht_args: {}" .format (ht_args )
@@ -363,6 +411,9 @@ def disco_response(self, context):
363411 """
364412 info = context .request
365413 state = context .state
414+ requested_attributes = state .pop (
415+ Context .KEY_REQUESTED_ATTRIBUTES , None
416+ )
366417
367418 try :
368419 entity_id = info ["entityID" ]
@@ -372,7 +423,11 @@ def disco_response(self, context):
372423 logger .debug (logline , exc_info = True )
373424 raise SATOSAAuthenticationError (state , "No IDP chosen" ) from err
374425
375- return self .authn_request (context , entity_id )
426+ return self .authn_request (
427+ context ,
428+ entity_id ,
429+ requested_attributes = requested_attributes
430+ )
376431
377432 def _translate_response (self , response , state ):
378433 """
0 commit comments