"Fossies" - the Fresh Open Source Software Archive

Member "keystone-17.0.0/keystone/api/_shared/EC2_S3_Resource.py" (13 May 2020, 7747 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 "EC2_S3_Resource.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 # Common base resource for EC2 and S3 Authentication
   14 
   15 import datetime
   16 
   17 from oslo_serialization import jsonutils
   18 from oslo_utils import timeutils
   19 from werkzeug import exceptions
   20 
   21 from keystone.common import provider_api
   22 from keystone.common import utils
   23 import keystone.conf
   24 from keystone import exception as ks_exceptions
   25 from keystone.i18n import _
   26 from keystone.server import flask as ks_flask
   27 
   28 CONF = keystone.conf.CONF
   29 PROVIDERS = provider_api.ProviderAPIs
   30 CRED_TYPE_EC2 = 'ec2'
   31 
   32 
   33 class ResourceBase(ks_flask.ResourceBase):
   34     def get(self):
   35         # SPECIAL CASE: GET is not allowed, raise METHOD_NOT_ALLOWED
   36         raise exceptions.MethodNotAllowed(valid_methods=['POST'])
   37 
   38     @staticmethod
   39     def _check_signature(cred_ref, credentials):
   40         # NOTE(morgan): @staticmethod doesn't always play nice with
   41         # the ABC module.
   42         raise NotImplementedError()
   43 
   44     @staticmethod
   45     def _check_timestamp(credentials):
   46         timestamp = (
   47             # AWS Signature v1/v2
   48             credentials.get('params', {}).get('Timestamp') or
   49             # AWS Signature v4
   50             credentials.get('headers', {}).get('X-Amz-Date') or
   51             credentials.get('params', {}).get('X-Amz-Date')
   52         )
   53         if not timestamp:
   54             # If the signed payload doesn't include a timestamp then the signer
   55             # must have intentionally left it off
   56             return
   57         try:
   58             timestamp = timeutils.parse_isotime(timestamp)
   59             timestamp = timeutils.normalize_time(timestamp)
   60         except Exception as e:
   61             raise ks_exceptions.Unauthorized(
   62                 _('Credential timestamp is invalid: %s') % e)
   63         auth_ttl = datetime.timedelta(minutes=CONF.credential.auth_ttl)
   64         current_time = timeutils.normalize_time(timeutils.utcnow())
   65         if current_time > timestamp + auth_ttl:
   66             raise ks_exceptions.Unauthorized(
   67                 _('Credential is expired'))
   68 
   69     def handle_authenticate(self):
   70         # TODO(morgan): convert this dirty check to JSON Schema validation
   71         # this mirrors the previous behavior of the webob system where an
   72         # empty request body for s3 and ec2 tokens would result in a BAD
   73         # REQUEST. Almost all other APIs use JSON Schema and therefore would
   74         # catch this early on. S3 and EC2 did not ever get json schema
   75         # implemented for them.
   76         if not self.request_body_json:
   77             msg = _('request must include a request body')
   78             raise ks_exceptions.ValidationError(msg)
   79 
   80         # NOTE(morgan): THIS IS SLOPPY! Apparently... keystone passed values
   81         # as "credential" and "credentials" in into the s3/ec2 authenticate
   82         # methods. There is no reason the multiple names should have worked
   83         # except that we totally did something wonky in the past... so now
   84         # there are 2 dirty "acceptable" body hacks for compatibility....
   85         # Try "credentials" then "credential" and THEN ec2Credentials. Final
   86         # default is {}
   87         credentials = (
   88             self.request_body_json.get('credentials') or
   89             self.request_body_json.get('credential') or
   90             self.request_body_json.get('ec2Credentials')
   91         )
   92         if not credentials:
   93             credentials = {}
   94 
   95         if 'access' not in credentials:
   96             raise ks_exceptions.Unauthorized(_('EC2 Signature not supplied'))
   97 
   98         # Load the credential from the backend
   99         credential_id = utils.hash_access_key(credentials['access'])
  100         cred = PROVIDERS.credential_api.get_credential(credential_id)
  101         if not cred or cred['type'] != CRED_TYPE_EC2:
  102             raise ks_exceptions.Unauthorized(_('EC2 access key not found.'))
  103 
  104         # load from json if needed
  105         try:
  106             loaded = jsonutils.loads(cred['blob'])
  107         except TypeError:
  108             loaded = cred['blob']
  109 
  110         # Convert to the legacy format
  111         cred_data = dict(
  112             user_id=cred.get('user_id'),
  113             project_id=cred.get('project_id'),
  114             access=loaded.get('access'),
  115             secret=loaded.get('secret'),
  116             trust_id=loaded.get('trust_id'),
  117             app_cred_id=loaded.get('app_cred_id'),
  118             access_token_id=loaded.get('access_token_id')
  119         )
  120 
  121         # validate the signature
  122         self._check_signature(cred_data, credentials)
  123         project_ref = PROVIDERS.resource_api.get_project(
  124             cred_data['project_id'])
  125         user_ref = PROVIDERS.identity_api.get_user(cred_data['user_id'])
  126 
  127         # validate that the auth info is valid and nothing is disabled
  128         try:
  129             PROVIDERS.identity_api.assert_user_enabled(
  130                 user_id=user_ref['id'], user=user_ref)
  131             PROVIDERS.resource_api.assert_project_enabled(
  132                 project_id=project_ref['id'], project=project_ref)
  133         except AssertionError as e:
  134             raise ks_exceptions.Unauthorized from e
  135 
  136         self._check_timestamp(credentials)
  137 
  138         trustee_user_id = None
  139         auth_context = None
  140         if cred_data['trust_id']:
  141             trust = PROVIDERS.trust_api.get_trust(cred_data['trust_id'])
  142             roles = [r['id'] for r in trust['roles']]
  143             # NOTE(cmurphy): if this credential was created using a
  144             # trust-scoped token with impersonation, the user_id will be for
  145             # the trustor, not the trustee. In this case, issuing a
  146             # trust-scoped token to the trustor will fail. In order to get a
  147             # trust-scoped token, use the user ID of the trustee. With
  148             # impersonation, the resulting token will still be for the trustor.
  149             # Without impersonation, the token will be for the trustee.
  150             if trust['impersonation'] is True:
  151                 trustee_user_id = trust['trustee_user_id']
  152         elif cred_data['app_cred_id']:
  153             ac_client = PROVIDERS.application_credential_api
  154             app_cred = ac_client.get_application_credential(
  155                 cred_data['app_cred_id'])
  156             roles = [r['id'] for r in app_cred['roles']]
  157         elif cred_data['access_token_id']:
  158             access_token = PROVIDERS.oauth_api.get_access_token(
  159                 cred_data['access_token_id'])
  160             roles = jsonutils.loads(access_token['role_ids'])
  161             auth_context = {'access_token_id': cred_data['access_token_id']}
  162         else:
  163             roles = PROVIDERS.assignment_api.get_roles_for_user_and_project(
  164                 user_ref['id'], project_ref['id'])
  165 
  166         if not roles:
  167             raise ks_exceptions.Unauthorized(_('User not valid for project.'))
  168 
  169         for r_id in roles:
  170             # Assert all roles exist.
  171             PROVIDERS.role_api.get_role(r_id)
  172 
  173         method_names = ['ec2credential']
  174 
  175         if trustee_user_id:
  176             user_id = trustee_user_id
  177         else:
  178             user_id = user_ref['id']
  179         token = PROVIDERS.token_provider_api.issue_token(
  180             user_id=user_id, method_names=method_names,
  181             project_id=project_ref['id'],
  182             trust_id=cred_data['trust_id'],
  183             app_cred_id=cred_data['app_cred_id'],
  184             auth_context=auth_context)
  185         return token