Exemplo n.º 1
0
    def get_mapped_user(self, project_id=None, domain_id=None):
        """Map client certificate to an existing user.

        If user is ephemeral, there is no validation on the user himself;
        however it will be mapped to a corresponding group(s) and the scope
        of this ephemeral user is the same as what is assigned to the group.

        :param project_id:  Project scope of the mapped user.
        :param domain_id: Domain scope of the mapped user.
        :returns: A dictionary that contains the keys, such as
            user_id, user_name, domain_id, domain_name
        :rtype: dict
        """
        idp_id = self._build_idp_id()
        LOG.debug(
            'The IdP Id %s and protocol Id %s are used to look up '
            'the mapping.', idp_id, CONF.tokenless_auth.protocol)

        mapped_properties, mapping_id = self.federation_api.evaluate(
            idp_id, CONF.tokenless_auth.protocol, self.env)

        user = mapped_properties.get('user', {})
        user_id = user.get('id')
        user_name = user.get('name')
        user_type = user.get('type')
        if user.get('domain') is not None:
            user_domain_id = user.get('domain').get('id')
            user_domain_name = user.get('domain').get('name')
        else:
            user_domain_id = None
            user_domain_name = None

        # if user is ephemeral type, we don't care if the user exists
        # or not, but just care if the mapped group(s) is valid.
        if user_type == utils.UserType.EPHEMERAL:
            user_ref = {'type': utils.UserType.EPHEMERAL}
            group_ids = mapped_properties['group_ids']
            utils.validate_mapped_group_ids(group_ids, mapping_id,
                                            self.identity_api)
            group_ids.extend(
                utils.transform_to_group_ids(mapped_properties['group_names'],
                                             mapping_id, self.identity_api,
                                             self.resource_api))
            roles = self.assignment_api.get_roles_for_groups(
                group_ids, project_id, domain_id)
            if roles is not None:
                role_names = [role['name'] for role in roles]
                user_ref['roles'] = role_names
            user_ref['group_ids'] = list(group_ids)
            user_ref[federation_constants.IDENTITY_PROVIDER] = idp_id
            user_ref[federation_constants.PROTOCOL] = (
                CONF.tokenless_auth.protocol)
            return user_ref

        if user_id:
            user_ref = self.identity_api.get_user(user_id)
        elif user_name and (user_domain_name or user_domain_id):
            if user_domain_name:
                user_domain = self.resource_api.get_domain_by_name(
                    user_domain_name)
                self.resource_api.assert_domain_enabled(
                    user_domain['id'], user_domain)
                user_domain_id = user_domain['id']
            user_ref = self.identity_api.get_user_by_name(
                user_name, user_domain_id)
        else:
            msg = _('User auth cannot be built due to missing either '
                    'user id, or user name with domain id, or user name '
                    'with domain name.')
            raise exception.ValidationError(msg)
        self.identity_api.assert_user_enabled(user_id=user_ref['id'],
                                              user=user_ref)
        user_ref['type'] = utils.UserType.LOCAL
        return user_ref
Exemplo n.º 2
0
    def _authenticate_token(self, request, auth):
        """Try to authenticate using an already existing token.

        Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
        """
        if 'token' not in auth:
            raise exception.ValidationError(attribute='token', target='auth')

        if 'id' not in auth['token']:
            raise exception.ValidationError(attribute='id', target='token')

        old_token = auth['token']['id']
        if len(old_token) > CONF.max_token_size:
            raise exception.ValidationSizeError(attribute='token',
                                                size=CONF.max_token_size)

        try:
            token_model_ref = token_model.KeystoneToken(
                token_id=old_token,
                token_data=self.token_provider_api.validate_v2_token(
                    old_token))
        except exception.NotFound as e:
            raise exception.Unauthorized(e)

        wsgi.validate_token_bind(request.context_dict, token_model_ref)

        self._restrict_scope(token_model_ref)
        user_id = token_model_ref.user_id
        tenant_id = self._get_project_id_from_auth(auth)

        if not CONF.trust.enabled and 'trust_id' in auth:
            raise exception.Forbidden('Trusts are disabled.')
        elif CONF.trust.enabled and 'trust_id' in auth:
            try:
                trust_ref = self.trust_api.get_trust(auth['trust_id'])
            except exception.TrustNotFound:
                raise exception.Forbidden()
            if user_id != trust_ref['trustee_user_id']:
                raise exception.Forbidden()
            if (trust_ref['project_id']
                    and tenant_id != trust_ref['project_id']):
                raise exception.Forbidden()
            if ('expires' in trust_ref) and (trust_ref['expires']):
                expiry = trust_ref['expires']
                if expiry < timeutils.parse_isotime(utils.isotime()):
                    raise exception.Forbidden()
            user_id = trust_ref['trustor_user_id']
            trustor_user_ref = self.identity_api.get_user(
                trust_ref['trustor_user_id'])
            if not trustor_user_ref['enabled']:
                raise exception.Forbidden()
            trustee_user_ref = self.identity_api.get_user(
                trust_ref['trustee_user_id'])
            if not trustee_user_ref['enabled']:
                raise exception.Forbidden()

            if trust_ref['impersonation'] is True:
                current_user_ref = trustor_user_ref
            else:
                current_user_ref = trustee_user_ref

        else:
            current_user_ref = self.identity_api.get_user(user_id)

        metadata_ref = {}
        tenant_ref, metadata_ref['roles'] = self._get_project_roles_and_ref(
            user_id, tenant_id)

        expiry = token_model_ref.expires
        if CONF.trust.enabled and 'trust_id' in auth:
            trust_id = auth['trust_id']
            trust_roles = []
            for role in trust_ref['roles']:
                if 'roles' not in metadata_ref:
                    raise exception.Forbidden()
                if role['id'] in metadata_ref['roles']:
                    trust_roles.append(role['id'])
                else:
                    raise exception.Forbidden()
            if 'expiry' in trust_ref and trust_ref['expiry']:
                trust_expiry = timeutils.parse_isotime(trust_ref['expiry'])
                if trust_expiry < expiry:
                    expiry = trust_expiry
            metadata_ref['roles'] = trust_roles
            metadata_ref['trustee_user_id'] = trust_ref['trustee_user_id']
            metadata_ref['trust_id'] = trust_id

        bind = token_model_ref.bind
        audit_id = token_model_ref.audit_chain_id

        return (current_user_ref, tenant_ref, metadata_ref, expiry, bind,
                audit_id)
Exemplo n.º 3
0
 def _validate_mapping_exists(self, mapping_id):
     try:
         self.driver.get_mapping(mapping_id)
     except exception.MappingNotFound:
         msg = _('Invalid mapping id: %s')
         raise exception.ValidationError(message=(msg % mapping_id))
Exemplo n.º 4
0
 def _assert_system_nand_domain(self):
     if (flask.request.args.get('scope.domain.id')
             and flask.request.args.get('scope.system')):
         msg = _('Specify system or domain, not both')
         raise exception.ValidationError(msg)
Exemplo n.º 5
0
 def _assert_user_nand_group(self):
     if (flask.request.args.get('user.id')
             and flask.request.args.get('group.id')):
         msg = _('Specify a user or group, not both')
         raise exception.ValidationError(msg)
Exemplo n.º 6
0
 def _require_attribute(self, ref, attr):
     """Ensures the reference contains the specified attribute."""
     if ref.get(attr) is None or ref.get(attr) == '':
         msg = '%s field is required and cannot be empty' % attr
         raise exception.ValidationError(message=msg)
Exemplo n.º 7
0
def check_type(property_name, value, expected_type, display_expected_type):
    if not isinstance(value, expected_type):
        msg = _("%(property_name)s is not a "
                "%(display_expected_type)s") % locals()
        raise exception.ValidationError(msg)
Exemplo n.º 8
0
    def create_access_token(self, context):
        headers = context['headers']
        oauth_headers = oauth1.get_oauth_headers(headers)
        consumer_id = oauth_headers.get('oauth_consumer_key')
        request_token_id = oauth_headers.get('oauth_token')
        oauth_verifier = oauth_headers.get('oauth_verifier')

        if not consumer_id:
            raise exception.ValidationError(
                attribute='oauth_consumer_key', target='request')
        if not request_token_id:
            raise exception.ValidationError(
                attribute='oauth_token', target='request')
        if not oauth_verifier:
            raise exception.ValidationError(
                attribute='oauth_verifier', target='request')

        req_token = self.oauth_api.get_request_token(
            request_token_id)

        expires_at = req_token['expires_at']
        if expires_at:
            now = timeutils.utcnow()
            expires = timeutils.normalize_time(
                timeutils.parse_isotime(expires_at))
            if now > expires:
                raise exception.Unauthorized(_('Request token is expired'))

        url = self.base_url(context, context['path'])

        access_verifier = oauth1.AccessTokenEndpoint(
            request_validator=validator.OAuthValidator(),
            token_generator=oauth1.token_generator)
        h, b, s = access_verifier.create_access_token_response(
            url,
            http_method='POST',
            body=context['query_string'],
            headers=headers)
        params = oauth1.extract_non_oauth_params(b)
        if len(params) != 0:
            msg = _('There should not be any non-oauth parameters')
            raise exception.Unauthorized(message=msg)

        if req_token['consumer_id'] != consumer_id:
            msg = _('provided consumer key does not match stored consumer key')
            raise exception.Unauthorized(message=msg)

        if req_token['verifier'] != oauth_verifier:
            msg = _('provided verifier does not match stored verifier')
            raise exception.Unauthorized(message=msg)

        if req_token['id'] != request_token_id:
            msg = _('provided request key does not match stored request key')
            raise exception.Unauthorized(message=msg)

        if not req_token.get('authorizing_user_id'):
            msg = _('Request Token does not have an authorizing user id')
            raise exception.Unauthorized(message=msg)

        access_token_duration = CONF.oauth1.access_token_duration
        token_ref = self.oauth_api.create_access_token(request_token_id,
                                                       access_token_duration)

        result = ('oauth_token=%(key)s&oauth_token_secret=%(secret)s'
                  % {'key': token_ref['id'],
                     'secret': token_ref['access_secret']})

        if CONF.oauth1.access_token_duration:
            expiry_bit = '&oauth_expires_at=%s' % (token_ref['expires_at'])
            result += expiry_bit

        headers = [('Content-Type', 'application/x-www-urlformencoded')]
        response = wsgi.render_response(result,
                                        status=(201, 'Created'),
                                        headers=headers)

        return response
Exemplo n.º 9
0
 def _validate_consumer_ref(self, consumer):
     if 'secret' in consumer:
         msg = _('Cannot change consumer secret')
         raise exception.ValidationError(message=msg)
    def _authenticate_token(self, context, auth):
        """Try to authenticate using an already existing token.

        Returns auth_token_data, (user_ref, tenant_ref, metadata_ref)
        """
        if 'token' not in auth:
            raise exception.ValidationError(
                attribute='token', target='auth')

        if "id" not in auth['token']:
            raise exception.ValidationError(
                attribute="id", target="token")

        old_token = auth['token']['id']
        if len(old_token) > CONF.max_token_size:
            raise exception.ValidationSizeError(attribute='token',
                                                size=CONF.max_token_size)

        try:
            old_token_ref = self.token_api.get_token(old_token)
        except exception.NotFound as e:
            raise exception.Unauthorized(e)

        wsgi.validate_token_bind(context, old_token_ref)

        #A trust token cannot be used to get another token
        if 'trust' in old_token_ref:
            raise exception.Forbidden()
        if 'trust_id' in old_token_ref['metadata']:
            raise exception.Forbidden()

        user_ref = old_token_ref['user']
        user_id = user_ref['id']
        if not CONF.trust.enabled and 'trust_id' in auth:
            raise exception.Forbidden('Trusts are disabled.')
        elif CONF.trust.enabled and 'trust_id' in auth:
            trust_ref = self.trust_api.get_trust(auth['trust_id'])
            if trust_ref is None:
                raise exception.Forbidden()
            if user_id != trust_ref['trustee_user_id']:
                raise exception.Forbidden()
            if ('expires' in trust_ref) and (trust_ref['expires']):
                expiry = trust_ref['expires']
                if expiry < timeutils.parse_isotime(timeutils.isotime()):
                    raise exception.Forbidden()()
            user_id = trust_ref['trustor_user_id']
            trustor_user_ref = self.identity_api.get_user(
                trust_ref['trustor_user_id'])
            if not trustor_user_ref['enabled']:
                raise exception.Forbidden()()
            trustee_user_ref = self.identity_api.get_user(
                trust_ref['trustee_user_id'])
            if not trustee_user_ref['enabled']:
                raise exception.Forbidden()()
            if trust_ref['impersonation'] is True:
                current_user_ref = trustor_user_ref
            else:
                current_user_ref = trustee_user_ref

        else:
            current_user_ref = self.identity_api.get_user(user_id)

        metadata_ref = {}
        tenant_id = self._get_project_id_from_auth(auth)
        tenant_ref, metadata_ref['roles'] = self._get_project_roles_and_ref(
            user_id, tenant_id)

        expiry = old_token_ref['expires']
        if CONF.trust.enabled and 'trust_id' in auth:
            trust_id = auth['trust_id']
            trust_roles = []
            for role in trust_ref['roles']:
                if 'roles' not in metadata_ref:
                    raise exception.Forbidden()()
                if role['id'] in metadata_ref['roles']:
                    trust_roles.append(role['id'])
                else:
                    raise exception.Forbidden()
            if 'expiry' in trust_ref and trust_ref['expiry']:
                trust_expiry = timeutils.parse_isotime(trust_ref['expiry'])
                if trust_expiry < expiry:
                    expiry = trust_expiry
            metadata_ref['roles'] = trust_roles
            metadata_ref['trustee_user_id'] = trust_ref['trustee_user_id']
            metadata_ref['trust_id'] = trust_id

        bind = old_token_ref.get('bind', None)

        return (current_user_ref, tenant_ref, metadata_ref, expiry, bind)
    def authenticate(self, context, auth=None):
        """Authenticate credentials and return a token.

        Accept auth as a dict that looks like::

            {
                "auth":{
                    "passwordCredentials":{
                        "username":"******",
                        "password":"******"
                    },
                    "tenantName":"customer-x"
                }
            }

        In this case, tenant is optional, if not provided the token will be
        considered "unscoped" and can later be used to get a scoped token.

        Alternatively, this call accepts auth with only a token and tenant
        that will return a token that is scoped to that tenant.
        """

        if auth is None:
            raise exception.ValidationError(attribute='auth',
                                            target='request body')

        auth_token_data = None

        if "token" in auth:
            # Try to authenticate using a token
            auth_info = self._authenticate_token(
                context, auth)
        else:
            # Try external authentication
            try:
                auth_info = self._authenticate_external(
                    context, auth)
            except ExternalAuthNotApplicable:
                # Try local authentication
                auth_info = self._authenticate_local(
                    context, auth)

        user_ref, tenant_ref, metadata_ref, expiry, bind = auth_info
        core.validate_auth_info(self, user_ref, tenant_ref)
        # NOTE(morganfainberg): Make sure the data is in correct form since it
        # might be consumed external to Keystone and this is a v2.0 controller.
        # The user_ref is encoded into the auth_token_data which is returned as
        # part of the token data. The token provider doesn't care about the
        # format.
        user_ref = self.v3_to_v2_user(user_ref)
        if tenant_ref:
            tenant_ref = self.filter_domain_id(tenant_ref)
        auth_token_data = self._get_auth_token_data(user_ref,
                                                    tenant_ref,
                                                    metadata_ref,
                                                    expiry)

        if tenant_ref:
            catalog_ref = self.catalog_api.get_catalog(
                user_ref['id'], tenant_ref['id'], metadata_ref)
        else:
            catalog_ref = {}

        auth_token_data['id'] = 'placeholder'
        if bind:
            auth_token_data['bind'] = bind

        roles_ref = []
        for role_id in metadata_ref.get('roles', []):
            role_ref = self.assignment_api.get_role(role_id)
            roles_ref.append(dict(name=role_ref['name']))

        (token_id, token_data) = self.token_provider_api.issue_v2_token(
            auth_token_data, roles_ref=roles_ref, catalog_ref=catalog_ref)
        return token_data
Exemplo n.º 12
0
 def _assert_user_nand_group(self, user_id, group_id):
     if user_id and group_id:
         msg = _('Specify a user or group, not both')
         raise exception.ValidationError(msg)
Exemplo n.º 13
0
 def _assert_domain_nand_project(self, domain_id, project_id):
     if domain_id and project_id:
         msg = _('Specify a domain or project, not both')
         raise exception.ValidationError(msg)
Exemplo n.º 14
0
    def _transform(self, identity_values):
        """Transform local mappings, to an easier to understand format.

        Transform the incoming array to generate the return value for
        the process function. Generating content for Keystone tokens will
        be easier if some pre-processing is done at this level.

        :param identity_values: local mapping from valid evaluations
        :type identity_values: array of dict

        Example identity_values::

            [
                {
                    'group': {'id': '0cd5e9'},
                    'user': {
                        'email': '*****@*****.**'
                    },
                },
                {
                    'groups': ['member', 'admin', tester'],
                    'domain': {
                        'name': 'default_domain'
                    }
                },
                {
                    'group_ids': ['abc123', 'def456', '0cd5e9']
                }
            ]

        :returns: dictionary with user name, group_ids and group_names.
        :rtype: dict

        """
        def extract_groups(groups_by_domain):
            for groups in list(groups_by_domain.values()):
                for group in list({g['name']: g for g in groups}.values()):
                    yield group

        def normalize_user(user):
            """Parse and validate user mapping."""
            user_type = user.get('type')

            if user_type and user_type not in (UserType.EPHEMERAL,
                                               UserType.LOCAL):
                msg = _("User type %s not supported") % user_type
                raise exception.ValidationError(msg)

            if user_type is None:
                user['type'] = UserType.EPHEMERAL

        # initialize the group_ids as a set to eliminate duplicates
        user = {}
        group_ids = set()
        group_names = list()
        groups_by_domain = dict()
        projects = []

        # if mapping yield no valid identity values, we should bail right away
        # instead of continuing on with a normalized bogus user
        if not identity_values:
            msg = ("Could not map any federated user properties to identity "
                   "values. Check debug logs or the mapping used for "
                   "additional details.")
            tr_msg = _("Could not map any federated user properties to "
                       "identity values. Check debug logs or the mapping "
                       "used for additional details.")
            LOG.warning(msg)
            raise exception.ValidationError(tr_msg)

        for identity_value in identity_values:
            if 'user' in identity_value:
                # if a mapping outputs more than one user name, log it
                if user:
                    LOG.warning('Ignoring user name')
                else:
                    user = identity_value.get('user')
            if 'group' in identity_value:
                group = identity_value['group']
                if 'id' in group:
                    group_ids.add(group['id'])
                elif 'name' in group:
                    domain = (group['domain'].get('name')
                              or group['domain'].get('id'))
                    groups_by_domain.setdefault(domain, list()).append(group)
                group_names.extend(extract_groups(groups_by_domain))
            if 'groups' in identity_value:
                if 'domain' not in identity_value:
                    msg = _("Invalid rule: %(identity_value)s. Both 'groups' "
                            "and 'domain' keywords must be specified.")
                    msg = msg % {'identity_value': identity_value}
                    raise exception.ValidationError(msg)
                # In this case, identity_value['groups'] is a string
                # representation of a list, and we want a real list.  This is
                # due to the way we do direct mapping substitutions today (see
                # function _update_local_mapping() )
                try:
                    group_names_list = ast.literal_eval(
                        identity_value['groups'])
                except (ValueError, SyntaxError):
                    group_names_list = [identity_value['groups']]
                domain = identity_value['domain']
                group_dicts = [{
                    'name': name,
                    'domain': domain
                } for name in group_names_list]

                group_names.extend(group_dicts)
            if 'group_ids' in identity_value:
                # If identity_values['group_ids'] is a string representation
                # of a list, parse it to a real list. Also, if the provided
                # group_ids parameter contains only one element, it will be
                # parsed as a simple string, and not a list or the
                # representation of a list.
                try:
                    group_ids.update(
                        ast.literal_eval(identity_value['group_ids']))
                except (ValueError, SyntaxError):
                    group_ids.update([identity_value['group_ids']])
            if 'projects' in identity_value:
                projects = identity_value['projects']

        normalize_user(user)

        return {
            'user': user,
            'group_ids': list(group_ids),
            'group_names': group_names,
            'projects': projects
        }
Exemplo n.º 15
0
    def __call__(self, req):
        arg_dict = req.environ['wsgiorg.routing_args'][1]
        action = arg_dict.pop('action')
        del arg_dict['controller']

        # allow middleware up the stack to provide context, params and headers.
        context = req.environ.get(CONTEXT_ENV, {})

        try:
            context['query_string'] = dict(req.params.items())
        except UnicodeDecodeError as e:
            # The webob package throws UnicodeError when a request cannot be
            # decoded. Raise ValidationError instead to avoid an UnknownError.
            msg = _('Query string is not UTF-8 encoded')
            raise exception.ValidationError(msg)

        context['headers'] = dict(req.headers.items())
        context['path'] = req.environ['PATH_INFO']
        scheme = (None if not CONF.secure_proxy_ssl_header else
                  req.environ.get(CONF.secure_proxy_ssl_header))
        if scheme:
            # NOTE(andrey-mp): "wsgi.url_scheme" contains the protocol used
            # before the proxy removed it ('https' usually). So if
            # the webob.Request instance is modified in order to use this
            # scheme instead of the one defined by API, the call to
            # webob.Request.relative_url() will return a URL with the correct
            # scheme.
            req.environ['wsgi.url_scheme'] = scheme
        context['host_url'] = req.host_url
        params = req.environ.get(PARAMS_ENV, {})
        # authentication and authorization attributes are set as environment
        # values by the container and processed by the pipeline. The complete
        # set is not yet known.
        context['environment'] = req.environ
        context['accept_header'] = req.accept
        req.environ = None

        params.update(arg_dict)

        context.setdefault('is_admin', False)

        # TODO(termie): do some basic normalization on methods
        method = getattr(self, action)

        # NOTE(morganfainberg): use the request method to normalize the
        # response code between GET and HEAD requests. The HTTP status should
        # be the same.
        LOG.info(
            '%(req_method)s %(uri)s', {
                'req_method': req.environ['REQUEST_METHOD'].upper(),
                'uri': wsgiref.util.request_uri(req.environ),
            })

        params = self._normalize_dict(params)

        try:
            result = method(context, **params)
        except exception.Unauthorized as e:
            LOG.warning(
                _LW("Authorization failed. %(exception)s from "
                    "%(remote_addr)s"), {
                        'exception': e,
                        'remote_addr': req.environ['REMOTE_ADDR']
                    })
            return render_exception(e,
                                    context=context,
                                    user_locale=best_match_language(req))
        except exception.Error as e:
            LOG.warning(six.text_type(e))
            return render_exception(e,
                                    context=context,
                                    user_locale=best_match_language(req))
        except TypeError as e:
            LOG.exception(six.text_type(e))
            return render_exception(exception.ValidationError(e),
                                    context=context,
                                    user_locale=best_match_language(req))
        except Exception as e:
            LOG.exception(six.text_type(e))
            return render_exception(exception.UnexpectedError(exception=e),
                                    context=context,
                                    user_locale=best_match_language(req))

        if result is None:
            return render_response(status=(204, 'No Content'))
        elif isinstance(result, six.string_types):
            return result
        elif isinstance(result, webob.Response):
            return result
        elif isinstance(result, webob.exc.WSGIHTTPException):
            return result

        response_code = self._get_response_code(req)
        return render_response(body=result,
                               status=response_code,
                               method=req.environ['REQUEST_METHOD'])
Exemplo n.º 16
0
    def _init_args(self, arguments):
        """Helper logic for collecting and parsing MongoDB specific arguments.

        The arguments passed in are separated out in connection specific
        setting and rest of arguments are passed to create/update/delete
        db operations.
        """
        self.conn_kwargs = {}  # connection specific arguments

        self.hosts = arguments.pop('db_hosts', None)
        if self.hosts is None:
            msg = _('db_hosts value is required')
            raise exception.ValidationError(message=msg)

        self.db_name = arguments.pop('db_name', None)
        if self.db_name is None:
            msg = _('database db_name is required')
            raise exception.ValidationError(message=msg)

        self.cache_collection = arguments.pop('cache_collection', None)
        if self.cache_collection is None:
            msg = _('cache_collection name is required')
            raise exception.ValidationError(message=msg)

        self.username = arguments.pop('username', None)
        self.password = arguments.pop('password', None)
        self.max_pool_size = arguments.pop('max_pool_size', 10)

        self.w = arguments.pop('w', -1)
        try:
            self.w = int(self.w)
        except ValueError:
            msg = _('integer value expected for w (write concern attribute)')
            raise exception.ValidationError(message=msg)

        self.read_preference = arguments.pop('read_preference', None)

        self.use_replica = arguments.pop('use_replica', False)
        if self.use_replica:
            if arguments.get('replicaset_name') is None:
                msg = _('replicaset_name required when use_replica is True')
                raise exception.ValidationError(message=msg)
            self.replicaset_name = arguments.get('replicaset_name')

        self.son_manipulator = arguments.pop('son_manipulator', None)

        # set if mongo collection needs to be TTL type.
        # This needs to be max ttl for any cache entry.
        # By default, -1 means don't use TTL collection.
        # With ttl set, it creates related index and have doc_date field with
        # needed expiration interval
        self.ttl_seconds = arguments.pop('mongo_ttl_seconds', -1)
        try:
            self.ttl_seconds = int(self.ttl_seconds)
        except ValueError:
            msg = _('integer value expected for mongo_ttl_seconds')
            raise exception.ValidationError(message=msg)

        self.conn_kwargs['ssl'] = arguments.pop('ssl', False)
        if self.conn_kwargs['ssl']:
            ssl_keyfile = arguments.pop('ssl_keyfile', None)
            ssl_certfile = arguments.pop('ssl_certfile', None)
            ssl_ca_certs = arguments.pop('ssl_ca_certs', None)
            ssl_cert_reqs = arguments.pop('ssl_cert_reqs', None)
            if ssl_keyfile:
                self.conn_kwargs['ssl_keyfile'] = ssl_keyfile
            if ssl_certfile:
                self.conn_kwargs['ssl_certfile'] = ssl_certfile
            if ssl_ca_certs:
                self.conn_kwargs['ssl_ca_certs'] = ssl_ca_certs
            if ssl_cert_reqs:
                self.conn_kwargs['ssl_cert_reqs'] = (
                    self._ssl_cert_req_type(ssl_cert_reqs))

        # rest of arguments are passed to mongo crud calls
        self.meth_kwargs = arguments
Exemplo n.º 17
0
    def update_user(self, context, user_id, user):
        # NOTE(termie): this is really more of a patch than a put
        user = self.normalize_username_in_request(user)
        self.assert_admin(context)

        if 'enabled' in user and not isinstance(user['enabled'], bool):
            msg = _('Enabled field should be a boolean')
            raise exception.ValidationError(message=msg)

        default_project_id = user.pop('tenantId', None)
        if default_project_id is not None:
            user['default_project_id'] = default_project_id

        old_user_ref = self.v3_to_v2_user(self.identity_api.get_user(user_id))

        # Check whether a tenant is being added or changed for the user.
        # Catch the case where the tenant is being changed for a user and also
        # where a user previously had no tenant but a tenant is now being
        # added for the user.
        if (('tenantId' in old_user_ref
             and old_user_ref['tenantId'] != default_project_id
             and default_project_id is not None)
                or ('tenantId' not in old_user_ref
                    and default_project_id is not None)):
            # Make sure the new project actually exists before we perform the
            # user update.
            self.assignment_api.get_project(default_project_id)

        user_ref = self.v3_to_v2_user(
            self.identity_api.update_user(user_id, user))

        # If 'tenantId' is in either ref, we might need to add or remove the
        # user from a project.
        if 'tenantId' in user_ref or 'tenantId' in old_user_ref:
            if user_ref['tenantId'] != old_user_ref.get('tenantId'):
                if old_user_ref.get('tenantId'):
                    try:
                        member_role_id = config.CONF.member_role_id
                        self.assignment_api.remove_role_from_user_and_project(
                            user_id, old_user_ref['tenantId'], member_role_id)
                    except exception.NotFound:
                        # NOTE(morganfainberg): This is not a critical error it
                        # just means that the user cannot be removed from the
                        # old tenant.  This could occur if roles aren't found
                        # or if the project is invalid or if there are no roles
                        # for the user on that project.
                        msg = _('Unable to remove user %(user)s from '
                                '%(tenant)s.')
                        LOG.warning(msg, {
                            'user': user_id,
                            'tenant': old_user_ref['tenantId']
                        })

                if user_ref['tenantId']:
                    try:
                        self.assignment_api.add_user_to_project(
                            user_ref['tenantId'], user_id)
                    except exception.Conflict:
                        # We are already a member of that tenant
                        pass
                    except exception.NotFound:
                        # NOTE(morganfainberg): Log this and move on. This is
                        # not the end of the world if we can't add the user to
                        # the appropriate tenant. Most of the time this means
                        # that the project is invalid or roles are some how
                        # incorrect.  This shouldn't prevent the return of the
                        # new ref.
                        msg = _('Unable to add user %(user)s to %(tenant)s.')
                        LOG.warning(msg, {
                            'user': user_id,
                            'tenant': user_ref['tenantId']
                        })

        return {'user': user_ref}
Exemplo n.º 18
0
    def __call__(self, req):
        arg_dict = req.environ['wsgiorg.routing_args'][1]
        action = arg_dict.pop('action')
        del arg_dict['controller']
        LOG.debug(_('arg_dict: %s'), arg_dict)

        # allow middleware up the stack to provide context, params and headers.
        context = req.environ.get(CONTEXT_ENV, {})
        context['query_string'] = dict(req.params.iteritems())
        context['headers'] = dict(req.headers.iteritems())
        context['path'] = req.environ['PATH_INFO']
        params = req.environ.get(PARAMS_ENV, {})

        for name in ['REMOTE_USER', 'AUTH_TYPE']:
            try:
                context[name] = req.environ[name]
            except KeyError:
                try:
                    del context[name]
                except KeyError:
                    pass

        params.update(arg_dict)

        context.setdefault('is_admin', False)

        # TODO(termie): do some basic normalization on methods
        method = getattr(self, action)

        # NOTE(vish): make sure we have no unicode keys for py2.6.
        params = self._normalize_dict(params)

        try:
            result = method(context, **params)
        except exception.Unauthorized as e:
            LOG.warning(
                _('Authorization failed. %(exception)s from %(remote_addr)s') %
                {
                    'exception': e,
                    'remote_addr': req.environ['REMOTE_ADDR']
                })
            return render_exception(e, user_locale=req.best_match_language())
        except exception.Error as e:
            LOG.warning(e)
            return render_exception(e, user_locale=req.best_match_language())
        except TypeError as e:
            LOG.exception(e)
            return render_exception(exception.ValidationError(e),
                                    user_locale=req.best_match_language())
        except Exception as e:
            LOG.exception(e)
            return render_exception(exception.UnexpectedError(exception=e),
                                    user_locale=req.best_match_language())

        if result is None:
            return render_response(status=(204, 'No Content'))
        elif isinstance(result, basestring):
            return result
        elif isinstance(result, webob.Response):
            return result
        elif isinstance(result, webob.exc.WSGIHTTPException):
            return result

        response_code = self._get_response_code(req)
        return render_response(body=result, status=response_code)
Exemplo n.º 19
0
 def _require_matching_id(self, value, ref):
     """Ensures the value matches the reference's ID, if any."""
     if 'id' in ref and ref['id'] != value:
         raise exception.ValidationError('Cannot change ID')
Exemplo n.º 20
0
    def __call__(self, req):
        arg_dict = req.environ['wsgiorg.routing_args'][1]
        action = arg_dict.pop('action')
        del arg_dict['controller']

        params = req.environ.get(PARAMS_ENV, {})
        params.update(arg_dict)

        # TODO(termie): do some basic normalization on methods
        method = getattr(self, action)

        # NOTE(morganfainberg): use the request method to normalize the
        # response code between GET and HEAD requests. The HTTP status should
        # be the same.
        LOG.info(
            '%(req_method)s %(uri)s', {
                'req_method': req.method.upper(),
                'uri': wsgiref.util.request_uri(req.environ),
            })

        params = self._normalize_dict(params)

        try:
            result = method(req, **params)
        except exception.Unauthorized as e:
            LOG.warning(
                _LW("Authorization failed. %(exception)s from "
                    "%(remote_addr)s"), {
                        'exception': e,
                        'remote_addr': req.environ['REMOTE_ADDR']
                    })
            return render_exception(e,
                                    context=req.context_dict,
                                    user_locale=best_match_language(req))
        except exception.Error as e:
            LOG.warning(six.text_type(e))
            return render_exception(e,
                                    context=req.context_dict,
                                    user_locale=best_match_language(req))
        except TypeError as e:
            LOG.exception(six.text_type(e))
            return render_exception(exception.ValidationError(e),
                                    context=req.context_dict,
                                    user_locale=best_match_language(req))
        except Exception as e:
            LOG.exception(six.text_type(e))
            return render_exception(exception.UnexpectedError(exception=e),
                                    context=req.context_dict,
                                    user_locale=best_match_language(req))

        if result is None:
            return render_response(
                status=(http_client.NO_CONTENT,
                        http_client.responses[http_client.NO_CONTENT]))
        elif isinstance(result, six.string_types):
            return result
        elif isinstance(result, webob.Response):
            return result
        elif isinstance(result, webob.exc.WSGIHTTPException):
            return result

        response_code = self._get_response_code(req)
        return render_response(body=result,
                               status=response_code,
                               method=req.method)
Exemplo n.º 21
0
 def _assert_domain_nand_project(self):
     if (flask.request.args.get('scope.domain.id')
             and flask.request.args.get('scope.project.id')):
         msg = _('Specify a domain or project, not both')
         raise exception.ValidationError(msg)
Exemplo n.º 22
0
    def handle_authenticate(self):
        # TODO(morgan): convert this dirty check to JSON Schema validation
        # this mirrors the previous behavior of the webob system where an
        # empty request body for s3 and ec2 tokens would result in a BAD
        # REQUEST. Almost all other APIs use JSON Schema and therefore would
        # catch this early on. S3 and EC2 did not ever get json schema
        # implemented for them.
        if not self.request_body_json:
            msg = _('request must include a request body')
            raise ks_exceptions.ValidationError(msg)

        # NOTE(morgan): THIS IS SLOPPY! Apparently... keystone passed values
        # as "credential" and "credentials" in into the s3/ec2 authenticate
        # methods. There is no reason the multiple names should have worked
        # except that we totally did something wonky in the past... so now
        # there are 2 dirty "acceptable" body hacks for compatibility....
        # Try "credentials" then "credential" and THEN ec2Credentials. Final
        # default is {}
        credentials = (self.request_body_json.get('credentials')
                       or self.request_body_json.get('credential')
                       or self.request_body_json.get('ec2Credentials'))
        if not credentials:
            credentials = {}

        if 'access' not in credentials:
            raise ks_exceptions.Unauthorized(_('EC2 Signature not supplied'))

        # Load the credential from the backend
        credential_id = utils.hash_access_key(credentials['access'])
        cred = PROVIDERS.credential_api.get_credential(credential_id)
        if not cred or cred['type'] != CRED_TYPE_EC2:
            raise ks_exceptions.Unauthorized(_('EC2 access key not found.'))

        # load from json if needed
        try:
            loaded = jsonutils.loads(cred['blob'])
        except TypeError:
            loaded = cred['blob']

        # Convert to the legacy format
        cred_data = dict(user_id=cred.get('user_id'),
                         project_id=cred.get('project_id'),
                         access=loaded.get('access'),
                         secret=loaded.get('secret'),
                         trust_id=loaded.get('trust_id'))

        # validate the signature
        self._check_signature(cred_data, credentials)
        project_ref = PROVIDERS.resource_api.get_project(
            cred_data['project_id'])
        user_ref = PROVIDERS.identity_api.get_user(cred_data['user_id'])

        # validate that the auth info is valid and nothing is disabled
        try:
            PROVIDERS.identity_api.assert_user_enabled(user_id=user_ref['id'],
                                                       user=user_ref)
            PROVIDERS.resource_api.assert_project_enabled(
                project_id=project_ref['id'], project=project_ref)
        except AssertionError as e:
            raise ks_exceptions.Unauthorized from e

        self._check_timestamp(credentials)
        roles = PROVIDERS.assignment_api.get_roles_for_user_and_project(
            user_ref['id'], project_ref['id'])

        if not roles:
            raise ks_exceptions.Unauthorized(_('User not valid for project.'))

        for r_id in roles:
            # Assert all roles exist.
            PROVIDERS.role_api.get_role(r_id)

        method_names = ['ec2credential']

        token = PROVIDERS.token_provider_api.issue_token(
            user_id=user_ref['id'],
            method_names=method_names,
            project_id=project_ref['id'])
        return token
Exemplo n.º 23
0
 def _assert_system_nand_project(self):
     if (flask.request.args.get('scope.project.id')
             and flask.request.args.get('scope.system')):
         msg = _('Specify system or project, not both')
         raise exception.ValidationError(msg)
Exemplo n.º 24
0
 def _require_domain_xor_project(self, domain_id, project_id):
     if (domain_id and project_id) or (not domain_id and not project_id):
         msg = _('Specify a domain or project, not both')
         raise exception.ValidationError(msg)
Exemplo n.º 25
0
    def _transform(self, identity_values):
        """Transform local mappings, to an easier to understand format.

        Transform the incoming array to generate the return value for
        the process function. Generating content for Keystone tokens will
        be easier if some pre-processing is done at this level.

        :param identity_values: local mapping from valid evaluations
        :type identity_values: array of dict

        Example identity_values::

            [
                {
                    'group': {'id': '0cd5e9'},
                    'user': {
                        'email': '*****@*****.**'
                    },
                },
                {
                    'groups': ['member', 'admin', tester'],
                    'domain': {
                        'name': 'default_domain'
                    }
                }
            ]

        :returns: dictionary with user name, group_ids and group_names.
        :rtype: dict

        """

        def extract_groups(groups_by_domain):
            for groups in list(groups_by_domain.values()):
                for group in list({g['name']: g for g in groups}.values()):
                    yield group

        def normalize_user(user):
            """Parse and validate user mapping."""

            user_type = user.get('type')

            if user_type and user_type not in (UserType.EPHEMERAL,
                                               UserType.LOCAL):
                msg = _("User type %s not supported") % user_type
                raise exception.ValidationError(msg)

            if user_type is None:
                user_type = user['type'] = UserType.EPHEMERAL

            if user_type == UserType.EPHEMERAL:
                user['domain'] = {
                    'id': CONF.federation.federated_domain_name
                }

        # initialize the group_ids as a set to eliminate duplicates
        user = {}
        group_ids = set()
        group_names = list()
        groups_by_domain = dict()

        for identity_value in identity_values:
            if 'user' in identity_value:
                # if a mapping outputs more than one user name, log it
                if user:
                    LOG.warning(_LW('Ignoring user name'))
                else:
                    user = identity_value.get('user')
            if 'group' in identity_value:
                group = identity_value['group']
                if 'id' in group:
                    group_ids.add(group['id'])
                elif 'name' in group:
                    domain = (group['domain'].get('name') or
                              group['domain'].get('id'))
                    groups_by_domain.setdefault(domain, list()).append(group)
                group_names.extend(extract_groups(groups_by_domain))
            if 'groups' in identity_value:
                if 'domain' not in identity_value:
                    msg = _("Invalid rule: %(identity_value)s. Both 'groups' "
                            "and 'domain' keywords must be specified.")
                    msg = msg % {'identity_value': identity_value}
                    raise exception.ValidationError(msg)
                # In this case, identity_value['groups'] is a string
                # representation of a list, and we want a real list.  This is
                # due to the way we do direct mapping substitutions today (see
                # function _update_local_mapping() )
                try:
                    group_names_list = ast.literal_eval(
                        identity_value['groups'])
                except ValueError:
                    group_names_list = [identity_value['groups']]
                domain = identity_value['domain']
                group_dicts = [{'name': name, 'domain': domain} for name in
                               group_names_list]

                group_names.extend(group_dicts)

        normalize_user(user)

        return {'user': user,
                'group_ids': list(group_ids),
                'group_names': group_names}
Exemplo n.º 26
0
 def _require_user_xor_group(self, user_id, group_id):
     if (user_id and group_id) or (not user_id and not group_id):
         msg = _('Specify a user or group, not both')
         raise exception.ValidationError(msg)
Exemplo n.º 27
0
    def authenticate(self, request, auth=None):
        """Authenticate credentials and return a token.

        Accept auth as a dict that looks like::

            {
                "auth":{
                    "passwordCredentials":{
                        "username":"******",
                        "password":"******"
                    },
                    "tenantName":"customer-x"
                }
            }

        In this case, tenant is optional, if not provided the token will be
        considered "unscoped" and can later be used to get a scoped token.

        Alternatively, this call accepts auth with only a token and tenant
        that will return a token that is scoped to that tenant.
        """
        if auth is None:
            raise exception.ValidationError(attribute='auth',
                                            target='request body')

        if 'token' in auth:
            # Try to authenticate using a token
            auth_info = self._authenticate_token(request, auth)
        else:
            # Try external authentication
            try:
                auth_info = self._authenticate_external(request, auth)
            except ExternalAuthNotApplicable:
                # Try local authentication
                auth_info = self._authenticate_local(request, auth)

        user_ref, tenant_ref, metadata_ref, expiry, bind, audit_id = auth_info
        # Validate that the auth info is valid and nothing is disabled
        try:
            self.identity_api.assert_user_enabled(user_id=user_ref['id'],
                                                  user=user_ref)
            if tenant_ref:
                self.resource_api.assert_project_enabled(
                    project_id=tenant_ref['id'], project=tenant_ref)
        except AssertionError as e:
            six.reraise(exception.Unauthorized, exception.Unauthorized(e),
                        sys.exc_info()[2])
        # NOTE(morganfainberg): Make sure the data is in correct form since it
        # might be consumed external to Keystone and this is a v2.0 controller.
        # The user_ref is encoded into the auth_token_data which is returned as
        # part of the token data. The token provider doesn't care about the
        # format.
        user_ref = self.v3_to_v2_user(user_ref)
        if tenant_ref:
            tenant_ref = self.v3_to_v2_project(tenant_ref)

        auth_token_data = self._get_auth_token_data(user_ref, tenant_ref,
                                                    metadata_ref, expiry,
                                                    audit_id)

        if tenant_ref:
            catalog_ref = self.catalog_api.get_catalog(user_ref['id'],
                                                       tenant_ref['id'])
        else:
            catalog_ref = {}

        auth_token_data['id'] = 'placeholder'
        if bind:
            auth_token_data['bind'] = bind

        roles_ref = []
        for role_id in metadata_ref.get('roles', []):
            role_ref = self.role_api.get_role(role_id)
            roles_ref.append(dict(name=role_ref['name']))

        (token_id, token_data) = self.token_provider_api.issue_v2_token(
            auth_token_data, roles_ref=roles_ref, catalog_ref=catalog_ref)

        # NOTE(wanghong): We consume a trust use only when we are using trusts
        # and have successfully issued a token.
        if CONF.trust.enabled and 'trust_id' in auth:
            self.trust_api.consume_use(auth['trust_id'])

        return token_data
Exemplo n.º 28
0
    def __call__(self, req):
        arg_dict = req.environ['wsgiorg.routing_args'][1]
        action = arg_dict.pop('action')
        del arg_dict['controller']
        LOG.debug('arg_dict: %s', arg_dict)

        # allow middleware up the stack to provide context, params and headers.
        context = req.environ.get(CONTEXT_ENV, {})
        context['query_string'] = dict(six.iteritems(req.params))
        context['headers'] = dict(six.iteritems(req.headers))
        context['path'] = req.environ['PATH_INFO']
        context['host_url'] = req.host_url
        params = req.environ.get(PARAMS_ENV, {})
        # authentication and authorization attributes are set as environment
        # values by the container and processed by the pipeline.  the complete
        # set is not yet know.
        context['environment'] = req.environ
        context['accept_header'] = req.accept
        req.environ = None

        params.update(arg_dict)

        context.setdefault('is_admin', False)

        # TODO(termie): do some basic normalization on methods
        method = getattr(self, action)

        # NOTE(morganfainberg): use the request method to normalize the
        # response code between GET and HEAD requests. The HTTP status should
        # be the same.
        req_method = req.environ['REQUEST_METHOD'].upper()

        # NOTE(vish): make sure we have no unicode keys for py2.6.
        params = self._normalize_dict(params)

        try:
            result = method(context, **params)
        except exception.Unauthorized as e:
            LOG.warning(
                _LW("Authorization failed. %(exception)s from "
                    "%(remote_addr)s"), {
                        'exception': e,
                        'remote_addr': req.environ['REMOTE_ADDR']
                    })
            return render_exception(e,
                                    context=context,
                                    user_locale=best_match_language(req))
        except exception.Error as e:
            LOG.warning(e)
            return render_exception(e,
                                    context=context,
                                    user_locale=best_match_language(req))
        except TypeError as e:
            LOG.exception(e)
            return render_exception(exception.ValidationError(e),
                                    context=context,
                                    user_locale=best_match_language(req))
        except Exception as e:
            LOG.exception(e)
            return render_exception(exception.UnexpectedError(exception=e),
                                    context=context,
                                    user_locale=best_match_language(req))

        if result is None:
            return render_response(status=(204, 'No Content'))
        elif isinstance(result, six.string_types):
            return result
        elif isinstance(result, webob.Response):
            return result
        elif isinstance(result, webob.exc.WSGIHTTPException):
            return result

        response_code = self._get_response_code(req)
        return render_response(body=result,
                               status=response_code,
                               method=req_method)
Exemplo n.º 29
0
    def set_user_password(self, context, user_id, user):
        token_id = context.get('token_id')
        original_password = user.get('original_password')

        token_data = self.token_provider_api.validate_token(token_id)
        token_ref = token_model.KeystoneToken(token_id=token_id,
                                              token_data=token_data)

        if token_ref.user_id != user_id:
            raise exception.Forbidden('Token belongs to another user')
        if original_password is None:
            raise exception.ValidationError(target='user',
                                            attribute='original password')

        try:
            user_ref = self.identity_api.authenticate(
                context, user_id=token_ref.user_id, password=original_password)
            if not user_ref.get('enabled', True):
                # NOTE(dolph): why can't you set a disabled user's password?
                raise exception.Unauthorized('User is disabled')
        except AssertionError:
            raise exception.Unauthorized()

        update_dict = {'password': user['password'], 'id': user_id}

        admin_context = copy.copy(context)
        admin_context['is_admin'] = True
        super(UserController, self).set_user_password(admin_context, user_id,
                                                      update_dict)

        # Issue a new token based upon the original token data. This will
        # always be a V2.0 token.

        # TODO(morganfainberg): Add a mechanism to issue a new token directly
        # from a token model so that this code can go away. This is likely
        # not the norm as most cases do not need to yank apart a token to
        # issue a new one.
        new_token_ref = {}
        metadata_ref = {}
        roles_ref = None

        new_token_ref['user'] = user_ref
        if token_ref.bind:
            new_token_ref['bind'] = token_ref.bind
        if token_ref.project_id:
            new_token_ref['tenant'] = self.resource_api.get_project(
                token_ref.project_id)
        if token_ref.role_names:
            roles_ref = [dict(name=value) for value in token_ref.role_names]
        if token_ref.role_ids:
            metadata_ref['roles'] = token_ref.role_ids
        if token_ref.trust_id:
            metadata_ref['trust'] = {
                'id': token_ref.trust_id,
                'trustee_user_id': token_ref.trustee_user_id
            }
        new_token_ref['metadata'] = metadata_ref
        new_token_ref['id'] = uuid.uuid4().hex

        catalog_ref = self.catalog_api.get_catalog(user_id,
                                                   token_ref.project_id)

        new_token_id, new_token_data = self.token_provider_api.issue_v2_token(
            token_ref=new_token_ref,
            roles_ref=roles_ref,
            catalog_ref=catalog_ref)
        LOG.debug('TOKEN_REF %s', new_token_data)
        return new_token_data
Exemplo n.º 30
0
 def _validate_endpoint(self, endpoint):
     if 'enabled' in endpoint and not isinstance(endpoint['enabled'], bool):
         msg = _('Enabled field must be a boolean')
         raise exception.ValidationError(message=msg)