From 920a49cfa055733d575282973e23558c33087a4a Mon Sep 17 00:00:00 2001 From: RHE Date: Fri, 24 Nov 2017 13:54:26 +0100 Subject: remove keystone-moon Change-Id: I80d7c9b669f19d5f6607e162de8e0e55c2f80fdd Signed-off-by: RHE --- keystone-moon/keystone/federation/idp.py | 615 ------------------------------- 1 file changed, 615 deletions(-) delete mode 100644 keystone-moon/keystone/federation/idp.py (limited to 'keystone-moon/keystone/federation/idp.py') diff --git a/keystone-moon/keystone/federation/idp.py b/keystone-moon/keystone/federation/idp.py deleted file mode 100644 index 494d58b9..00000000 --- a/keystone-moon/keystone/federation/idp.py +++ /dev/null @@ -1,615 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import datetime -import os -import uuid - -from oslo_config import cfg -from oslo_log import log -from oslo_utils import fileutils -from oslo_utils import importutils -from oslo_utils import timeutils -import saml2 -from saml2 import client_base -from saml2 import md -from saml2.profile import ecp -from saml2 import saml -from saml2 import samlp -from saml2.schema import soapenv -from saml2 import sigver -xmldsig = importutils.try_import("saml2.xmldsig") -if not xmldsig: - xmldsig = importutils.try_import("xmldsig") - -from keystone.common import environment -from keystone.common import utils -from keystone import exception -from keystone.i18n import _, _LE - - -LOG = log.getLogger(__name__) -CONF = cfg.CONF - - -class SAMLGenerator(object): - """A class to generate SAML assertions.""" - - def __init__(self): - self.assertion_id = uuid.uuid4().hex - - def samlize_token(self, issuer, recipient, user, user_domain_name, roles, - project, project_domain_name, expires_in=None): - """Convert Keystone attributes to a SAML assertion. - - :param issuer: URL of the issuing party - :type issuer: string - :param recipient: URL of the recipient - :type recipient: string - :param user: User name - :type user: string - :param user_domain_name: User Domain name - :type user_domain_name: string - :param roles: List of role names - :type roles: list - :param project: Project name - :type project: string - :param project_domain_name: Project Domain name - :type project_domain_name: string - :param expires_in: Sets how long the assertion is valid for, in seconds - :type expires_in: int - - :returns: XML object - - """ - expiration_time = self._determine_expiration_time(expires_in) - status = self._create_status() - saml_issuer = self._create_issuer(issuer) - subject = self._create_subject(user, expiration_time, recipient) - attribute_statement = self._create_attribute_statement( - user, user_domain_name, roles, project, project_domain_name) - authn_statement = self._create_authn_statement(issuer, expiration_time) - signature = self._create_signature() - - assertion = self._create_assertion(saml_issuer, signature, - subject, authn_statement, - attribute_statement) - - assertion = _sign_assertion(assertion) - - response = self._create_response(saml_issuer, status, assertion, - recipient) - return response - - def _determine_expiration_time(self, expires_in): - if expires_in is None: - expires_in = CONF.saml.assertion_expiration_time - now = timeutils.utcnow() - future = now + datetime.timedelta(seconds=expires_in) - return utils.isotime(future, subsecond=True) - - def _create_status(self): - """Create an object that represents a SAML Status. - - - - - - :returns: XML object - - """ - status = samlp.Status() - status_code = samlp.StatusCode() - status_code.value = samlp.STATUS_SUCCESS - status_code.set_text('') - status.status_code = status_code - return status - - def _create_issuer(self, issuer_url): - """Create an object that represents a SAML Issuer. - - - https://acme.com/FIM/sps/openstack/saml20 - - :returns: XML object - - """ - issuer = saml.Issuer() - issuer.format = saml.NAMEID_FORMAT_ENTITY - issuer.set_text(issuer_url) - return issuer - - def _create_subject(self, user, expiration_time, recipient): - """Create an object that represents a SAML Subject. - - - - john@smith.com - - - - - - :returns: XML object - - """ - name_id = saml.NameID() - name_id.set_text(user) - subject_conf_data = saml.SubjectConfirmationData() - subject_conf_data.recipient = recipient - subject_conf_data.not_on_or_after = expiration_time - subject_conf = saml.SubjectConfirmation() - subject_conf.method = saml.SCM_BEARER - subject_conf.subject_confirmation_data = subject_conf_data - subject = saml.Subject() - subject.subject_confirmation = subject_conf - subject.name_id = name_id - return subject - - def _create_attribute_statement(self, user, user_domain_name, roles, - project, project_domain_name): - """Create an object that represents a SAML AttributeStatement. - - - - test_user - - - Default - - - admin - member - - - development - - - Default - - - - :returns: XML object - - """ - def _build_attribute(attribute_name, attribute_values): - attribute = saml.Attribute() - attribute.name = attribute_name - - for value in attribute_values: - attribute_value = saml.AttributeValue() - attribute_value.set_text(value) - attribute.attribute_value.append(attribute_value) - - return attribute - - user_attribute = _build_attribute('openstack_user', [user]) - roles_attribute = _build_attribute('openstack_roles', roles) - project_attribute = _build_attribute('openstack_project', [project]) - project_domain_attribute = _build_attribute( - 'openstack_project_domain', [project_domain_name]) - user_domain_attribute = _build_attribute( - 'openstack_user_domain', [user_domain_name]) - - attribute_statement = saml.AttributeStatement() - attribute_statement.attribute.append(user_attribute) - attribute_statement.attribute.append(roles_attribute) - attribute_statement.attribute.append(project_attribute) - attribute_statement.attribute.append(project_domain_attribute) - attribute_statement.attribute.append(user_domain_attribute) - return attribute_statement - - def _create_authn_statement(self, issuer, expiration_time): - """Create an object that represents a SAML AuthnStatement. - - - - - urn:oasis:names:tc:SAML:2.0:ac:classes:Password - - - https://acme.com/FIM/sps/openstack/saml20 - - - - - :returns: XML object - - """ - authn_statement = saml.AuthnStatement() - authn_statement.authn_instant = utils.isotime() - authn_statement.session_index = uuid.uuid4().hex - authn_statement.session_not_on_or_after = expiration_time - - authn_context = saml.AuthnContext() - authn_context_class = saml.AuthnContextClassRef() - authn_context_class.set_text(saml.AUTHN_PASSWORD) - - authn_authority = saml.AuthenticatingAuthority() - authn_authority.set_text(issuer) - authn_context.authn_context_class_ref = authn_context_class - authn_context.authenticating_authority = authn_authority - - authn_statement.authn_context = authn_context - - return authn_statement - - def _create_assertion(self, issuer, signature, subject, authn_statement, - attribute_statement): - """Create an object that represents a SAML Assertion. - - - ... - ... - ... - ... - ... - - - :returns: XML object - - """ - assertion = saml.Assertion() - assertion.id = self.assertion_id - assertion.issue_instant = utils.isotime() - assertion.version = '2.0' - assertion.issuer = issuer - assertion.signature = signature - assertion.subject = subject - assertion.authn_statement = authn_statement - assertion.attribute_statement = attribute_statement - return assertion - - def _create_response(self, issuer, status, assertion, recipient): - """Create an object that represents a SAML Response. - - - ... - ... - ... - - - :returns: XML object - - """ - response = samlp.Response() - response.id = uuid.uuid4().hex - response.destination = recipient - response.issue_instant = utils.isotime() - response.version = '2.0' - response.issuer = issuer - response.status = status - response.assertion = assertion - return response - - def _create_signature(self): - """Create an object that represents a SAML . - - This must be filled with algorithms that the signing binary will apply - in order to sign the whole message. - Currently we enforce X509 signing. - Example of the template:: - - - - - - - - - - - - - - - - - - - - - :returns: XML object - - """ - canonicalization_method = xmldsig.CanonicalizationMethod() - canonicalization_method.algorithm = xmldsig.ALG_EXC_C14N - signature_method = xmldsig.SignatureMethod( - algorithm=xmldsig.SIG_RSA_SHA1) - - transforms = xmldsig.Transforms() - envelope_transform = xmldsig.Transform( - algorithm=xmldsig.TRANSFORM_ENVELOPED) - - c14_transform = xmldsig.Transform(algorithm=xmldsig.ALG_EXC_C14N) - transforms.transform = [envelope_transform, c14_transform] - - digest_method = xmldsig.DigestMethod(algorithm=xmldsig.DIGEST_SHA1) - digest_value = xmldsig.DigestValue() - - reference = xmldsig.Reference() - reference.uri = '#' + self.assertion_id - reference.digest_method = digest_method - reference.digest_value = digest_value - reference.transforms = transforms - - signed_info = xmldsig.SignedInfo() - signed_info.canonicalization_method = canonicalization_method - signed_info.signature_method = signature_method - signed_info.reference = reference - - key_info = xmldsig.KeyInfo() - key_info.x509_data = xmldsig.X509Data() - - signature = xmldsig.Signature() - signature.signed_info = signed_info - signature.signature_value = xmldsig.SignatureValue() - signature.key_info = key_info - - return signature - - -def _sign_assertion(assertion): - """Sign a SAML assertion. - - This method utilizes ``xmlsec1`` binary and signs SAML assertions in a - separate process. ``xmlsec1`` cannot read input data from stdin so the - prepared assertion needs to be serialized and stored in a temporary - file. This file will be deleted immediately after ``xmlsec1`` returns. - The signed assertion is redirected to a standard output and read using - subprocess.PIPE redirection. A ``saml.Assertion`` class is created - from the signed string again and returned. - - Parameters that are required in the CONF:: - * xmlsec_binary - * private key file path - * public key file path - :returns: XML object - - """ - xmlsec_binary = CONF.saml.xmlsec1_binary - idp_private_key = CONF.saml.keyfile - idp_public_key = CONF.saml.certfile - - # xmlsec1 --sign --privkey-pem privkey,cert --id-attr:ID - certificates = '%(idp_private_key)s,%(idp_public_key)s' % { - 'idp_public_key': idp_public_key, - 'idp_private_key': idp_private_key - } - - command_list = [xmlsec_binary, '--sign', '--privkey-pem', certificates, - '--id-attr:ID', 'Assertion'] - - file_path = None - try: - # NOTE(gyee): need to make the namespace prefixes explicit so - # they won't get reassigned when we wrap the assertion into - # SAML2 response - file_path = fileutils.write_to_tempfile(assertion.to_string( - nspair={'saml': saml2.NAMESPACE, - 'xmldsig': xmldsig.NAMESPACE})) - command_list.append(file_path) - subprocess = environment.subprocess - stdout = subprocess.check_output(command_list, # nosec : The contents - # of the command list are coming from - # a trusted source because the - # executable and arguments all either - # come from the config file or are - # hardcoded. The command list is - # initialized earlier in this function - # to a list and it's still a list at - # this point in the function. There is - # no opportunity for an attacker to - # attempt command injection via string - # parsing. - stderr=subprocess.STDOUT) - except Exception as e: - msg = _LE('Error when signing assertion, reason: %(reason)s%(output)s') - LOG.error(msg, - {'reason': e, - 'output': ' ' + e.output if hasattr(e, 'output') else ''}) - raise exception.SAMLSigningError(reason=e) - finally: - try: - if file_path: - os.remove(file_path) - except OSError: # nosec - # The file is already gone, good. - pass - - return saml2.create_class_from_xml_string(saml.Assertion, stdout) - - -class MetadataGenerator(object): - """A class for generating SAML IdP Metadata.""" - - def generate_metadata(self): - """Generate Identity Provider Metadata. - - Generate and format metadata into XML that can be exposed and - consumed by a federated Service Provider. - - :returns: XML object. - :raises keystone.exception.ValidationError: If the required - config options aren't set. - """ - self._ensure_required_values_present() - entity_descriptor = self._create_entity_descriptor() - entity_descriptor.idpsso_descriptor = ( - self._create_idp_sso_descriptor()) - return entity_descriptor - - def _create_entity_descriptor(self): - ed = md.EntityDescriptor() - ed.entity_id = CONF.saml.idp_entity_id - return ed - - def _create_idp_sso_descriptor(self): - - def get_cert(): - try: - return sigver.read_cert_from_file(CONF.saml.certfile, 'pem') - except (IOError, sigver.CertificateError) as e: - msg = _('Cannot open certificate %(cert_file)s. ' - 'Reason: %(reason)s') - msg = msg % {'cert_file': CONF.saml.certfile, 'reason': e} - LOG.error(msg) - raise IOError(msg) - - def key_descriptor(): - cert = get_cert() - return md.KeyDescriptor( - key_info=xmldsig.KeyInfo( - x509_data=xmldsig.X509Data( - x509_certificate=xmldsig.X509Certificate(text=cert) - ) - ), use='signing' - ) - - def single_sign_on_service(): - idp_sso_endpoint = CONF.saml.idp_sso_endpoint - return md.SingleSignOnService( - binding=saml2.BINDING_URI, - location=idp_sso_endpoint) - - def organization(): - name = md.OrganizationName(lang=CONF.saml.idp_lang, - text=CONF.saml.idp_organization_name) - display_name = md.OrganizationDisplayName( - lang=CONF.saml.idp_lang, - text=CONF.saml.idp_organization_display_name) - url = md.OrganizationURL(lang=CONF.saml.idp_lang, - text=CONF.saml.idp_organization_url) - - return md.Organization( - organization_display_name=display_name, - organization_url=url, organization_name=name) - - def contact_person(): - company = md.Company(text=CONF.saml.idp_contact_company) - given_name = md.GivenName(text=CONF.saml.idp_contact_name) - surname = md.SurName(text=CONF.saml.idp_contact_surname) - email = md.EmailAddress(text=CONF.saml.idp_contact_email) - telephone = md.TelephoneNumber( - text=CONF.saml.idp_contact_telephone) - contact_type = CONF.saml.idp_contact_type - - return md.ContactPerson( - company=company, given_name=given_name, sur_name=surname, - email_address=email, telephone_number=telephone, - contact_type=contact_type) - - def name_id_format(): - return md.NameIDFormat(text=saml.NAMEID_FORMAT_TRANSIENT) - - idpsso = md.IDPSSODescriptor() - idpsso.protocol_support_enumeration = samlp.NAMESPACE - idpsso.key_descriptor = key_descriptor() - idpsso.single_sign_on_service = single_sign_on_service() - idpsso.name_id_format = name_id_format() - if self._check_organization_values(): - idpsso.organization = organization() - if self._check_contact_person_values(): - idpsso.contact_person = contact_person() - return idpsso - - def _ensure_required_values_present(self): - """Ensure idp_sso_endpoint and idp_entity_id have values.""" - if CONF.saml.idp_entity_id is None: - msg = _('Ensure configuration option idp_entity_id is set.') - raise exception.ValidationError(msg) - if CONF.saml.idp_sso_endpoint is None: - msg = _('Ensure configuration option idp_sso_endpoint is set.') - raise exception.ValidationError(msg) - - def _check_contact_person_values(self): - """Determine if contact information is included in metadata.""" - # Check if we should include contact information - params = [CONF.saml.idp_contact_company, - CONF.saml.idp_contact_name, - CONF.saml.idp_contact_surname, - CONF.saml.idp_contact_email, - CONF.saml.idp_contact_telephone] - for value in params: - if value is None: - return False - - # Check if contact type is an invalid value - valid_type_values = ['technical', 'other', 'support', 'administrative', - 'billing'] - if CONF.saml.idp_contact_type not in valid_type_values: - msg = _('idp_contact_type must be one of: [technical, other, ' - 'support, administrative or billing.') - raise exception.ValidationError(msg) - return True - - def _check_organization_values(self): - """Determine if organization information is included in metadata.""" - params = [CONF.saml.idp_organization_name, - CONF.saml.idp_organization_display_name, - CONF.saml.idp_organization_url] - for value in params: - if value is None: - return False - return True - - -class ECPGenerator(object): - """A class for generating an ECP assertion.""" - - @staticmethod - def generate_ecp(saml_assertion, relay_state_prefix): - ecp_generator = ECPGenerator() - header = ecp_generator._create_header(relay_state_prefix) - body = ecp_generator._create_body(saml_assertion) - envelope = soapenv.Envelope(header=header, body=body) - return envelope - - def _create_header(self, relay_state_prefix): - relay_state_text = relay_state_prefix + uuid.uuid4().hex - relay_state = ecp.RelayState(actor=client_base.ACTOR, - must_understand='1', - text=relay_state_text) - header = soapenv.Header() - header.extension_elements = ( - [saml2.element_to_extension_element(relay_state)]) - return header - - def _create_body(self, saml_assertion): - body = soapenv.Body() - body.extension_elements = ( - [saml2.element_to_extension_element(saml_assertion)]) - return body -- cgit 1.2.3-korg