def create_authn_request(relay_state, selected_idp, required_loa, force_authn=False): kwargs = { "force_authn": str(force_authn).lower(), } # LOA current_app.logger.debug('Requesting AuthnContext {}'.format(required_loa)) loa_uri = current_app.config.authentication_context_map[required_loa] requested_authn_context = RequestedAuthnContext( authn_context_class_ref=AuthnContextClassRef(text=loa_uri), comparison='exact' ) kwargs['requested_authn_context'] = requested_authn_context # Authn algorithms kwargs['sign_alg'] = current_app.config.authn_sign_alg kwargs['digest_alg'] = current_app.config.authn_digest_alg client = Saml2Client(current_app.saml2_config) try: session_id, info = client.prepare_for_authenticate( entityid=selected_idp, relay_state=relay_state, binding=BINDING_HTTP_REDIRECT, **kwargs ) except TypeError: current_app.logger.error('Unable to know which IdP to use') raise oq_cache = OutstandingQueriesCache(session) oq_cache.set(session_id, relay_state) return info
def requested_authn_context(class_ref, comparison="minimum"): if not isinstance(class_ref, list): class_ref = [class_ref] return RequestedAuthnContext(authn_context_class_ref=[ AuthnContextClassRef(text=i) for i in class_ref ], comparison=comparison)
def get_authn_request(config, session, came_from, selected_idp, required_loa=None, force_authn=False): # Request the right AuthnContext for workmode # (AL1 for 'personal', AL2 for 'helpdesk' and AL3 for 'admin' by default) if required_loa is None: required_loa = config.get('required_loa', {}) workmode = config.get('workmode', 'personal') required_loa = required_loa.get(workmode, '') logger.debug('Requesting AuthnContext {!r}'.format(required_loa)) kwargs = { "requested_authn_context": RequestedAuthnContext( authn_context_class_ref=AuthnContextClassRef( text=required_loa ) ), "force_authn": str(force_authn).lower(), } client = Saml2Client(get_saml2_config(config['SAML2_SETTINGS_MODULE'])) try: (session_id, info) = client.prepare_for_authenticate( entityid=selected_idp, relay_state=came_from, binding=BINDING_HTTP_REDIRECT, **kwargs ) except TypeError: logger.error('Unable to know which IdP to use') raise oq_cache = OutstandingQueriesCache(session) oq_cache.set(session_id, came_from) return info
def _create_idp_response( self, authn_request_id='2aaaeb7692471eb4ba00d5546877a7fd', cls=Response): issue_instant = datetime.utcnow().isoformat() + 'Z' not_before = (datetime.utcnow() - timedelta(minutes=5)).isoformat() + 'Z' not_on_or_after = (datetime.utcnow() + timedelta(minutes=5)).isoformat() + 'Z' issuer = Issuer(format=NAMEID_FORMAT_ENTITY, text='http://nohost/auth') signature = pre_signature_part( 's2998eb2e03b5006acb0a931d0fb558b0e4ec360c7') status = Status(status_code=StatusCode(value=STATUS_SUCCESS)) subject_confirmation_data = SubjectConfirmationData( not_on_or_after=not_on_or_after, in_response_to=authn_request_id, recipient='http://nohost/') subject_confirmation = SubjectConfirmation( method=SCM_BEARER, subject_confirmation_data=subject_confirmation_data) subject = Subject(name_id=NameID(text='AABVSVesMLYDiHtowyX4MDu6UopU', format=NAMEID_FORMAT_TRANSIENT), subject_confirmation=subject_confirmation) conditions = Conditions(not_before=not_before, not_on_or_after=not_on_or_after, audience_restriction=AudienceRestriction( Audience('http://nohost/')), one_time_use=OneTimeUse()) authn_context = AuthnContext(authn_context_decl_ref=AuthnContextClassRef( 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport' )) authn_statement = AuthnStatement(authn_instant=issue_instant, authn_context=authn_context, session_index=self.session_index) attribute_statement = attribute_statement_from_string( self.attribute_xml) assertion = Assertion( id='s2bb879ef893d1b27fb90903e7c7e2779a3e7502c1', version='2.0', issue_instant=issue_instant, issuer=issuer, subject=subject, conditions=conditions, authn_statement=authn_statement, attribute_statement=attribute_statement, ) return cls(id='s2998eb2e03b5006acb0a931d0fb558b0e4ec360c7', in_response_to=authn_request_id, version='2.0', issue_instant=issue_instant, destination='http://nohost/', issuer=issuer, signature=signature, status=status, assertion=assertion)
def load_sso_kwargs_authn_context(self, sso_kwargs): # this would work when https://github.com/IdentityPython/pysaml2/pull/807 ac = getattr(self.conf, '_sp_requested_authn_context', {}) # this works even without https://github.com/IdentityPython/pysaml2/pull/807 # hopefully to be removed soon ! if not ac: scs = getattr(settings, 'SAML_CONFIG', {}).get('service', {}).get('sp', {}) ac = scs.get('requested_authn_context', {}) # end transitional things to be removed soon ! if ac: sso_kwargs["requested_authn_context"] = RequestedAuthnContext( authn_context_class_ref=[ AuthnContextClassRef(ac['authn_context_class_ref']), ], comparison=ac.get('comparison', "minimum"), )
def _redirect_to_auth(self, _cli, entity_id, came_from, vorg_name="", dont_send=False): try: _binding, destination = _cli.pick_binding("single_sign_on_service", self.bindings, "idpsso", entity_id=entity_id) logger.debug("binding: %s, destination: %s" % (_binding, destination)) if "accr" in self.kwargs: kwargs = { "requested_authn_context": RequestedAuthnContext( authn_context_class_ref=AuthnContextClassRef( text=self.kwargs["accr"])) } else: kwargs = {} req = _cli.create_authn_request(destination, vorg=vorg_name, **kwargs) _rstate = rndstr() self.cache.relay_state[_rstate] = came_from ht_args = _cli.apply_binding(_binding, "%s" % req, destination, relay_state=_rstate) _sid = req.id SESSIONDB[_sid] = self.kwargs logger.debug("ht_args: %s" % ht_args) except Exception, exc: logger.exception(exc) resp = ServiceError("Failed to construct the AuthnRequest: %s" % exc) return resp(self.environ, self.start_response)
def test_authn_request_with_acs_by_index(): # ACS index and location from SP metadata in servera.xml. ACS_INDEX = '4' ACS_LOCATION = 'http://lingon.catalogix.se:8087/another/path' # Create SP using the configuration found in servera_conf.py. sp = Saml2Client(config_file="servera_conf") # Generate an authn request object that uses AssertionConsumerServiceIndex # instead of AssertionConsumerServiceURL. The index with label ACS_INDEX # exists in the SP metadata in servera.xml. request_id, authn_request = sp.create_authn_request( sp.config.entityid, assertion_consumer_service_index=ACS_INDEX) assert authn_request.requested_authn_context.authn_context_class_ref == [ AuthnContextClassRef(accr) for accr in sp.config.getattr( "requested_authn_context").get("authn_context_class_ref") ] assert authn_request.requested_authn_context.comparison == ( sp.config.getattr("requested_authn_context").get("comparison")) # Make sure the authn_request contains AssertionConsumerServiceIndex. acs_index = getattr(authn_request, 'assertion_consumer_service_index', None) assert acs_index == ACS_INDEX # Create IdP. with closing(Server(config_file="idp_all_conf")) as idp: # Ask the IdP to pick out the binding and destination from the # authn_request. binding, destination = idp.pick_binding("assertion_consumer_service", request=authn_request) # Make sure the IdP pick_binding method picks the correct location # or destination based on the ACS index in the authn request. assert destination == ACS_LOCATION
def requested_authn_context(class_ref, comparison="minimum"): return RequestedAuthnContext( authn_context_class_ref=[AuthnContextClassRef(text=class_ref)], comparison=comparison)
def authn_context_class_ref(ref): return AuthnContext(authn_context_class_ref=AuthnContextClassRef(text=ref))
def create_authn_request( self, destination, vorg="", scoping=None, binding=BINDING_HTTP_POST, nameid_format=None, service_url_binding=None, message_id=0, consent=None, extensions=None, sign=None, sign_prepare=None, sign_alg=None, digest_alg=None, allow_create=None, requested_attributes=None, **kwargs, ): """ Creates an authentication request. :param destination: Where the request should be sent. :param vorg: The virtual organization the service belongs to. :param scoping: The scope of the request :param binding: The protocol to use for the Response !! :param nameid_format: Format of the NameIDPolicy :param service_url_binding: Where the reply should be sent dependent on reply binding. :param message_id: The identifier for this request :param consent: Whether the principal have given her consent :param extensions: Possible extensions :param sign: Whether the request should be signed or not. :param sign_prepare: Whether the signature should be prepared or not. :param sign_alg: The request signature algorithm :param digest_alg: The request digest algorithm :param allow_create: If the identity provider is allowed, in the course of fulfilling the request, to create a new identifier to represent the principal. :param requested_attributes: A list of dicts which define attributes to be used as eIDAS Requested Attributes for this request. If not defined the configuration option requested_attributes will be used, if defined. The format is the same as the requested_attributes configuration option. :param kwargs: Extra key word arguments :return: either a tuple of request ID and <samlp:AuthnRequest> instance or a tuple of request ID and str when sign is set to True """ args = {} # AssertionConsumerServiceURL # AssertionConsumerServiceIndex hide_assertion_consumer_service = self.config.getattr( 'hide_assertion_consumer_service', 'sp') assertion_consumer_service_url = ( kwargs.pop("assertion_consumer_service_urls", [None])[0] or kwargs.pop("assertion_consumer_service_url", None)) assertion_consumer_service_index = kwargs.pop( "assertion_consumer_service_index", None) service_url = (self.service_urls(service_url_binding or binding) or [None])[0] if hide_assertion_consumer_service: args["assertion_consumer_service_url"] = None binding = None elif assertion_consumer_service_url: args[ "assertion_consumer_service_url"] = assertion_consumer_service_url elif assertion_consumer_service_index: args[ "assertion_consumer_service_index"] = assertion_consumer_service_index elif service_url: args["assertion_consumer_service_url"] = service_url # ProviderName provider_name = kwargs.get("provider_name") if not provider_name and binding != BINDING_PAOS: provider_name = self._my_name() args["provider_name"] = provider_name requested_authn_context = (kwargs.pop("requested_authn_context", None) or self.config.getattr( "requested_authn_context", "sp") or {}) if isinstance(requested_authn_context, RequestedAuthnContext): args["requested_authn_context"] = requested_authn_context elif isinstance(requested_authn_context, Mapping): requested_authn_context_accrs = requested_authn_context.get( "authn_context_class_ref", []) requested_authn_context_comparison = requested_authn_context.get( "comparison", "exact") if requested_authn_context_accrs: args["requested_authn_context"] = RequestedAuthnContext( authn_context_class_ref=[ AuthnContextClassRef(accr) for accr in requested_authn_context_accrs ], comparison=requested_authn_context_comparison, ) else: logger.warning({ "message": "Cannot process requested_authn_context", "requested_authn_context": requested_authn_context, "type_of_requested_authn_context": type(requested_authn_context), }) # Allow argument values either as class instances or as dictionaries # all of these have cardinality 0..1 _msg = AuthnRequest() for param in ["scoping", "conditions", "subject"]: _item = kwargs.pop(param, None) if not _item: continue if isinstance(_item, _msg.child_class(param)): args[param] = _item else: raise ValueError( "Wrong type for param {name}".format(name=param)) # NameIDPolicy nameid_policy_format_config = self.config.getattr( "name_id_policy_format", "sp") nameid_policy_format = (nameid_format or nameid_policy_format_config or None) allow_create_config = self.config.getattr( "name_id_format_allow_create", "sp") allow_create = ( None # SAML 2.0 errata says AllowCreate MUST NOT be used for transient ids if nameid_policy_format == NAMEID_FORMAT_TRANSIENT else allow_create if allow_create else str( bool(allow_create_config)).lower()) name_id_policy = ( kwargs.pop("name_id_policy", None) if "name_id_policy" in kwargs else None if not nameid_policy_format else samlp.NameIDPolicy( allow_create=allow_create, format=nameid_policy_format)) if name_id_policy and vorg: name_id_policy.sp_name_qualifier = vorg name_id_policy.format = nameid_policy_format or NAMEID_FORMAT_PERSISTENT args["name_id_policy"] = name_id_policy # eIDAS SPType conf_sp_type = self.config.getattr('sp_type', 'sp') conf_sp_type_in_md = self.config.getattr('sp_type_in_metadata', 'sp') if conf_sp_type and conf_sp_type_in_md is False: if not extensions: extensions = Extensions() item = sp_type.SPType(text=conf_sp_type) extensions.add_extension_element(item) # eIDAS RequestedAttributes requested_attrs = (requested_attributes or self.config.getattr('requested_attributes', 'sp') or []) if requested_attrs: req_attrs_node = create_requested_attribute_node( requested_attrs, self.config.attribute_converters) if not extensions: extensions = Extensions() extensions.add_extension_element(req_attrs_node) # ForceAuthn force_authn = str( kwargs.pop("force_authn", None) or self.config.getattr("force_authn", "sp")).lower() in [ "true", "1" ] if force_authn: kwargs["force_authn"] = "true" if kwargs: _args, extensions = self._filter_args(AuthnRequest(), extensions, **kwargs) args.update(_args) args.pop("id", None) client_crt = kwargs.get("client_crt") nsprefix = kwargs.get("nsprefix") msg = self._message( AuthnRequest, destination, message_id, consent, extensions, sign, sign_prepare, sign_alg=sign_alg, digest_alg=digest_alg, protocol_binding=binding, scoping=scoping, nsprefix=nsprefix, **args, ) return msg