"Fossies" - the Fresh Open Source Software Archive

Member "keystone-17.0.0/keystone/api/os_federation.py" (13 May 2020, 19093 Bytes) of package /linux/misc/openstack/keystone-17.0.0.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "os_federation.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 16.0.1_vs_17.0.0.

    1 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    2 #    not use this file except in compliance with the License. You may obtain
    3 #    a copy of the License at
    4 #
    5 #         http://www.apache.org/licenses/LICENSE-2.0
    6 #
    7 #    Unless required by applicable law or agreed to in writing, software
    8 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    9 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   10 #    License for the specific language governing permissions and limitations
   11 #    under the License.
   12 
   13 # This file handles all flask-restful resources for /v3/OS-FEDERATION
   14 
   15 import flask
   16 import flask_restful
   17 import http.client
   18 from oslo_serialization import jsonutils
   19 
   20 from keystone.api._shared import authentication
   21 from keystone.api._shared import json_home_relations
   22 from keystone.common import provider_api
   23 from keystone.common import rbac_enforcer
   24 from keystone.common import render_token
   25 from keystone.common import validation
   26 import keystone.conf
   27 from keystone import exception
   28 from keystone.federation import schema
   29 from keystone.federation import utils
   30 from keystone.server import flask as ks_flask
   31 
   32 
   33 CONF = keystone.conf.CONF
   34 ENFORCER = rbac_enforcer.RBACEnforcer
   35 PROVIDERS = provider_api.ProviderAPIs
   36 
   37 
   38 _build_param_relation = json_home_relations.os_federation_parameter_rel_func
   39 _build_resource_relation = json_home_relations.os_federation_resource_rel_func
   40 
   41 IDP_ID_PARAMETER_RELATION = _build_param_relation(parameter_name='idp_id')
   42 PROTOCOL_ID_PARAMETER_RELATION = _build_param_relation(
   43     parameter_name='protocol_id')
   44 SP_ID_PARAMETER_RELATION = _build_param_relation(parameter_name='sp_id')
   45 
   46 
   47 def _combine_lists_uniquely(a, b):
   48     # it's most likely that only one of these will be filled so avoid
   49     # the combination if possible.
   50     if a and b:
   51         return {x['id']: x for x in a + b}.values()
   52     else:
   53         return a or b
   54 
   55 
   56 class _ResourceBase(ks_flask.ResourceBase):
   57     json_home_resource_rel_func = _build_resource_relation
   58     json_home_parameter_rel_func = _build_param_relation
   59 
   60     @classmethod
   61     def wrap_member(cls, ref, collection_name=None, member_name=None):
   62         cls._add_self_referential_link(ref, collection_name)
   63         cls._add_related_links(ref)
   64         return {member_name or cls.member_key: cls.filter_params(ref)}
   65 
   66     @staticmethod
   67     def _add_related_links(ref):
   68         # Do Nothing, This is in support of child class mechanisms.
   69         pass
   70 
   71 
   72 class IdentityProvidersResource(_ResourceBase):
   73     collection_key = 'identity_providers'
   74     member_key = 'identity_provider'
   75     api_prefix = '/OS-FEDERATION'
   76     _public_parameters = frozenset(['id', 'enabled', 'description',
   77                                     'remote_ids', 'links', 'domain_id',
   78                                     'authorization_ttl'
   79                                     ])
   80     _id_path_param_name_override = 'idp_id'
   81 
   82     @staticmethod
   83     def _add_related_links(ref):
   84         """Add URLs for entities related with Identity Provider.
   85 
   86         Add URLs pointing to:
   87         - protocols tied to the Identity Provider
   88 
   89         """
   90         base_path = ref['links'].get('self')
   91         if base_path is None:
   92             base_path = '/'.join(ks_flask.base_url(path='/%s' % ref['id']))
   93 
   94         for name in ['protocols']:
   95             ref['links'][name] = '/'.join([base_path, name])
   96 
   97     def get(self, idp_id=None):
   98         if idp_id is not None:
   99             return self._get_idp(idp_id)
  100         return self._list_idps()
  101 
  102     def _get_idp(self, idp_id):
  103         """Get an IDP resource.
  104 
  105         GET/HEAD /OS-FEDERATION/identity_providers/{idp_id}
  106         """
  107         ENFORCER.enforce_call(action='identity:get_identity_provider')
  108         ref = PROVIDERS.federation_api.get_idp(idp_id)
  109         return self.wrap_member(ref)
  110 
  111     def _list_idps(self):
  112         """List all identity providers.
  113 
  114         GET/HEAD /OS-FEDERATION/identity_providers
  115         """
  116         filters = ['id', 'enabled']
  117         ENFORCER.enforce_call(action='identity:list_identity_providers',
  118                               filters=filters)
  119         hints = self.build_driver_hints(filters)
  120         refs = PROVIDERS.federation_api.list_idps(hints=hints)
  121         refs = [self.filter_params(r) for r in refs]
  122         collection = self.wrap_collection(refs, hints=hints)
  123         for r in collection[self.collection_key]:
  124             # Add the related links explicitly
  125             self._add_related_links(r)
  126         return collection
  127 
  128     def put(self, idp_id):
  129         """Create an idp resource for federated authentication.
  130 
  131         PUT /OS-FEDERATION/identity_providers/{idp_id}
  132         """
  133         ENFORCER.enforce_call(action='identity:create_identity_provider')
  134         idp = self.request_body_json.get('identity_provider', {})
  135         validation.lazy_validate(schema.identity_provider_create,
  136                                  idp)
  137         idp = self._normalize_dict(idp)
  138         idp.setdefault('enabled', False)
  139         idp_ref = PROVIDERS.federation_api.create_idp(
  140             idp_id, idp)
  141         return self.wrap_member(idp_ref), http.client.CREATED
  142 
  143     def patch(self, idp_id):
  144         ENFORCER.enforce_call(action='identity:update_identity_provider')
  145         idp = self.request_body_json.get('identity_provider', {})
  146         validation.lazy_validate(schema.identity_provider_update, idp)
  147         idp = self._normalize_dict(idp)
  148         idp_ref = PROVIDERS.federation_api.update_idp(
  149             idp_id, idp)
  150         return self.wrap_member(idp_ref)
  151 
  152     def delete(self, idp_id):
  153         ENFORCER.enforce_call(action='identity:delete_identity_provider')
  154         PROVIDERS.federation_api.delete_idp(idp_id)
  155         return None, http.client.NO_CONTENT
  156 
  157 
  158 class _IdentityProvidersProtocolsResourceBase(_ResourceBase):
  159     collection_key = 'protocols'
  160     member_key = 'protocol'
  161     _public_parameters = frozenset(['id', 'mapping_id', 'links'])
  162     json_home_additional_parameters = {
  163         'idp_id': IDP_ID_PARAMETER_RELATION}
  164     json_home_collection_resource_name_override = 'identity_provider_protocols'
  165     json_home_member_resource_name_override = 'identity_provider_protocol'
  166 
  167     @staticmethod
  168     def _add_related_links(ref):
  169         """Add new entries to the 'links' subdictionary in the response.
  170 
  171         Adds 'identity_provider' key with URL pointing to related identity
  172         provider as a value.
  173 
  174         :param ref: response dictionary
  175 
  176         """
  177         ref.setdefault('links', {})
  178         ref['links']['identity_provider'] = ks_flask.base_url(
  179             path=ref['idp_id'])
  180 
  181 
  182 class IDPProtocolsListResource(_IdentityProvidersProtocolsResourceBase):
  183 
  184     def get(self, idp_id):
  185         """List protocols for an IDP.
  186 
  187         HEAD/GET /OS-FEDERATION/identity_providers/{idp_id}/protocols
  188         """
  189         ENFORCER.enforce_call(action='identity:list_protocols')
  190         protocol_refs = PROVIDERS.federation_api.list_protocols(idp_id)
  191         protocols = list(protocol_refs)
  192         collection = self.wrap_collection(protocols)
  193         for r in collection[self.collection_key]:
  194             # explicitly add related links
  195             self._add_related_links(r)
  196         return collection
  197 
  198 
  199 class IDPProtocolsCRUDResource(_IdentityProvidersProtocolsResourceBase):
  200 
  201     def get(self, idp_id, protocol_id):
  202         """Get protocols for an IDP.
  203 
  204         HEAD/GET /OS-FEDERATION/identity_providers/
  205                  {idp_id}/protocols/{protocol_id}
  206         """
  207         ENFORCER.enforce_call(action='identity:get_protocol')
  208         ref = PROVIDERS.federation_api.get_protocol(idp_id, protocol_id)
  209         return self.wrap_member(ref)
  210 
  211     def put(self, idp_id, protocol_id):
  212         """Create protocol for an IDP.
  213 
  214         PUT /OS-Federation/identity_providers/{idp_id}/protocols/{protocol_id}
  215         """
  216         ENFORCER.enforce_call(action='identity:create_protocol')
  217         protocol = self.request_body_json.get('protocol', {})
  218         validation.lazy_validate(schema.protocol_create, protocol)
  219         protocol = self._normalize_dict(protocol)
  220         ref = PROVIDERS.federation_api.create_protocol(idp_id, protocol_id,
  221                                                        protocol)
  222         return self.wrap_member(ref), http.client.CREATED
  223 
  224     def patch(self, idp_id, protocol_id):
  225         """Update protocol for an IDP.
  226 
  227         PATCH /OS-FEDERATION/identity_providers/
  228               {idp_id}/protocols/{protocol_id}
  229         """
  230         ENFORCER.enforce_call(action='identity:update_protocol')
  231         protocol = self.request_body_json.get('protocol', {})
  232         validation.lazy_validate(schema.protocol_update, protocol)
  233         ref = PROVIDERS.federation_api.update_protocol(idp_id, protocol_id,
  234                                                        protocol)
  235         return self.wrap_member(ref)
  236 
  237     def delete(self, idp_id, protocol_id):
  238         """Delete a protocol from an IDP.
  239 
  240         DELETE /OS-FEDERATION/identity_providers/
  241                {idp_id}/protocols/{protocol_id}
  242         """
  243         ENFORCER.enforce_call(action='identity:delete_protocol')
  244         PROVIDERS.federation_api.delete_protocol(idp_id, protocol_id)
  245         return None, http.client.NO_CONTENT
  246 
  247 
  248 class MappingResource(_ResourceBase):
  249     collection_key = 'mappings'
  250     member_key = 'mapping'
  251     api_prefix = '/OS-FEDERATION'
  252 
  253     def get(self, mapping_id=None):
  254         if mapping_id is not None:
  255             return self._get_mapping(mapping_id)
  256         return self._list_mappings()
  257 
  258     def _get_mapping(self, mapping_id):
  259         """Get a mapping.
  260 
  261         HEAD/GET /OS-FEDERATION/mappings/{mapping_id}
  262         """
  263         ENFORCER.enforce_call(action='identity:get_mapping')
  264         return self.wrap_member(PROVIDERS.federation_api.get_mapping(
  265             mapping_id))
  266 
  267     def _list_mappings(self):
  268         """List mappings.
  269 
  270         HEAD/GET /OS-FEDERATION/mappings
  271         """
  272         ENFORCER.enforce_call(action='identity:list_mappings')
  273         return self.wrap_collection(PROVIDERS.federation_api.list_mappings())
  274 
  275     def put(self, mapping_id):
  276         """Create a mapping.
  277 
  278         PUT /OS-FEDERATION/mappings/{mapping_id}
  279         """
  280         ENFORCER.enforce_call(action='identity:create_mapping')
  281         mapping = self.request_body_json.get('mapping', {})
  282         mapping = self._normalize_dict(mapping)
  283         utils.validate_mapping_structure(mapping)
  284         mapping_ref = PROVIDERS.federation_api.create_mapping(
  285             mapping_id, mapping)
  286         return self.wrap_member(mapping_ref), http.client.CREATED
  287 
  288     def patch(self, mapping_id):
  289         """Update a mapping.
  290 
  291         PATCH /OS-FEDERATION/mappings/{mapping_id}
  292         """
  293         ENFORCER.enforce_call(action='identity:update_mapping')
  294         mapping = self.request_body_json.get('mapping', {})
  295         mapping = self._normalize_dict(mapping)
  296         utils.validate_mapping_structure(mapping)
  297         mapping_ref = PROVIDERS.federation_api.update_mapping(
  298             mapping_id, mapping)
  299         return self.wrap_member(mapping_ref)
  300 
  301     def delete(self, mapping_id):
  302         """Delete a mapping.
  303 
  304         DELETE /OS-FEDERATION/mappings/{mapping_id}
  305         """
  306         ENFORCER.enforce_call(action='identity:delete_mapping')
  307         PROVIDERS.federation_api.delete_mapping(mapping_id)
  308         return None, http.client.NO_CONTENT
  309 
  310 
  311 class ServiceProvidersResource(_ResourceBase):
  312     collection_key = 'service_providers'
  313     member_key = 'service_provider'
  314     _public_parameters = frozenset(['auth_url', 'id', 'enabled', 'description',
  315                                     'links', 'relay_state_prefix', 'sp_url'])
  316     _id_path_param_name_override = 'sp_id'
  317     api_prefix = '/OS-FEDERATION'
  318 
  319     def get(self, sp_id=None):
  320         if sp_id is not None:
  321             return self._get_service_provider(sp_id)
  322         return self._list_service_providers()
  323 
  324     def _get_service_provider(self, sp_id):
  325         """Get a service provider.
  326 
  327         GET/HEAD /OS-FEDERATION/service_providers/{sp_id}
  328         """
  329         ENFORCER.enforce_call(action='identity:get_service_provider')
  330         return self.wrap_member(PROVIDERS.federation_api.get_sp(sp_id))
  331 
  332     def _list_service_providers(self):
  333         """List service providers.
  334 
  335         GET/HEAD /OS-FEDERATION/service_providers
  336         """
  337         filters = ['id', 'enabled']
  338         ENFORCER.enforce_call(action='identity:list_service_providers',
  339                               filters=filters)
  340         hints = self.build_driver_hints(filters)
  341         refs = [self.filter_params(r)
  342                 for r in
  343                 PROVIDERS.federation_api.list_sps(hints=hints)]
  344         return self.wrap_collection(refs, hints=hints)
  345 
  346     def put(self, sp_id):
  347         """Create a service provider.
  348 
  349         PUT /OS-FEDERATION/service_providers/{sp_id}
  350         """
  351         ENFORCER.enforce_call(action='identity:create_service_provider')
  352         sp = self.request_body_json.get('service_provider', {})
  353         validation.lazy_validate(schema.service_provider_create, sp)
  354         sp = self._normalize_dict(sp)
  355         sp.setdefault('enabled', False)
  356         sp.setdefault('relay_state_prefix',
  357                       CONF.saml.relay_state_prefix)
  358         sp_ref = PROVIDERS.federation_api.create_sp(sp_id, sp)
  359         return self.wrap_member(sp_ref), http.client.CREATED
  360 
  361     def patch(self, sp_id):
  362         """Update a service provider.
  363 
  364         PATCH /OS-FEDERATION/service_providers/{sp_id}
  365         """
  366         ENFORCER.enforce_call(action='identity:update_service_provider')
  367         sp = self.request_body_json.get('service_provider', {})
  368         validation.lazy_validate(schema.service_provider_update, sp)
  369         sp = self._normalize_dict(sp)
  370         sp_ref = PROVIDERS.federation_api.update_sp(sp_id, sp)
  371         return self.wrap_member(sp_ref)
  372 
  373     def delete(self, sp_id):
  374         """Delete a service provider.
  375 
  376         DELETE /OS-FEDERATION/service_providers/{sp_id}
  377         """
  378         ENFORCER.enforce_call(action='identity:delete_service_provider')
  379         PROVIDERS.federation_api.delete_sp(sp_id)
  380         return None, http.client.NO_CONTENT
  381 
  382 
  383 class SAML2MetadataResource(flask_restful.Resource):
  384     @ks_flask.unenforced_api
  385     def get(self):
  386         """Get SAML2 metadata.
  387 
  388         GET/HEAD /OS-FEDERATION/saml2/metadata
  389         """
  390         metadata_path = CONF.saml.idp_metadata_path
  391         try:
  392             with open(metadata_path, 'r') as metadata_handler:
  393                 metadata = metadata_handler.read()
  394         except IOError as e:
  395             # Raise HTTP 500 in case Metadata file cannot be read.
  396             raise exception.MetadataFileError(reason=e)
  397         resp = flask.make_response(metadata, http.client.OK)
  398         resp.headers['Content-Type'] = 'text/xml'
  399         return resp
  400 
  401 
  402 class OSFederationAuthResource(flask_restful.Resource):
  403 
  404     @ks_flask.unenforced_api
  405     def get(self, idp_id, protocol_id):
  406         """Authenticate from dedicated uri endpoint.
  407 
  408         GET/HEAD /OS-FEDERATION/identity_providers/
  409                  {idp_id}/protocols/{protocol_id}/auth
  410         """
  411         return self._auth(idp_id, protocol_id)
  412 
  413     @ks_flask.unenforced_api
  414     def post(self, idp_id, protocol_id):
  415         """Authenticate from dedicated uri endpoint.
  416 
  417         POST /OS-FEDERATION/identity_providers/
  418              {idp_id}/protocols/{protocol_id}/auth
  419         """
  420         return self._auth(idp_id, protocol_id)
  421 
  422     def _auth(self, idp_id, protocol_id):
  423         """Build and pass auth data to authentication code.
  424 
  425         Build HTTP request body for federated authentication and inject
  426         it into the ``authenticate_for_token`` function.
  427         """
  428         auth = {
  429             'identity': {
  430                 'methods': [protocol_id],
  431                 protocol_id: {
  432                     'identity_provider': idp_id,
  433                     'protocol': protocol_id
  434                 },
  435             }
  436         }
  437         token = authentication.authenticate_for_token(auth)
  438         token_data = render_token.render_token_response_from_model(token)
  439         resp_data = jsonutils.dumps(token_data)
  440         flask_resp = flask.make_response(resp_data, http.client.CREATED)
  441         flask_resp.headers['X-Subject-Token'] = token.id
  442         flask_resp.headers['Content-Type'] = 'application/json'
  443         return flask_resp
  444 
  445 
  446 class OSFederationAPI(ks_flask.APIBase):
  447     _name = 'OS-FEDERATION'
  448     _import_name = __name__
  449     _api_url_prefix = '/OS-FEDERATION'
  450     resources = []
  451     resource_mapping = [
  452         ks_flask.construct_resource_map(
  453             resource=SAML2MetadataResource,
  454             url='/saml2/metadata',
  455             resource_kwargs={},
  456             rel='metadata',
  457             resource_relation_func=_build_resource_relation),
  458         ks_flask.construct_resource_map(
  459             resource=OSFederationAuthResource,
  460             url=('/identity_providers/<string:idp_id>/protocols/'
  461                  '<string:protocol_id>/auth'),
  462             resource_kwargs={},
  463             rel='identity_provider_protocol_auth',
  464             resource_relation_func=_build_resource_relation,
  465             path_vars={
  466                 'idp_id': IDP_ID_PARAMETER_RELATION,
  467                 'protocol_id': PROTOCOL_ID_PARAMETER_RELATION}),
  468     ]
  469 
  470 
  471 class OSFederationIdentityProvidersAPI(ks_flask.APIBase):
  472     _name = 'identity_providers'
  473     _import_name = __name__
  474     _api_url_prefix = '/OS-FEDERATION'
  475     resources = [IdentityProvidersResource]
  476     resource_mapping = []
  477 
  478 
  479 class OSFederationIdentityProvidersProtocolsAPI(ks_flask.APIBase):
  480     _name = 'protocols'
  481     _import_name = __name__
  482     resources = []
  483     resource_mapping = [
  484         ks_flask.construct_resource_map(
  485             resource=IDPProtocolsCRUDResource,
  486             url=('/OS-FEDERATION/identity_providers/<string:idp_id>/protocols/'
  487                  '<string:protocol_id>'),
  488             resource_kwargs={},
  489             rel='identity_provider_protocol',
  490             resource_relation_func=_build_resource_relation,
  491             path_vars={
  492                 'idp_id': IDP_ID_PARAMETER_RELATION,
  493                 'protocol_id': PROTOCOL_ID_PARAMETER_RELATION
  494             }
  495         ),
  496         ks_flask.construct_resource_map(
  497             resource=IDPProtocolsListResource,
  498             url='/OS-FEDERATION/identity_providers/<string:idp_id>/protocols',
  499             resource_kwargs={},
  500             rel='identity_provider_protocols',
  501             resource_relation_func=_build_resource_relation,
  502             path_vars={
  503                 'idp_id': IDP_ID_PARAMETER_RELATION
  504             }
  505         ),
  506     ]
  507 
  508 
  509 class OSFederationMappingsAPI(ks_flask.APIBase):
  510     _name = 'mappings'
  511     _import_name = __name__
  512     _api_url_prefix = '/OS-FEDERATION'
  513     resources = [MappingResource]
  514     resource_mapping = []
  515 
  516 
  517 class OSFederationServiceProvidersAPI(ks_flask.APIBase):
  518     _name = 'service_providers'
  519     _import_name = __name__
  520     _api_url_prefix = '/OS-FEDERATION'
  521     resources = [ServiceProvidersResource]
  522     resource_mapping = []
  523 
  524 
  525 APIs = (
  526     OSFederationAPI,
  527     OSFederationIdentityProvidersAPI,
  528     OSFederationIdentityProvidersProtocolsAPI,
  529     OSFederationMappingsAPI,
  530     OSFederationServiceProvidersAPI
  531 )