keystone/keystone/models/receipt_model.py
Adrian Turjak d9e6c1d4dd Implement auth receipts spec
Adds a new model and provider for receipts which are
very similar to tokens (fernet based), and share the
same fernet mechanisms.

Adds changes to the auth layer to handle the creation,
validation, and consumptions of receipts as part of
the auth process.

Change-Id: Iccb6e6fc7aee57c58a53f90c1d671402b8efcdbb
bp: mfa-auth-receipt
2018-11-02 15:06:19 +01:00

151 lines
4.8 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Unified in-memory receipt model."""
from oslo_log import log
from oslo_serialization import msgpackutils
from oslo_utils import reflection
import six
from keystone.auth import core
from keystone.common import cache
from keystone.common import provider_api
from keystone import exception
from keystone.identity.backends import resource_options as ro
LOG = log.getLogger(__name__)
PROVIDERS = provider_api.ProviderAPIs
class ReceiptModel(object):
"""An object that represents a receipt emitted by keystone.
This is a queryable object that other parts of keystone can use to reason
about a user's receipt.
"""
def __init__(self):
self.user_id = None
self.__user = None
self.__user_domain = None
self.methods = None
self.__required_methods = None
self.__expires_at = None
self.__issued_at = None
def __repr__(self):
"""Return string representation of KeystoneReceipt."""
desc = ('<%(type)s at %(loc)s>')
self_cls_name = reflection.get_class_name(self, fully_qualified=False)
return desc % {'type': self_cls_name, 'loc': hex(id(self))}
@property
def expires_at(self):
return self.__expires_at
@expires_at.setter
def expires_at(self, value):
if not isinstance(value, six.string_types):
raise ValueError('expires_at must be a string.')
self.__expires_at = value
@property
def issued_at(self):
return self.__issued_at
@issued_at.setter
def issued_at(self, value):
if not isinstance(value, six.string_types):
raise ValueError('issued_at must be a string.')
self.__issued_at = value
@property
def user(self):
if not self.__user:
if self.user_id:
self.__user = PROVIDERS.identity_api.get_user(self.user_id)
return self.__user
@property
def user_domain(self):
if not self.__user_domain:
if self.user:
self.__user_domain = PROVIDERS.resource_api.get_domain(
self.user['domain_id']
)
return self.__user_domain
@property
def required_methods(self):
if not self.__required_methods:
mfa_rules = self.user['options'].get(
ro.MFA_RULES_OPT.option_name, [])
rules = core.UserMFARulesValidator._parse_rule_structure(
mfa_rules, self.user_id)
methods = set(self.methods)
active_methods = set(core.AUTH_METHODS.keys())
required_auth_methods = []
for r in rules:
r_set = set(r).intersection(active_methods)
if r_set.intersection(methods):
required_auth_methods.append(list(r_set))
self.__required_methods = required_auth_methods
return self.__required_methods
def mint(self, receipt_id, issued_at):
"""Set the ``id`` and ``issued_at`` attributes of a receipt.
The process of building a Receipt requires setting attributes about the
partial authentication context, like ``user_id`` and ``methods`` for
example. Once a Receipt object accurately represents this information
it should be "minted". Receipt are minted when they get an ``id``
attribute and their creation time is recorded.
"""
self.id = receipt_id
self.issued_at = issued_at
class _ReceiptModelHandler(object):
identity = 125
handles = (ReceiptModel,)
def __init__(self, registry):
self._registry = registry
def serialize(self, obj):
serialized = msgpackutils.dumps(obj.__dict__, registry=self._registry)
return serialized
def deserialize(self, data):
receipt_data = msgpackutils.loads(data, registry=self._registry)
try:
receipt_model = ReceiptModel()
for k, v in iter(receipt_data.items()):
setattr(receipt_model, k, v)
except Exception:
LOG.debug(
"Failed to deserialize ReceiptModel. Data is %s", receipt_data
)
raise exception.CacheDeserializationError(
ReceiptModel.__name__, receipt_data
)
return receipt_model
cache.register_model_handler(_ReceiptModelHandler)