From a24143c55e37ed11aaefd9b9355a4131d34d78a2 Mon Sep 17 00:00:00 2001 From: eraviart <> Date: Mon, 19 Jul 2004 22:34:24 +0000 Subject: [PATCH] Liberty Alliance login now works (but may be a raise is not a clean way to show that it works). --- extra-modules/inscriptions.py | 7 +- src/core/dataholders.py | 12 +- src/core/http.py | 13 +- src/core/main.py | 1 + src/core/sessions.py | 1 + src/modules/dbxmlcontainers.py | 4 +- src/modules/identities.py | 67 ++++++- src/modules/libertyalliance.py | 166 ++++++++++++------ src/modules/libertyallianceaccounts.py | 15 +- src/modules/libertyallianceresponses.py | 10 +- src/modules/passwordaccounts.py | 11 +- vhosts/identity-provider/_config.xml | 1 + .../liberty-alliance/liberty-alliance.xml | 1 + 13 files changed, 231 insertions(+), 78 deletions(-) diff --git a/extra-modules/inscriptions.py b/extra-modules/inscriptions.py index 38f8294..e4c3011 100644 --- a/extra-modules/inscriptions.py +++ b/extra-modules/inscriptions.py @@ -25,6 +25,8 @@ """Inscriptions Module""" +import os + import libxml2 import dataholders @@ -367,7 +369,10 @@ class InscriptionHolder(dataholders.XmlHolder): return None # We replace " " by "-" instead of "_" because this is better for Google # indexing. - return strings.simplify(email) + publicName = strings.simplify(email) + if self.isRootElder: + publicName = os.path.join(self.getDirectoryAbsolutePath(), publicName) + return publicName def setPublicName(self, publicName): pass diff --git a/src/core/dataholders.py b/src/core/dataholders.py index 1feb071..6eb0073 100644 --- a/src/core/dataholders.py +++ b/src/core/dataholders.py @@ -808,7 +808,10 @@ class XmlHolder(DataHolder, documents.AbstractDocument): else: doc = self.parseDataFile() self._node = doc - self.publicName = self.localId + if self.isRootElder: + self.publicName = self.getAbsolutePath() + else: + self.publicName = self.localId doc.xincludeProcess() # Now that we know what is the root node, we can change the class of the holder. @@ -901,7 +904,12 @@ class XmlHolder(DataHolder, documents.AbstractDocument): fileSystem = self.fileSystem fileSystemPath = self.getFileSystemPath() if isRenamed: - fileSystem.rename(oldFileSystemPath, fileSystemPath) + try: + fileSystem.rename(oldFileSystemPath, fileSystemPath) + except OSError: + logs.debug('oldFileSystemPath = "%s", fileSystemPath = "%s"' % ( + oldFileSystemPath, fileSystemPath)) + raise dataFile = fileSystem.open(fileSystemPath, "wb") dataFile.write(data) dataFile.close() diff --git a/src/core/http.py b/src/core/http.py index 0be1a7c..6a30b40 100644 --- a/src/core/http.py +++ b/src/core/http.py @@ -318,7 +318,6 @@ class HttpRequestHandlerMixin: message = """Access to "%s" Forbidden.""" % filePath if station == "current": station = environs.getVar("currentStation", default = None) - self.setCookie() logs.info(message) data = "%s" % message if station: @@ -331,7 +330,7 @@ class HttpRequestHandlerMixin: mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt) if mimeType == "text/html": data = styledData - return self.send_error(403, message, data) + return self.send_error(403, message, data, setCookie = True) def outputErrorBadRequest(self, reason, station = "current"): if reason: @@ -393,7 +392,6 @@ class HttpRequestHandlerMixin: message = """Path "%s" Not Found.""" % filePath if station == "current": station = environs.getVar("currentStation", default = None) - self.setCookie() logs.info(message) data = "%s" % message if station: @@ -406,7 +404,7 @@ class HttpRequestHandlerMixin: mimeType, styledData = station.generateXmlDocumentStyledData(xmlDocument, xslt) if mimeType == "text/html": data = styledData - return self.send_error(404, message, data) + return self.send_error(404, message, data, setCookie = True) def outputErrorUnauthorized(self, filePath, station = "current"): if filePath is None: @@ -415,7 +413,6 @@ class HttpRequestHandlerMixin: message = """Access to "%s" Unauthorized.""" % filePath if station == "current": station = environs.getVar("currentStation", default = None) - self.setCookie() logs.info(message) data = "%s" % message headers = {} @@ -432,7 +429,7 @@ class HttpRequestHandlerMixin: if station.getConfigBoolean("yep:useHttpAuthentication", default = False): headers["WWW-Authenticate"] = 'Basic realm="%s"' % station.getConfigString( "yep:title", default = "Expression") - return self.send_error(401, message, data, headers) + return self.send_error(401, message, data, headers, setCookie = True) def outputInformationContinue(self): message = "Continue" @@ -584,7 +581,7 @@ Client: %(clientIp)s return self.outputErrorInternalServer() - def send_error(self, code, message = None, data = None, headers = None): + def send_error(self, code, message = None, data = None, headers = None, setCookie = False): # We need to save the session before sending response, otherwise, the # server may receive a new HTTP request before the session is saved. session = environs.getVar("session", default = None) @@ -605,6 +602,8 @@ Client: %(clientIp)s if headers is not None: for name, value in headers.items(): self.send_header(name, value) + if setCookie: + self.setCookie() self.end_headers() if self.command != "HEAD": self.wfile.write(data) diff --git a/src/core/main.py b/src/core/main.py index e9296d6..864ef56 100644 --- a/src/core/main.py +++ b/src/core/main.py @@ -251,6 +251,7 @@ class Application(object): if submission.hasField("login") and not authorization \ and rootDataHolder.getConfigBoolean("yep:useHttpAuthentication", default = False): + # Ask for HTTP authentication. return httpRequestHandler.outputErrorUnauthorized(httpPath) if submission.hasField("logout") and authorization: # Since HTTP authentication provides no way to logout, we send a status diff --git a/src/core/sessions.py b/src/core/sessions.py index 101bb41..e4f37d8 100644 --- a/src/core/sessions.py +++ b/src/core/sessions.py @@ -25,6 +25,7 @@ """Sessions Module""" +import errno import os import socket diff --git a/src/modules/dbxmlcontainers.py b/src/modules/dbxmlcontainers.py index c666199..b17b835 100644 --- a/src/modules/dbxmlcontainers.py +++ b/src/modules/dbxmlcontainers.py @@ -358,7 +358,9 @@ class DbXmlDocumentStation(stations.AbstractStation, documents.AbstractDocument) def getPublicName(self): publicName = self.node.prop("publicName") if publicName is None: - return "" + return None + if self.isRootElder: + publicName = os.path.join(self.getDirectoryAbsolutePath(), publicName) return publicName def outputHttpSource(self): diff --git a/src/modules/identities.py b/src/modules/identities.py index abb986f..e7329d8 100644 --- a/src/modules/identities.py +++ b/src/modules/identities.py @@ -25,12 +25,18 @@ """Identities Module""" +import errno + import libxml2 +import expression.core.dataholders as dataholders import expression.core.directories as directories import expression.core.elements as elements import expression.core.faults as faults +import expression.core.filesystems as filesystems +import expression.core.logs as logs import expression.core.namespaces as namespaces +import expression.core.sessions as sessions import expression.core.stations as stations import expression.core.things as things @@ -38,8 +44,17 @@ from persons import Person class Identity(things.Thing): + _libertyAllianceAccount = None _session = None + def deleteLibertyAllianceAccountPath(self): + nodes = self.evaluateXpath("yep:libertyAllianceAccount") + if nodes: + node = nodes[0] + node.unlinkNode() + node.freeNode() + self.getDataHolder().isDirty = True + def deleteSessionToken(self): nodes = self.evaluateXpath("yep:sessionToken") if nodes: @@ -64,6 +79,44 @@ class Identity(things.Thing): person = personHolder.getRootElement() return person.getLanguage() + def getLibertyAllianceAccount(self): + if self._libertyAllianceAccount is None: + libertyAllianceAccountAbsolutePath = self.getLibertyAllianceAccountAbsolutePath() + if libertyAllianceAccountAbsolutePath: + try: + libertyAllianceAccountHolder = dataholders.DataHolder( + pathFragment = libertyAllianceAccountAbsolutePath, mimeType = "text/xml", + isRootElder = True, + containedFileSystem = filesystems.PartialFileSystem( + libertyAllianceAccountAbsolutePath)) + except IOError, error: + if error.errno == errno.ENOENT: + logs.debug("""LibertyAllianceAccount at path "%s" doesn't exist.""" + % libertyAllianceAccountAbsolutePath) + self._libertyAllianceAccount = "none" + else: + raise + else: + self._libertyAllianceAccount = libertyAllianceAccountHolder.getRootElement() + else: + libertyAllianceAccountNodes = self.evaluateXpath("yep:libertyAllianceAccount") + if libertyAllianceAccountNodes: + self._libertyAllianceAccount = elements.newElement( + libertyAllianceAccountNodes[0], previous = self, owner = self) + else: + self._libertyAllianceAccount = "none" + if self._libertyAllianceAccount == "none": + return None + return self._libertyAllianceAccount + + def getLibertyAllianceAccountAbsolutePath(self): + nodes = self.evaluateXpath("yep:libertyAllianceAccount/@src") + if nodes: + libertyAllianceAccountPath = nodes[0].content + return self.convertPathToAbsolute(libertyAllianceAccountPath) + else: + return None + def getPerson(self): if self.getPersonLocation(): personHolder = self.walkToLocation(self.getPersonLocation()) @@ -88,7 +141,7 @@ class Identity(things.Thing): if sessionToken is None: self._session = "none" else: - self._session = sessions.retrieveSession(getSessionToken) + self._session = sessions.retrieveSession(sessionToken) if self._session == "none": return None return self._session @@ -106,6 +159,12 @@ class Identity(things.Thing): else: return person.getSimpleLabel() + def getUser(self): + # Some times, users are stored in independant files; some times they are embedded inside + # accounts. + # => This method is needed to ease user retrieval. + return self + def getVoteToken(self, ballotBoxLocalId): votesNodes = self.evaluateXpath("yep:votes") if not votesNodes: @@ -120,6 +179,12 @@ class Identity(things.Thing): return None return tokenNodes[0].content + def setLibertyAllianceAccountAbsolutePath(self, libertyAllianceAccountAbsolutePath): + libertyAllianceAccountBaseRelativePath = self.convertAbsolutePathToBaseRelative( + libertyAllianceAccountAbsolutePath) + self.setElementAttribute( + "yep:libertyAllianceAccount", "src", libertyAllianceAccountBaseRelativePath) + def setPersonLocation(self, personLocation): self.setElementAttribute("yep:person", "src", personLocation) diff --git a/src/modules/libertyalliance.py b/src/modules/libertyalliance.py index 367d2a9..f11f74b 100644 --- a/src/modules/libertyalliance.py +++ b/src/modules/libertyalliance.py @@ -142,7 +142,7 @@ class LibertyAlliance(things.Thing): authenticationMethod = session.getAuthenticationMethod() if authenticationMethod == "password": - authenticationMethod = lasso.samlAuthenticationMethodPassword + lassoAuthenticationMethod = lasso.samlAuthenticationMethodPassword else: raise Exception("Unknown authentication method %s" % authenticationMethod) @@ -207,7 +207,7 @@ class LibertyAlliance(things.Thing): idpNameIdentifierFormat = "onetime" authenticationStatement = lasso.AuthenticationStatement( - authenticationMethod, + lassoAuthenticationMethod, "2005-05-03T16:12:00Z", # reauthenticateOnOrAfter nameIdentifier = nameIdentifier, nameQualifier = serviceProviderId, @@ -369,20 +369,22 @@ You have been succesfully authenticated; click ok to go back to the service prov account = accountHolder.getRootElement() account.setNameIdentifier(nameIdentifier) account.setUserAbsolutePath(user.getAbsolutePath()) + user.setLibertyAllianceAccountAbsolutePath(account.getAbsolutePath()) userContextDump = None else: account = accountHolder.getRootElement() + user = account.getUser() userContextDump = account.getUserContextDump() loginContext.create_user(userContextDump) # Save the new or updated userContext into account. account.setUserContextDump(loginContext.user.dump()) # Retrieve the authentication method and store it in account. - authenticationMethod = loginContext.response.get_child( + lassoAuthenticationMethod = loginContext.response.get_child( "AuthenticationStatement").get_attr_value("AuthenticationMethod") - if authenticationMethod == lasso.samlAuthenticationMethodPassword: + if lassoAuthenticationMethod == lasso.samlAuthenticationMethodPassword: authenticationMethod = "password" else: - logs.debug('Unknown authentication method = "%s"' % authenticationMethod) + logs.debug('Unknown authentication method = "%s"' % lassoAuthenticationMethod) account.setAuthenticationMethod(authenticationMethod) account.getDocument().save() # User is now authenticated => create session, cookie... @@ -595,9 +597,10 @@ You have been succesfully authenticated; click ok to go back to the service prov serverContextDump = self.getServerContextDump() serverContext = lasso.Server.new_from_dump(serverContextDump) userContext = None + account = None user = environs.getVar("user") if user is not None: - account = user.getAccount() + account = user.getLibertyAllianceAccount() if account is not None: userContextDump = account.getUserContextDump() if userContextDump is not None: @@ -616,61 +619,110 @@ You have been succesfully authenticated; click ok to go back to the service prov mustAuthenticate = loginContext.must_authenticate() logs.debug("User must be authenticated = %s" % mustAuthenticate) if mustAuthenticate: - # FIXME: Authenticate the user and set the right authenticationMethod. - userAuthenticated = True - authenticationMethod = lasso.samlAuthenticationMethodPassword - if loginContext.protocolProfile == lasso.loginProtocolProfileBrwsArt: - # We can send the artifact either using a redirect or a POST. It is an IDP choice, - # but redirect is better than POST (no need for a page with a form). - useRedirectForArtifact = True - if useRedirectForArtifact: - errorCode = loginContext.build_artifact_msg( - userAuthenticated, authenticationMethod, "FIXME: reauthenticateOnOrAfter", - lasso.httpMethodRedirect) - if errorCode: - raise Exception("Lasso login error %s" % errorCode) - logs.debug("msg_url = %s; assertionArtifact = %s; response_dump = %s" % ( - loginContext.msg_url, loginContext.assertionArtifact, - loginContext.response_dump)) - - # Save response dump, to retrieve it later, when receiving a request with the - # same artifact. - responsesPath = self.getResponsesDirectoryAbsolutePath() - responsesHolder = directories.DirectoryHolder( - pathFragment = responsesPath, previous = environs.getVar("rootStation"), - isRootElder = True, - containedFileSystem = filesystems.PartialFileSystem(responsesPath)) - responseFeature = modules.getElementFeature( - namespaces.yep.uri, "libertyAllianceResponse") - responseHolder = responseFeature.newXmlHolder( - responsesHolder, pathFragment = loginContext.assertionArtifact + ".xml") - response = responseHolder.getRootElement() - response.setArtifact(loginContext.assertionArtifact) - response.setResponseDump(loginContext.response_dump) - response.getDocument().save() - - return environs.getVar("httpRequestHandler").outputRedirect( - loginContext.msg_url) + if self.getConfigBoolean("yep:useHttpAuthentication", default = False): + # HTTP authentication. + if user is None: + return environs.getVar("httpRequestHandler").outputErrorUnauthorized( + self.getUriAbsolutePath()) else: - errorCode = loginContext.build_artifact_msg( - userAuthenticated, authenticationMethod, "FIXME: reauthenticateOnOrAfter", - lasso.httpMethodPost) - if errorCode: - raise Exception("Lasso login error %s" % errorCode) - logs.debug("msg_url = %s; msg_body = %s; msg_relayState = %s; " - "assertionArtifact = %s; response_dump = %s" % ( - loginContext.msg_url, loginContext.msg_body, loginContext.msg_relayState, - loginContext.assertionArtifact, loginContext.response_dump)) - raise "FIXME" - else: # loginContext.protocolProfile == lasso.loginProtocolProfileBrwsPost: - errorCode = loginContext.build_authn_response_msg( - userAuthenticated, authenticationMethod, "FIXME: reauthenticateOnOrAfter") + # The user is already authenticated using HTTP authentication. + userAuthenticated = True + authenticationMethod = "password" + else: + # FIXME: Authenticate the user and set the right authenticationMethod. + raise "TODO" + else: + userAuthenticated = True + authenticationMethod = "password" # FIXME + if authenticationMethod == "password": + lassoAuthenticationMethod = lasso.samlAuthenticationMethodPassword + else: + logs.debug('Unknown authentication method = "%s"' % authenticationMethod) + if loginContext.protocolProfile == lasso.loginProtocolProfileBrwsArt: + # We can send the artifact either using a redirect or a POST. It is an IDP choice, + # but redirect is better than POST (no need for a page with a form). + useRedirectForArtifact = True + if useRedirectForArtifact: + errorCode = loginContext.build_artifact_msg( + userAuthenticated, lassoAuthenticationMethod, + "FIXME: reauthenticateOnOrAfter", lasso.httpMethodRedirect) if errorCode: raise Exception("Lasso login error %s" % errorCode) - logs.debug("msg_url = %s; msg_body = %s; msg_relayState = %s" % ( - loginContext.msg_url, loginContext.msg_body, loginContext.msg_relayState)) + logs.debug("msg_url = %s; assertionArtifact = %s; response_dump = %s" % ( + loginContext.msg_url, loginContext.assertionArtifact, + loginContext.response_dump)) + nameIdentifier = loginContext.nameIdentifier + + # Save user dump and name identifier. + if account is None: + accountsPath = self.getAccountsDirectoryAbsolutePath() + accountsHolder = directories.DirectoryHolder( + pathFragment = accountsPath, previous = environs.getVar("rootStation"), + isRootElder = True, + containedFileSystem = filesystems.PartialFileSystem(accountsPath)) + accountLocalId = accountsHolder.getItemLocalId(nameIdentifier) + if accountLocalId is None: + accountHolder = None + else: + accountHolder = accountsHolder.getItem(accountLocalId) + if accountHolder is None: + # There is no account indexed by this name identifier. Create it and + # link it to the user. + accountFeature = modules.getElementFeature( + namespaces.yep.uri, "libertyAllianceAccount") + accountHolder = accountFeature.newXmlHolder( + accountsHolder, pathFragment = nameIdentifier + ".xml") + account = accountHolder.getRootElement() + account.setNameIdentifier(nameIdentifier) + else: + account = accountHolder.getRootElement() + logs.warning( + 'Liberty Alliance account "%s" was not referenced by user %s' + % (account.getAbsolutePath(), user.getAbsolutePath())) + account.setUserAbsolutePath(user.getAbsolutePath()) + user.setLibertyAllianceAccountAbsolutePath(account.getAbsolutePath()) + user.getDocument().save() + # Save the new or updated userContext into account. + account.setUserContextDump(loginContext.user.dump()) + account.setAuthenticationMethod(authenticationMethod) + account.getDocument().save() + + # Save response dump, to retrieve it later, when receiving a request with the + # same artifact. + responsesPath = self.getResponsesDirectoryAbsolutePath() + responsesHolder = directories.DirectoryHolder( + pathFragment = responsesPath, previous = environs.getVar("rootStation"), + isRootElder = True, + containedFileSystem = filesystems.PartialFileSystem(responsesPath)) + responseFeature = modules.getElementFeature( + namespaces.yep.uri, "libertyAllianceResponse") + responseHolder = responseFeature.newXmlHolder( + responsesHolder, pathFragment = loginContext.assertionArtifact + ".xml") + response = responseHolder.getRootElement() + response.setArtifact(loginContext.assertionArtifact) + response.setResponseDump(loginContext.response_dump) + response.getDocument().save() + + return environs.getVar("httpRequestHandler").outputRedirect( + loginContext.msg_url) + else: + errorCode = loginContext.build_artifact_msg( + userAuthenticated, lassoAuthenticationMethod, + "FIXME: reauthenticateOnOrAfter", lasso.httpMethodPost) + if errorCode: + raise Exception("Lasso login error %s" % errorCode) + logs.debug("msg_url = %s; msg_body = %s; msg_relayState = %s; " + "assertionArtifact = %s; response_dump = %s" % ( + loginContext.msg_url, loginContext.msg_body, loginContext.msg_relayState, + loginContext.assertionArtifact, loginContext.response_dump)) raise "FIXME" - else: + else: # loginContext.protocolProfile == lasso.loginProtocolProfileBrwsPost: + errorCode = loginContext.build_authn_response_msg( + userAuthenticated, lassoAuthenticationMethod, "FIXME: reauthenticateOnOrAfter") + if errorCode: + raise Exception("Lasso login error %s" % errorCode) + logs.debug("msg_url = %s; msg_body = %s; msg_relayState = %s" % ( + loginContext.msg_url, loginContext.msg_body, loginContext.msg_relayState)) raise "FIXME" raise "TODO" diff --git a/src/modules/libertyallianceaccounts.py b/src/modules/libertyallianceaccounts.py index 9bde6c2..1f2a6a0 100644 --- a/src/modules/libertyallianceaccounts.py +++ b/src/modules/libertyallianceaccounts.py @@ -26,9 +26,11 @@ import errno +import os import expression.core.dataholders as dataholders import expression.core.elements as elements +import expression.core.filesystems as filesystems import expression.core.namespaces as namespaces import expression.core.things as things @@ -99,7 +101,10 @@ class LibertyAllianceAccount(things.Thing): else: raise else: - self._user = userHolder.getRootElement() + # Some times, users are stored in independant files; some times they are + # embedded inside accounts. + accountOrUser = userHolder.getRootElement() + self._user = accountOrUser.getUser() else: userNodes = self.evaluateXpath("yep:user") if userNodes: @@ -144,10 +149,12 @@ class LibertyAllianceAccountHolder(dataholders.XmlHolder): pass def getPublicName(self): - nameIdentifier = self.getRootElement().getNameIdentifier() - if nameIdentifier is None: + publicName = self.getRootElement().getNameIdentifier() + if publicName is None: return None - return nameIdentifier + if self.isRootElder: + publicName = os.path.join(self.getDirectoryAbsolutePath(), publicName) + return publicName def setPublicName(self, publicName): pass diff --git a/src/modules/libertyallianceresponses.py b/src/modules/libertyallianceresponses.py index 252fcc5..bb58516 100644 --- a/src/modules/libertyallianceresponses.py +++ b/src/modules/libertyallianceresponses.py @@ -25,6 +25,8 @@ """Liberty Alliance Responses Module""" +import os + import expression.core.dataholders as dataholders import expression.core.elements as elements import expression.core.namespaces as namespaces @@ -72,10 +74,12 @@ class LibertyAllianceResponseHolder(dataholders.XmlHolder): pass def getPublicName(self): - artifact = self.getRootElement().getArtifact() - if artifact is None: + publicName = self.getRootElement().getArtifact() + if publicName is None: return None - return artifact + if self.isRootElder: + publicName = os.path.join(self.getDirectoryAbsolutePath(), publicName) + return publicName def setPublicName(self, publicName): pass diff --git a/src/modules/passwordaccounts.py b/src/modules/passwordaccounts.py index b30e4ce..9d5a207 100644 --- a/src/modules/passwordaccounts.py +++ b/src/modules/passwordaccounts.py @@ -26,6 +26,7 @@ import errno +import os import expression.core.dataholders as dataholders import expression.core.directories as directories @@ -246,7 +247,10 @@ class PasswordAccount(things.Thing): else: raise else: - self._user = userHolder.getRootElement() + # Some times, users are stored in independant files; some times they are + # embedded inside accounts. + accountOrUser = userHolder.getRootElement() + self._user = accountOrUser.getUser() else: userNodes = self.evaluateXpath("yep:user") if userNodes: @@ -288,7 +292,10 @@ class PasswordAccountHolder(dataholders.XmlHolder): email = self.getRootElement().email if email is None: return None - return strings.simplify(email) + publicName = strings.simplify(email) + if self.isRootElder: + publicName = os.path.join(self.getDirectoryAbsolutePath(), publicName) + return publicName def setPublicName(self, publicName): pass diff --git a/vhosts/identity-provider/_config.xml b/vhosts/identity-provider/_config.xml index c2f0b98..6991e2e 100644 --- a/vhosts/identity-provider/_config.xml +++ b/vhosts/identity-provider/_config.xml @@ -2,4 +2,5 @@ list sessions + true diff --git a/vhosts/identity-provider/liberty-alliance/liberty-alliance.xml b/vhosts/identity-provider/liberty-alliance/liberty-alliance.xml index 2daef6c..b4ffd15 100644 --- a/vhosts/identity-provider/liberty-alliance/liberty-alliance.xml +++ b/vhosts/identity-provider/liberty-alliance/liberty-alliance.xml @@ -7,6 +7,7 @@ ../security/public/service-provider-liberty-alliance.key ../security/public/ca-liberty-alliance.crt + accounts responses