# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-from ipsilon.providers.common import ProviderBase, ProviderPageBase
+from ipsilon.providers.common import ProviderBase, ProviderPageBase, \
+ ProviderInstaller
from ipsilon.providers.saml2.auth import AuthenticateRequest
from ipsilon.providers.saml2.logout import LogoutRequest
from ipsilon.providers.saml2.admin import Saml2AdminPage
+from ipsilon.providers.saml2.rest import Saml2RestBase
from ipsilon.providers.saml2.provider import IdentityProvider
from ipsilon.tools.certs import Certificate
from ipsilon.tools import saml2metadata as metadata
import lasso
import os
import time
+import uuid
class Redirect(AuthenticateRequest):
def __init__(self, *pargs):
super(IdpProvider, self).__init__('saml2', 'saml2', *pargs)
self.admin = None
+ self.rest = None
self.page = None
self.idp = None
self.description = """
'idp key file',
'The IdP Certificate Key genearated at install time.',
'certificate.key'),
+ pconfig.String(
+ 'idp nameid salt',
+ 'The salt used for persistent Name IDs.',
+ None),
pconfig.Condition(
'allow self registration',
'Allow authenticated users to register applications.',
'default allowed nameids',
'Default Allowed NameIDs for Service Providers.',
metadata.SAML2_NAMEID_MAP.keys(),
- ['persistent', 'transient', 'email', 'kerberos', 'x509']),
+ ['unspecified', 'persistent', 'transient', 'email',
+ 'kerberos', 'x509']),
pconfig.Pick(
'default nameid',
'Default NameID used by Service Providers.',
metadata.SAML2_NAMEID_MAP.keys(),
- 'persistent'),
+ 'unspecified'),
pconfig.String(
'default email domain',
'Used for users missing the email property.',
'example.com'),
+ pconfig.MappingList(
+ 'default attribute mapping',
+ 'Defines how to map attributes before returning them to SPs',
+ [['*', '*']]),
+ pconfig.ComplexList(
+ 'default allowed attributes',
+ 'Defines a list of allowed attributes, applied after mapping',
+ ['*']),
)
if cherrypy.config.get('debug', False):
import logging
return os.path.join(self.idp_storage_path,
self.get_config_value('idp key file'))
+ @property
+ def idp_nameid_salt(self):
+ return self.get_config_value('idp nameid salt')
+
@property
def default_allowed_nameids(self):
return self.get_config_value('default allowed nameids')
def default_email_domain(self):
return self.get_config_value('default email domain')
+ @property
+ def default_attribute_mapping(self):
+ return self.get_config_value('default attribute mapping')
+
+ @property
+ def default_allowed_attributes(self):
+ return self.get_config_value('default allowed attributes')
+
def get_tree(self, site):
self.idp = self.init_idp()
self.page = SAML2(site, self)
self.admin = Saml2AdminPage(site, self)
+ self.rest = Saml2RestBase(site, self)
return self.page
def init_idp(self):
self._debug('Failed to init SAML2 provider: %r' % e)
return None
+ self._root.logout.add_handler(self.name, self.idp_initiated_logout)
+
# Import all known applications
data = self.get_data()
for idval in data:
if self.admin:
self.admin.add_sps()
+ def idp_initiated_logout(self):
+ """
+ Logout all SP sessions when the logout comes from the IdP.
+
+ For the current user only.
+ """
+ self._debug("IdP-initiated SAML2 logout")
+ us = UserSession()
+
+ saml_sessions = us.get_provider_data('saml2')
+ if saml_sessions is None:
+ self._debug("No SAML2 sessions to logout")
+ return
+ session = saml_sessions.get_next_logout(remove=False)
+ if session is None:
+ return
+
+ # Add a fake session to indicate where the user should
+ # be redirected to when all SP's are logged out.
+ idpurl = self._root.instance_base_url()
+ saml_sessions.add_session("_idp_initiated_logout",
+ idpurl,
+ "")
+ init_session = saml_sessions.find_session_by_provider(idpurl)
+ init_session.set_logoutstate(idpurl, "idp_initiated_logout", None)
+ saml_sessions.start_logout(init_session)
+
+ logout = self.idp.get_logout_handler()
+ logout.setSessionFromDump(session.session.dump())
+ logout.initRequest(session.provider_id)
+ try:
+ logout.buildRequestMsg()
+ except lasso.Error, e:
+ self.error('failure to build logout request msg: %s' % e)
+ raise cherrypy.HTTPRedirect(400, 'Failed to log out user: %s '
+ % e)
+
+ raise cherrypy.HTTPRedirect(logout.msgUrl)
+
class IdpMetadataGenerator(object):
'%s/saml2/SSO/Redirect' % url)
self.meta.add_service(metadata.SAML2_SERVICE_MAP['logout-redirect'],
'%s/saml2/SLO/Redirect' % url)
- self.meta.add_allowed_name_format(
- lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT)
self.meta.add_allowed_name_format(
lasso.SAML2_NAME_IDENTIFIER_FORMAT_PERSISTENT)
+ self.meta.add_allowed_name_format(
+ lasso.SAML2_NAME_IDENTIFIER_FORMAT_TRANSIENT)
self.meta.add_allowed_name_format(
lasso.SAML2_NAME_IDENTIFIER_FORMAT_EMAIL)
return self.meta.output(path)
-class Installer(object):
+class Installer(ProviderInstaller):
def __init__(self, *pargs):
+ super(Installer, self).__init__()
self.name = 'saml2'
- self.ptype = 'provider'
self.pargs = pargs
def install_args(self, group):
config = {'idp storage path': path,
'idp metadata file': 'metadata.xml',
'idp certificate file': cert.cert,
- 'idp key file': cert.key}
+ 'idp key file': cert.key,
+ 'idp nameid salt': uuid.uuid4().hex}
po.save_plugin_config(config)
# Update global config to add login plugin