def _idp_initiated_login(self, spidentifier, relaystate): """ Perform an Idp-initiated login Exceptions are handled by the caller """ login = self.cfg.idp.get_login_handler() login.initIdpInitiatedAuthnRequest(spidentifier) # Hardcode for now, handle Artifact later login.request.protocolBinding = lasso.SAML2_METADATA_BINDING_POST login.processAuthnRequestMsg() if relaystate is not None: login.msgRelayState = relaystate else: provider = ServiceProvider(self.cfg, login.remoteProviderId) if provider.splink is not None: login.msgRelayState = provider.splink else: login.msgRelayState = login.remoteProviderId return login
def add_sps(self): if self.cfg.idp: for p in self.cfg.idp.get_providers(): try: sp = ServiceProvider(self.cfg, p) self.del_sp(sp.name) self.add_sp(sp.name, sp) except Exception as e: # pylint: disable=broad-except self.debug("Failed to find provider %s: %s" % (p, str(e)))
def _parse_request(self, message, hint=None, final=False): login = self.cfg.idp.get_login_handler() try: if hint: login.setSignatureVerifyHint(hint) login.processAuthnRequestMsg(message) except lasso.DsInvalidSigalgError as e: if login.remoteProviderId and not final: provider = ServiceProvider(self.cfg, login.remoteProviderId) if not provider.has_signing_keys: self.error('Invalid or missing signature, setting hint.') return self._parse_request( message, hint=provider.get_signature_hint(), final=True) msg = 'Invalid or missing signature algorithm %r [%r]' % (e, message) raise InvalidRequest(msg) except (lasso.ProfileInvalidMsgError, lasso.ProfileMissingIssuerError) as e: msg = 'Malformed Request %r [%r]' % (e, message) raise InvalidRequest(msg) except (lasso.ProfileInvalidProtocolprofileError, lasso.DsError) as e: msg = 'Invalid SAML Request: %r (%r [%r])' % (login.request, e, message) raise InvalidRequest(msg) except (lasso.ServerProviderNotFoundError, lasso.ProfileUnknownProviderError) as e: msg = 'Invalid SP [%s] (%r [%r])' % (login.remoteProviderId, e, message) raise UnknownProvider(msg) self.debug('SP %s requested authentication' % login.remoteProviderId) return login
def saml2checks(self, login): us = UserSession() user = us.get_user() if user.is_anonymous: if self.stage == 'init': returl = '%s/saml2/SSO/Continue?%s' % ( self.basepath, self.trans.get_GET_arg()) data = { 'saml2_stage': 'auth', 'saml2_request': login.dump(), 'login_return': returl, 'login_target': login.remoteProviderId } self.trans.store(data) redirect = '%s/login?%s' % (self.basepath, self.trans.get_GET_arg()) raise cherrypy.HTTPRedirect(redirect) else: raise AuthenticationError("Unknown user", lasso.SAML2_STATUS_CODE_AUTHN_FAILED) self._audit("Logged in user: %s [%s]" % (user.name, user.fullname)) # We can wipe the transaction now, as this is the last step self.trans.wipe() # TODO: check if this is the first time this user access this SP # If required by user prefs, ask user for consent once and then # record it consent = True # TODO: check destination try: provider = ServiceProvider(self.cfg, login.remoteProviderId) nameidfmt = provider.get_valid_nameid(login.request.nameIdPolicy) except NameIdNotAllowed as e: raise AuthenticationError( str(e), lasso.SAML2_STATUS_CODE_INVALID_NAME_ID_POLICY) except InvalidProviderId as e: raise AuthenticationError(str(e), lasso.SAML2_STATUS_CODE_AUTHN_FAILED) # TODO: check login.request.forceAuthn login.validateRequestMsg(not user.is_anonymous, consent) authtime = datetime.datetime.utcnow() skew = datetime.timedelta(0, 60) authtime_notbefore = authtime - skew authtime_notafter = authtime + skew # Let's first do the attribute mapping, so we could map the username # Check attribute policy and perform mapping and filtering. # If the SP has its own mapping or filtering policy use that # instead of the global policy. if (provider.attribute_mappings is not None and len(provider.attribute_mappings) > 0): attribute_mappings = provider.attribute_mappings else: attribute_mappings = self.cfg.default_attribute_mapping if (provider.allowed_attributes is not None and len(provider.allowed_attributes) > 0): allowed_attributes = provider.allowed_attributes else: allowed_attributes = self.cfg.default_allowed_attributes self.debug("Allowed attrs: %s" % allowed_attributes) self.debug("Mapping: %s" % attribute_mappings) policy = Policy(attribute_mappings, allowed_attributes) userattrs = us.get_user_attrs() mappedattrs, _ = policy.map_attributes(userattrs) attributes = policy.filter_attributes(mappedattrs) if '_groups' in attributes and 'groups' not in attributes: attributes['groups'] = attributes['_groups'] self.debug("%s's attributes: %s" % (user.name, attributes)) # Perform authorization check. # We use the raw userattrs here so that we can make decisions based # on attributes we don't want to send to the SP provinfo = { 'name': provider.name, 'url': provider.splink, 'owner': provider.owner } if not self._site['authz'].authorize_user('saml2', provinfo, user.name, userattrs): self.trans.wipe() self.error('Authorization denied by authorization provider') raise AuthenticationError("Authorization denied", lasso.SAML2_STATUS_CODE_AUTHN_FAILED) # TODO: get authentication type fnd name format from session # need to save which login manager authenticated and map it to a # saml2 authentication context authn_context = lasso.SAML2_AUTHN_CONTEXT_UNSPECIFIED timeformat = '%Y-%m-%dT%H:%M:%SZ' login.buildAssertion(authn_context, authtime.strftime(timeformat), None, authtime_notbefore.strftime(timeformat), authtime_notafter.strftime(timeformat)) nameid = None if nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT: idpsalt = self.cfg.idp_nameid_salt if idpsalt is None: raise AuthenticationError( "idp nameid salt is not set in configuration") value = hashlib.sha512() value.update(idpsalt) value.update(login.remoteProviderId) value.update(mappedattrs.get('_username')) nameid = '_' + value.hexdigest() elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT: nameid = '_' + uuid.uuid4().hex elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_KERBEROS: nameid = userattrs.get('gssapi_principal_name') elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL: nameid = mappedattrs.get('email') if not nameid: nameid = '%s@%s' % (user.name, self.cfg.default_email_domain) elif nameidfmt == lasso.SAML2_NAME_IDENTIFIER_FORMAT_UNSPECIFIED: nameid = provider.normalize_username(mappedattrs.get('_username')) if nameid: login.assertion.subject.nameId.format = nameidfmt login.assertion.subject.nameId.content = nameid else: self.trans.wipe() self.error('Authentication succeeded but it was not ' + 'provided by NameID %s' % nameidfmt) raise AuthenticationError("Unavailable Name ID type", lasso.SAML2_STATUS_CODE_AUTHN_FAILED) # The saml-core-2.0-os specification section 2.7.3 requires # the AttributeStatement element to be non-empty. if attributes: if not login.assertion.attributeStatement: attrstat = lasso.Saml2AttributeStatement() login.assertion.attributeStatement = [attrstat] else: attrstat = login.assertion.attributeStatement[0] if not attrstat.attribute: attrstat.attribute = () for key in attributes: # skip internal info if key[0] == '_': continue values = attributes[key] if isinstance(values, dict): continue if not isinstance(values, list): values = [values] attr = lasso.Saml2Attribute() attr.name = key attr.nameFormat = lasso.SAML2_ATTRIBUTE_NAME_FORMAT_BASIC attr.attributeValue = [] vals = [] for value in values: if value is None: self.log('Ignoring None value for attribute %s' % key) continue self.debug('value %s' % value) node = lasso.MiscTextNode.newWithString(value) node.textChild = True attrvalue = lasso.Saml2AttributeValue() attrvalue.any = [node] vals.append(attrvalue) attr.attributeValue = vals attrstat.attribute = attrstat.attribute + (attr, ) self.debug('Assertion: %s' % login.assertion.dump()) saml_sessions = self.cfg.idp.sessionfactory lasso_session = lasso.Session() lasso_session.addAssertion(login.remoteProviderId, login.assertion) provider = ServiceProvider(self.cfg, login.remoteProviderId) saml_sessions.add_session(login.assertion.id, login.remoteProviderId, user.name, lasso_session.dump(), None, provider.logout_mechs)