diff options
author | WuKong <rebirthmonkey@gmail.com> | 2015-06-30 18:47:29 +0200 |
---|---|---|
committer | WuKong <rebirthmonkey@gmail.com> | 2015-06-30 18:47:29 +0200 |
commit | b8c756ecdd7cced1db4300935484e8c83701c82e (patch) | |
tree | 87e51107d82b217ede145de9d9d59e2100725bd7 /keystone-moon/keystone/auth/plugins/mapped.py | |
parent | c304c773bae68fb854ed9eab8fb35c4ef17cf136 (diff) |
migrate moon code from github to opnfv
Change-Id: Ice53e368fd1114d56a75271aa9f2e598e3eba604
Signed-off-by: WuKong <rebirthmonkey@gmail.com>
Diffstat (limited to 'keystone-moon/keystone/auth/plugins/mapped.py')
-rw-r--r-- | keystone-moon/keystone/auth/plugins/mapped.py | 252 |
1 files changed, 252 insertions, 0 deletions
diff --git a/keystone-moon/keystone/auth/plugins/mapped.py b/keystone-moon/keystone/auth/plugins/mapped.py new file mode 100644 index 00000000..abf44481 --- /dev/null +++ b/keystone-moon/keystone/auth/plugins/mapped.py @@ -0,0 +1,252 @@ +# 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 functools + +from oslo_log import log +from oslo_serialization import jsonutils +from pycadf import cadftaxonomy as taxonomy +from six.moves.urllib import parse + +from keystone import auth +from keystone.auth import plugins as auth_plugins +from keystone.common import dependency +from keystone.contrib import federation +from keystone.contrib.federation import utils +from keystone import exception +from keystone.i18n import _ +from keystone.models import token_model +from keystone import notifications + + +LOG = log.getLogger(__name__) + +METHOD_NAME = 'mapped' + + +@dependency.requires('assignment_api', 'federation_api', 'identity_api', + 'token_provider_api') +class Mapped(auth.AuthMethodHandler): + + def _get_token_ref(self, auth_payload): + token_id = auth_payload['id'] + response = self.token_provider_api.validate_token(token_id) + return token_model.KeystoneToken(token_id=token_id, + token_data=response) + + def authenticate(self, context, auth_payload, auth_context): + """Authenticate mapped user and return an authentication context. + + :param context: keystone's request context + :param auth_payload: the content of the authentication for a + given method + :param auth_context: user authentication context, a dictionary + shared by all plugins. + + In addition to ``user_id`` in ``auth_context``, this plugin sets + ``group_ids``, ``OS-FEDERATION:identity_provider`` and + ``OS-FEDERATION:protocol`` + + """ + + if 'id' in auth_payload: + token_ref = self._get_token_ref(auth_payload) + handle_scoped_token(context, auth_payload, auth_context, token_ref, + self.federation_api, + self.identity_api, + self.token_provider_api) + else: + handle_unscoped_token(context, auth_payload, auth_context, + self.assignment_api, self.federation_api, + self.identity_api) + + +def handle_scoped_token(context, auth_payload, auth_context, token_ref, + federation_api, identity_api, token_provider_api): + utils.validate_expiration(token_ref) + token_audit_id = token_ref.audit_id + identity_provider = token_ref.federation_idp_id + protocol = token_ref.federation_protocol_id + user_id = token_ref.user_id + group_ids = token_ref.federation_group_ids + send_notification = functools.partial( + notifications.send_saml_audit_notification, 'authenticate', + context, user_id, group_ids, identity_provider, protocol, + token_audit_id) + + utils.assert_enabled_identity_provider(federation_api, identity_provider) + + try: + mapping = federation_api.get_mapping_from_idp_and_protocol( + identity_provider, protocol) + utils.validate_groups(group_ids, mapping['id'], identity_api) + + except Exception: + # NOTE(topol): Diaper defense to catch any exception, so we can + # send off failed authentication notification, raise the exception + # after sending the notification + send_notification(taxonomy.OUTCOME_FAILURE) + raise + else: + send_notification(taxonomy.OUTCOME_SUCCESS) + + auth_context['user_id'] = user_id + auth_context['group_ids'] = group_ids + auth_context[federation.IDENTITY_PROVIDER] = identity_provider + auth_context[federation.PROTOCOL] = protocol + + +def handle_unscoped_token(context, auth_payload, auth_context, + assignment_api, federation_api, identity_api): + + def is_ephemeral_user(mapped_properties): + return mapped_properties['user']['type'] == utils.UserType.EPHEMERAL + + def build_ephemeral_user_context(auth_context, user, mapped_properties, + identity_provider, protocol): + auth_context['user_id'] = user['id'] + auth_context['group_ids'] = mapped_properties['group_ids'] + auth_context[federation.IDENTITY_PROVIDER] = identity_provider + auth_context[federation.PROTOCOL] = protocol + + def build_local_user_context(auth_context, mapped_properties): + user_info = auth_plugins.UserAuthInfo.create(mapped_properties, + METHOD_NAME) + auth_context['user_id'] = user_info.user_id + + assertion = extract_assertion_data(context) + identity_provider = auth_payload['identity_provider'] + protocol = auth_payload['protocol'] + + utils.assert_enabled_identity_provider(federation_api, identity_provider) + + group_ids = None + # NOTE(topol): The user is coming in from an IdP with a SAML assertion + # instead of from a token, so we set token_id to None + token_id = None + # NOTE(marek-denis): This variable is set to None and there is a + # possibility that it will be used in the CADF notification. This means + # operation will not be mapped to any user (even ephemeral). + user_id = None + + try: + mapped_properties = apply_mapping_filter( + identity_provider, protocol, assertion, assignment_api, + federation_api, identity_api) + + if is_ephemeral_user(mapped_properties): + user = setup_username(context, mapped_properties) + user_id = user['id'] + group_ids = mapped_properties['group_ids'] + mapping = federation_api.get_mapping_from_idp_and_protocol( + identity_provider, protocol) + utils.validate_groups_cardinality(group_ids, mapping['id']) + build_ephemeral_user_context(auth_context, user, + mapped_properties, + identity_provider, protocol) + else: + build_local_user_context(auth_context, mapped_properties) + + except Exception: + # NOTE(topol): Diaper defense to catch any exception, so we can + # send off failed authentication notification, raise the exception + # after sending the notification + outcome = taxonomy.OUTCOME_FAILURE + notifications.send_saml_audit_notification('authenticate', context, + user_id, group_ids, + identity_provider, + protocol, token_id, + outcome) + raise + else: + outcome = taxonomy.OUTCOME_SUCCESS + notifications.send_saml_audit_notification('authenticate', context, + user_id, group_ids, + identity_provider, + protocol, token_id, + outcome) + + +def extract_assertion_data(context): + assertion = dict(utils.get_assertion_params_from_env(context)) + return assertion + + +def apply_mapping_filter(identity_provider, protocol, assertion, + assignment_api, federation_api, identity_api): + idp = federation_api.get_idp(identity_provider) + utils.validate_idp(idp, assertion) + mapping = federation_api.get_mapping_from_idp_and_protocol( + identity_provider, protocol) + rules = jsonutils.loads(mapping['rules']) + LOG.debug('using the following rules: %s', rules) + rule_processor = utils.RuleProcessor(rules) + mapped_properties = rule_processor.process(assertion) + + # NOTE(marek-denis): We update group_ids only here to avoid fetching + # groups identified by name/domain twice. + # NOTE(marek-denis): Groups are translated from name/domain to their + # corresponding ids in the auth plugin, as we need information what + # ``mapping_id`` was used as well as idenity_api and assignment_api + # objects. + group_ids = mapped_properties['group_ids'] + utils.validate_groups_in_backend(group_ids, + mapping['id'], + identity_api) + group_ids.extend( + utils.transform_to_group_ids( + mapped_properties['group_names'], mapping['id'], + identity_api, assignment_api)) + mapped_properties['group_ids'] = list(set(group_ids)) + return mapped_properties + + +def setup_username(context, mapped_properties): + """Setup federated username. + + Function covers all the cases for properly setting user id, a primary + identifier for identity objects. Initial version of the mapping engine + assumed user is identified by ``name`` and his ``id`` is built from the + name. We, however need to be able to accept local rules that identify user + by either id or name/domain. + + The following use-cases are covered: + + 1) If neither user_name nor user_id is set raise exception.Unauthorized + 2) If user_id is set and user_name not, set user_name equal to user_id + 3) If user_id is not set and user_name is, set user_id as url safe version + of user_name. + + :param context: authentication context + :param mapped_properties: Properties issued by a RuleProcessor. + :type: dictionary + + :raises: exception.Unauthorized + :returns: dictionary with user identification + :rtype: dict + + """ + user = mapped_properties['user'] + + user_id = user.get('id') + user_name = user.get('name') or context['environment'].get('REMOTE_USER') + + if not any([user_id, user_name]): + raise exception.Unauthorized(_("Could not map user")) + + elif not user_name: + user['name'] = user_id + + elif not user_id: + user['id'] = parse.quote(user_name) + + return user |