"Fossies" - the Fresh Open Source Software Archive

Member "keystone-19.0.0/keystone/models/revoke_model.py" (14 Apr 2021, 10652 Bytes) of package /linux/misc/openstack/keystone-19.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 "revoke_model.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 18.0.0_vs_19.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 from oslo_log import log
   14 from oslo_serialization import msgpackutils
   15 from oslo_utils import timeutils
   16 
   17 from keystone.common import cache
   18 from keystone.common import utils
   19 
   20 
   21 LOG = log.getLogger(__name__)
   22 
   23 # The set of attributes common between the RevokeEvent
   24 # and the dictionaries created from the token Data.
   25 _NAMES = ['trust_id',
   26           'consumer_id',
   27           'access_token_id',
   28           'audit_id',
   29           'audit_chain_id',
   30           'expires_at',
   31           'domain_id',
   32           'project_id',
   33           'user_id',
   34           'role_id']
   35 
   36 
   37 # Additional arguments for creating a RevokeEvent
   38 _EVENT_ARGS = ['issued_before', 'revoked_at']
   39 
   40 # Names of attributes in the RevocationEvent, including "virtual" attributes.
   41 # Virtual attributes are those added based on other values.
   42 _EVENT_NAMES = _NAMES + ['domain_scope_id']
   43 
   44 # Values that will be in the token data but not in the event.
   45 # These will compared with event values that have different names.
   46 # For example: both trustor_id and trustee_id are compared against user_id
   47 _TOKEN_KEYS = ['identity_domain_id',
   48                'assignment_domain_id',
   49                'issued_at',
   50                'trustor_id',
   51                'trustee_id']
   52 
   53 # Alternative names to be checked in token for every field in
   54 # revoke tree.
   55 ALTERNATIVES = {
   56     'user_id': ['user_id', 'trustor_id', 'trustee_id'],
   57     'domain_id': ['identity_domain_id', 'assignment_domain_id'],
   58     # For a domain-scoped token, the domain is in assignment_domain_id.
   59     'domain_scope_id': ['assignment_domain_id', ],
   60 }
   61 
   62 
   63 REVOKE_KEYS = _NAMES + _EVENT_ARGS
   64 
   65 
   66 def blank_token_data(issued_at):
   67     token_data = dict()
   68     for name in _NAMES:
   69         token_data[name] = None
   70     for name in _TOKEN_KEYS:
   71         token_data[name] = None
   72     # required field
   73     token_data['issued_at'] = issued_at
   74     return token_data
   75 
   76 
   77 class RevokeEvent(object):
   78     def __init__(self, **kwargs):
   79         for k in REVOKE_KEYS:
   80             v = kwargs.get(k)
   81             setattr(self, k, v)
   82 
   83         if self.domain_id and self.expires_at:
   84             # This is revoking a domain-scoped token.
   85             self.domain_scope_id = self.domain_id
   86             self.domain_id = None
   87         else:
   88             # This is revoking all tokens for a domain.
   89             self.domain_scope_id = None
   90 
   91         if self.expires_at is not None:
   92             # Trim off the expiration time because MySQL timestamps are only
   93             # accurate to the second.
   94             self.expires_at = self.expires_at.replace(microsecond=0)
   95 
   96         if self.revoked_at is None:
   97             self.revoked_at = timeutils.utcnow().replace(microsecond=0)
   98         if self.issued_before is None:
   99             self.issued_before = self.revoked_at
  100 
  101     def to_dict(self):
  102         keys = ['user_id',
  103                 'role_id',
  104                 'domain_id',
  105                 'domain_scope_id',
  106                 'project_id',
  107                 'audit_id',
  108                 'audit_chain_id',
  109                 ]
  110         event = {key: self.__dict__[key] for key in keys
  111                  if self.__dict__[key] is not None}
  112         if self.trust_id is not None:
  113             event['OS-TRUST:trust_id'] = self.trust_id
  114         if self.consumer_id is not None:
  115             event['OS-OAUTH1:consumer_id'] = self.consumer_id
  116         if self.access_token_id is not None:
  117             event['OS-OAUTH1:access_token_id'] = self.access_token_id
  118         if self.expires_at is not None:
  119             event['expires_at'] = utils.isotime(self.expires_at)
  120         if self.issued_before is not None:
  121             event['issued_before'] = utils.isotime(self.issued_before,
  122                                                    subsecond=True)
  123         if self.revoked_at is not None:
  124             event['revoked_at'] = utils.isotime(self.revoked_at,
  125                                                 subsecond=True)
  126         return event
  127 
  128 
  129 def is_revoked(events, token_data):
  130     """Check if a token matches a revocation event.
  131 
  132     Compare a token against every revocation event. If the token matches an
  133     event in the `events` list, the token is revoked. If the token is compared
  134     against every item in the list without a match, it is not considered
  135     revoked from the `revoke_api`.
  136 
  137     :param events: a list of RevokeEvent instances
  138     :param token_data: map based on a flattened view of the token. The required
  139                        fields are `expires_at`,`user_id`, `project_id`,
  140                        `identity_domain_id`, `assignment_domain_id`,
  141                        `trust_id`, `trustor_id`, `trustee_id` `consumer_id` and
  142                        `access_token_id`
  143     :returns: True if the token matches an existing revocation event, meaning
  144               the token is revoked. False is returned if the token does not
  145               match any revocation events, meaning the token is considered
  146               valid by the revocation API.
  147     """
  148     return any([matches(e, token_data) for e in events])
  149 
  150 
  151 def matches(event, token_values):
  152     """See if the token matches the revocation event.
  153 
  154     A brute force approach to checking.
  155     Compare each attribute from the event with the corresponding
  156     value from the token.  If the event does not have a value for
  157     the attribute, a match is still possible.  If the event has a
  158     value for the attribute, and it does not match the token, no match
  159     is possible, so skip the remaining checks.
  160 
  161     :param event: a RevokeEvent instance
  162     :param token_values: dictionary with set of values taken from the
  163                          token
  164     :returns: True if the token matches the revocation event, indicating the
  165               token has been revoked
  166     """
  167     # If any one check does not match, the whole token does
  168     # not match the event. The numerous return False indicate
  169     # that the token is still valid and short-circuits the
  170     # rest of the logic.
  171 
  172     # The token has two attributes that can match the domain_id.
  173     if event.domain_id is not None and event.domain_id not in (
  174             token_values['identity_domain_id'],
  175             token_values['assignment_domain_id'],):
  176         return False
  177 
  178     if event.domain_scope_id is not None and event.domain_scope_id not in (
  179             token_values['assignment_domain_id'],):
  180         return False
  181 
  182     # If an event specifies an attribute name, but it does not match, the token
  183     # is not revoked.
  184     if event.expires_at is not None and event.expires_at not in (
  185             token_values['expires_at'],):
  186         return False
  187 
  188     if event.trust_id is not None and event.trust_id not in (
  189             token_values['trust_id'],):
  190         return False
  191 
  192     if event.consumer_id is not None and event.consumer_id not in (
  193             token_values['consumer_id'],):
  194         return False
  195 
  196     if event.audit_chain_id is not None and event.audit_chain_id not in (
  197             token_values['audit_chain_id'],):
  198         return False
  199 
  200     if event.role_id is not None and event.role_id not in (
  201             token_values['roles']):
  202         return False
  203 
  204     return True
  205 
  206 
  207 def build_token_values(token):
  208 
  209     token_expires_at = timeutils.parse_isotime(token.expires_at)
  210 
  211     # Trim off the microseconds because the revocation event only has
  212     # expirations accurate to the second.
  213     token_expires_at = token_expires_at.replace(microsecond=0)
  214 
  215     token_values = {
  216         'expires_at': timeutils.normalize_time(token_expires_at),
  217         'issued_at': timeutils.normalize_time(
  218             timeutils.parse_isotime(token.issued_at)),
  219         'audit_id': token.audit_id,
  220         'audit_chain_id': token.parent_audit_id,
  221     }
  222 
  223     if token.user_id is not None:
  224         token_values['user_id'] = token.user_id
  225         # Federated users do not have a domain, be defensive and get the user
  226         # domain set to None in the federated user case.
  227         token_values['identity_domain_id'] = token.user_domain['id']
  228     else:
  229         token_values['user_id'] = None
  230         token_values['identity_domain_id'] = None
  231 
  232     if token.project_id is not None:
  233         token_values['project_id'] = token.project_id
  234         # The domain_id of projects acting as domains is None
  235         token_values['assignment_domain_id'] = token.project_domain['id']
  236     else:
  237         token_values['project_id'] = None
  238 
  239     if token.domain_id is not None:
  240         token_values['assignment_domain_id'] = token.domain_id
  241     else:
  242         token_values['assignment_domain_id'] = None
  243 
  244     role_list = []
  245     if token.roles is not None:
  246         for role in token.roles:
  247             role_list.append(role['id'])
  248     token_values['roles'] = role_list
  249 
  250     if token.trust_scoped:
  251         token_values['trust_id'] = token.trust['id']
  252         token_values['trustor_id'] = token.trustor['id']
  253         token_values['trustee_id'] = token.trustee['id']
  254     else:
  255         token_values['trust_id'] = None
  256         token_values['trustor_id'] = None
  257         token_values['trustee_id'] = None
  258 
  259     if token.oauth_scoped:
  260         token_values['consumer_id'] = token.access_token['consumer_id']
  261         token_values['access_token_id'] = token.access_token['id']
  262     else:
  263         token_values['consumer_id'] = None
  264         token_values['access_token_id'] = None
  265 
  266     return token_values
  267 
  268 
  269 class _RevokeEventHandler(object):
  270     # NOTE(morganfainberg): There needs to be reserved "registry" entries set
  271     # in oslo_serialization for application-specific handlers. We picked 127
  272     # here since it's waaaaaay far out before oslo_serialization will use it.
  273     identity = 127
  274     handles = (RevokeEvent,)
  275 
  276     def __init__(self, registry):
  277         self._registry = registry
  278 
  279     def serialize(self, obj):
  280         return msgpackutils.dumps(obj.__dict__, registry=self._registry)
  281 
  282     def deserialize(self, data):
  283         revoke_event_data = msgpackutils.loads(data, registry=self._registry)
  284         try:
  285             revoke_event = RevokeEvent(**revoke_event_data)
  286         except Exception:
  287             LOG.debug("Failed to deserialize RevokeEvent. Data is %s",
  288                       revoke_event_data)
  289             raise
  290         return revoke_event
  291 
  292 
  293 cache.register_model_handler(_RevokeEventHandler)