"Fossies" - the Fresh Open Source Software Archive

Member "keystone-17.0.0/keystone/auth/plugins/core.py" (13 May 2020, 9800 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 "core.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 # Copyright 2013 OpenStack Foundation
    2 #
    3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
    4 # not use this file except in compliance with the License. You may obtain
    5 # a copy of the License at
    6 #
    7 #      http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 # Unless required by applicable law or agreed to in writing, software
   10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 # License for the specific language governing permissions and limitations
   13 # under the License.
   14 
   15 from oslo_log import log
   16 from pycadf import cadftaxonomy as taxonomy
   17 from pycadf import reason
   18 from pycadf import resource
   19 
   20 from keystone.common import driver_hints
   21 from keystone.common import provider_api
   22 import keystone.conf
   23 from keystone import exception
   24 from keystone import notifications
   25 
   26 
   27 CONF = keystone.conf.CONF
   28 LOG = log.getLogger(__name__)
   29 PROVIDERS = provider_api.ProviderAPIs
   30 _NOTIFY_OP = 'authenticate'
   31 _NOTIFY_EVENT = '{service}.{event}'.format(service=notifications.SERVICE,
   32                                            event=_NOTIFY_OP)
   33 
   34 
   35 def construct_method_map_from_config():
   36     """Determine authentication method types for deployment.
   37 
   38     :returns: a dictionary containing the methods and their indexes
   39 
   40     """
   41     method_map = dict()
   42     method_index = 1
   43     for method in CONF.auth.methods:
   44         method_map[method_index] = method
   45         method_index = method_index * 2
   46 
   47     return method_map
   48 
   49 
   50 def convert_method_list_to_integer(methods):
   51     """Convert the method type(s) to an integer.
   52 
   53     :param methods: a list of method names
   54     :returns: an integer representing the methods
   55 
   56     """
   57     method_map = construct_method_map_from_config()
   58 
   59     method_ints = []
   60     for method in methods:
   61         for k, v in method_map.items():
   62             if v == method:
   63                 method_ints.append(k)
   64     return sum(method_ints)
   65 
   66 
   67 def convert_integer_to_method_list(method_int):
   68     """Convert an integer to a list of methods.
   69 
   70     :param method_int: an integer representing methods
   71     :returns: a corresponding list of methods
   72 
   73     """
   74     # If the method_int is 0 then no methods were used so return an empty
   75     # method list
   76     if method_int == 0:
   77         return []
   78 
   79     method_map = construct_method_map_from_config()
   80     method_ints = sorted(method_map, reverse=True)
   81 
   82     methods = []
   83     for m_int in method_ints:
   84         # (lbragstad): By dividing the method_int by each key in the
   85         # method_map, we know if the division results in an integer of 1, that
   86         # key was used in the construction of the total sum of the method_int.
   87         # In that case, we should confirm the key value and store it so we can
   88         # look it up later. Then we should take the remainder of what is
   89         # confirmed and the method_int and continue the process. In the end, we
   90         # should have a list of integers that correspond to indexes in our
   91         # method_map and we can reinflate the methods that the original
   92         # method_int represents.
   93         result = int(method_int / m_int)
   94         if result == 1:
   95             methods.append(method_map[m_int])
   96             method_int = method_int - m_int
   97 
   98     return methods
   99 
  100 
  101 class BaseUserInfo(provider_api.ProviderAPIMixin, object):
  102 
  103     @classmethod
  104     def create(cls, auth_payload, method_name):
  105         user_auth_info = cls()
  106         user_auth_info._validate_and_normalize_auth_data(auth_payload)
  107         user_auth_info.METHOD_NAME = method_name
  108         return user_auth_info
  109 
  110     def __init__(self):
  111         self.user_id = None
  112         self.user_ref = None
  113         self.METHOD_NAME = None
  114 
  115     def _assert_domain_is_enabled(self, domain_ref):
  116         try:
  117             PROVIDERS.resource_api.assert_domain_enabled(
  118                 domain_id=domain_ref['id'],
  119                 domain=domain_ref)
  120         except AssertionError as e:
  121             LOG.warning(e)
  122             raise exception.Unauthorized from e
  123 
  124     def _assert_user_is_enabled(self, user_ref):
  125         try:
  126             PROVIDERS.identity_api.assert_user_enabled(
  127                 user_id=user_ref['id'],
  128                 user=user_ref)
  129         except AssertionError as e:
  130             LOG.warning(e)
  131             raise exception.Unauthorized from e
  132 
  133     def _lookup_domain(self, domain_info):
  134         domain_id = domain_info.get('id')
  135         domain_name = domain_info.get('name')
  136         if not domain_id and not domain_name:
  137             raise exception.ValidationError(attribute='id or name',
  138                                             target='domain')
  139         try:
  140             if domain_name:
  141                 domain_ref = PROVIDERS.resource_api.get_domain_by_name(
  142                     domain_name)
  143             else:
  144                 domain_ref = PROVIDERS.resource_api.get_domain(domain_id)
  145         except exception.DomainNotFound as e:
  146             LOG.warning(e)
  147             raise exception.Unauthorized(e)
  148         self._assert_domain_is_enabled(domain_ref)
  149         return domain_ref
  150 
  151     def _validate_and_normalize_auth_data(self, auth_payload):
  152         if 'user' not in auth_payload:
  153             raise exception.ValidationError(attribute='user',
  154                                             target=self.METHOD_NAME)
  155         user_info = auth_payload['user']
  156         user_id = user_info.get('id')
  157         user_name = user_info.get('name')
  158         domain_ref = {}
  159         if not user_id and not user_name:
  160             raise exception.ValidationError(attribute='id or name',
  161                                             target='user')
  162         try:
  163             if user_name:
  164                 if 'domain' not in user_info:
  165                     raise exception.ValidationError(attribute='domain',
  166                                                     target='user')
  167                 domain_ref = self._lookup_domain(user_info['domain'])
  168                 user_ref = PROVIDERS.identity_api.get_user_by_name(
  169                     user_name, domain_ref['id'])
  170             else:
  171                 user_ref = PROVIDERS.identity_api.get_user(user_id)
  172                 domain_ref = PROVIDERS.resource_api.get_domain(
  173                     user_ref['domain_id'])
  174                 self._assert_domain_is_enabled(domain_ref)
  175         except exception.UserNotFound as e:
  176             LOG.warning(e)
  177 
  178             # We need to special case USER NOT FOUND here for CADF
  179             # notifications as the normal path for notification(s) come from
  180             # `identity_api.authenticate` and we are a bit before dropping into
  181             # that method.
  182             audit_reason = reason.Reason(str(e), str(e.code))
  183             audit_initiator = notifications.build_audit_initiator()
  184             # build an appropriate audit initiator with relevant information
  185             # for the failed request. This will catch invalid user_name and
  186             # invalid user_id.
  187             if user_name:
  188                 audit_initiator.user_name = user_name
  189             else:
  190                 audit_initiator.user_id = user_id
  191             audit_initiator.domain_id = domain_ref.get('id')
  192             audit_initiator.domain_name = domain_ref.get('name')
  193             notifications._send_audit_notification(
  194                 action=_NOTIFY_OP,
  195                 initiator=audit_initiator,
  196                 outcome=taxonomy.OUTCOME_FAILURE,
  197                 target=resource.Resource(typeURI=taxonomy.ACCOUNT_USER),
  198                 event_type=_NOTIFY_EVENT,
  199                 reason=audit_reason)
  200             raise exception.Unauthorized(e)
  201         self._assert_user_is_enabled(user_ref)
  202         self.user_ref = user_ref
  203         self.user_id = user_ref['id']
  204         self.domain_id = domain_ref['id']
  205 
  206 
  207 class UserAuthInfo(BaseUserInfo):
  208 
  209     def __init__(self):
  210         super(UserAuthInfo, self).__init__()
  211         self.password = None
  212 
  213     def _validate_and_normalize_auth_data(self, auth_payload):
  214         super(UserAuthInfo, self)._validate_and_normalize_auth_data(
  215             auth_payload)
  216         user_info = auth_payload['user']
  217         self.password = user_info.get('password')
  218 
  219 
  220 class TOTPUserInfo(BaseUserInfo):
  221 
  222     def __init__(self):
  223         super(TOTPUserInfo, self).__init__()
  224         self.passcode = None
  225 
  226     def _validate_and_normalize_auth_data(self, auth_payload):
  227         super(TOTPUserInfo, self)._validate_and_normalize_auth_data(
  228             auth_payload)
  229         user_info = auth_payload['user']
  230         self.passcode = user_info.get('passcode')
  231 
  232 
  233 class AppCredInfo(BaseUserInfo):
  234     def __init__(self):
  235         super(AppCredInfo, self).__init__()
  236         self.id = None
  237         self.secret = None
  238 
  239     def _validate_and_normalize_auth_data(self, auth_payload):
  240         app_cred_api = PROVIDERS.application_credential_api
  241         if auth_payload.get('id'):
  242             app_cred = app_cred_api.get_application_credential(
  243                 auth_payload['id'])
  244             self.user_id = app_cred['user_id']
  245             if not auth_payload.get('user'):
  246                 auth_payload['user'] = {}
  247                 auth_payload['user']['id'] = self.user_id
  248             super(AppCredInfo, self)._validate_and_normalize_auth_data(
  249                 auth_payload)
  250         elif auth_payload.get('name'):
  251             super(AppCredInfo, self)._validate_and_normalize_auth_data(
  252                 auth_payload)
  253             hints = driver_hints.Hints()
  254             hints.add_filter('name', auth_payload['name'])
  255             app_cred = app_cred_api.list_application_credentials(
  256                 self.user_id, hints)[0]
  257             auth_payload['id'] = app_cred['id']
  258         else:
  259             raise exception.ValidationError(attribute='id or name',
  260                                             target='application credential')
  261         self.id = auth_payload['id']
  262         self.secret = auth_payload.get('secret')