OpenStack Keystone 源码解析

# 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.

import os

from keystone.common import dependency
from keystone.common import utils as ks_utils
import keystone.conf
from keystone.federation import constants as federation_constants
from keystone.i18n import _
from keystone.token.providers import common
from keystone.token.providers.fernet import token_formatters as tf

""" 引入keystone配置 """
CONF = keystone.conf.CONF

""" 定义依赖API需求trust-oauth-identity三方API """

@dependency.requires('trust_api', 'oauth_api', 'identity_api')
class Provider(common.BaseProvider):
    __init__()

    """ 方法意义重大的原因有两个。第一个原因是在对象生命周期中初始化是最重要的一步; 每个对象必须正确初始化后才能正常工作。第二个原因是__init__() 参数值可以有多种形式。 """

    def __init__(self, *args, **kwargs):
        super(Provider, self).__init__(*args, **kwargs)

        # NOTE(lbragstad): We add these checks here because if the fernet
        # provider is going to be used and either the `key_repository` is empty
        # or doesn't exist we should fail, hard. It doesn't make sense to start
        # keystone and just 500 because we can't do anything with an empty or
        # non-existant key repository.
        if not os.path.exists(CONF.fernet_tokens.key_repository):
            # 配置中fernet_tokens.key_repository用来提供加密的密钥生成库
            subs = {'key_repo': CONF.fernet_tokens.key_repository}
            raise SystemExit(_('%(key_repo)s does not exist') % subs)
        if not os.listdir(CONF.fernet_tokens.key_repository):
            subs = {'key_repo': CONF.fernet_tokens.key_repository}
            # 配置中fernet_tokens.key_repository没有生成初始密钥
            raise SystemExit(_('%(key_repo)s does not contain keys, use '
                               'keystone-manage fernet_setup to create '
                               'Fernet keys.') % subs)

        self.token_formatter = tf.TokenFormatter()

    """ 返回是否需要持久化,使用fernet生成token不需要实例化 """

    def needs_persistence(self):
        """Should the token be written to a backend."""
        """ 使用Fernet加密信息作为token不需要实例化,所以返回fasle """
        return False

    """ v2接口调用拿到生成后的token进行id标记 """

    def issue_v2_token(self, *args, **kwargs):
        token_id, token_data = super(Provider, self).issue_v2_token(
            *args, **kwargs)
        self._build_issued_at_info(token_id, token_data)
        return token_id, token_data

    """ v3接口调用拿到生成后的token进行id标记 """

    def issue_v3_token(self, *args, **kwargs):
        token_id, token_data = super(Provider, self).issue_v3_token(
            *args, **kwargs)
        self._build_issued_at_info(token_id, token_data)
        return token_id, token_data

    """ 为token添加issue_at时间戳 """

    def _build_issued_at_info(self, token_id, token_data):
        # NOTE(roxanaghe, lbragstad): We must use the creation time that
        # Fernet builds into it's token. The Fernet spec details that the
        # token creation time is built into the token, outside of the payload
        # provided by Keystone. This is the reason why we don't pass the
        # issued_at time in the payload. This also means that we shouldn't
        # return a token reference with a creation time that we created
        # when Fernet uses a different creation time. We should use the
        # creation time provided by Fernet because it's the creation time
        # that we have to rely on when we validate the token.
        fernet_creation_datetime_obj = self.token_formatter.creation_time(
            token_id)
        if token_data.get('access'):
            token_data['access']['token']['issued_at'] = ks_utils.isotime(
                at=fernet_creation_datetime_obj, subsecond=True)
        else:
            token_data['token']['issued_at'] = ks_utils.isotime(
                at=fernet_creation_datetime_obj, subsecond=True)

    """ 根据federation_constants配置添加identity_provider与protocol信息 """

    def _build_federated_info(self, token_data):
        """Extract everything needed for federated tokens.

        This dictionary is passed to federated token formatters, which unpack
        the values and build federated Fernet tokens.

        """
        token_data = token_data['token']
        try:
            user = token_data['user']
            federation = user[federation_constants.FEDERATION]
            idp_id = federation['identity_provider']['id']
            protocol_id = federation['protocol']['id']
        except KeyError:
            # The token data doesn't have federated info, so we aren't dealing
            # with a federated token and no federated info to build.
            return

        group_ids = federation.get('groups')

        return {'group_ids': group_ids,
                'idp_id': idp_id,
                'protocol_id': protocol_id}

    """ 在token信息传入V3TokenDataHelper之前进一步格式化 """
    def _rebuild_federated_info(self, federated_dict, user_id):
        """ Format federated information into the token reference.
        The federated_dict is passed back from the federated token formatters.
        The responsibility of this method is to format the information passed
        back from the token formatter into the token reference before
        constructing the token data from the V3TokenDataHelper.

        """
        g_ids = federated_dict['group_ids']
        idp_id = federated_dict['idp_id']
        protocol_id = federated_dict['protocol_id']

        federated_info = {
            'groups': g_ids,
            'identity_provider': {'id': idp_id},
            'protocol': {'id': protocol_id}
        }

        user_dict = self.identity_api.get_user(user_id)
        user_name = user_dict['name']

        token_dict = {
            'user': {
                federation_constants.FEDERATION: federated_info,
                'id': user_id,
                'name': user_name,
                'domain': {'id': CONF.federation.federated_domain_name,
                           'name': CONF.federation.federated_domain_name, },
            }
        }

        return token_dict

    """ 如果联邦认证使用的IDP没有提供角色信息,这里进行额外的角色信息补充"""
    def _rebuild_federated_token_roles(self, token_dict, federated_dict,
                                       user_id, project_id, domain_id):
        """Populate roles based on (groups, project/domain) pair.

        We must populate roles from (groups, project/domain) as ephemeral users
        don't exist in the backend. Upon success, a ``roles`` key will be added
        to ``token_dict``.

        :param token_dict: dictionary with data used for building token
        :param federated_dict: federated information such as identity provider
            protocol and set of group IDs
        :param user_id: user ID
        :param project_id: project ID the token is being scoped to
        :param domain_id: domain ID the token is being scoped to

        """
        group_ids = [x['id'] for x in federated_dict['group_ids']]
        self.v3_token_data_helper.populate_roles_for_federated_user(
            token_dict, group_ids, project_id, domain_id, user_id)

    """ 为v2接口生成额外的token的信息   """

    def _extract_v2_token_data(self, token_data):
        user_id = token_data['access']['user']['id']
        expires_at = token_data['access']['token']['expires']
        audit_ids = token_data['access']['token'].get('audit_ids')
        methods = ['password']
        if len(audit_ids) > 1:
            methods.append('token')
        project_id = token_data['access']['token'].get('tenant', {}).get('id')
        domain_id = None
        trust_id = token_data['access'].get('trust', {}).get('id')
        access_token_id = None
        federated_info = None
        return (user_id, expires_at, audit_ids, methods, domain_id, project_id,
                trust_id, access_token_id, federated_info)

    """ 为v3接口生成额外的token的信息 """

    def _extract_v3_token_data(self, token_data):
        """Extract information from a v3 token reference."""
        user_id = token_data['token']['user']['id']
        expires_at = token_data['token']['expires_at']
        audit_ids = token_data['token']['audit_ids']
        methods = token_data['token'].get('methods')
        domain_id = token_data['token'].get('domain', {}).get('id')
        project_id = token_data['token'].get('project', {}).get('id')
        trust_id = token_data['token'].get('OS-TRUST:trust', {}).get('id')
        access_token_id = token_data['token'].get('OS-OAUTH1', {}).get(
            'access_token_id')
        federated_info = self._build_federated_info(token_data)

        return (user_id, expires_at, audit_ids, methods, domain_id, project_id,
                trust_id, access_token_id, federated_info)

    """ 生成token_data的tokenID """

    def _get_token_id(self, token_data):
        """Generate the token_id based upon the data in token_data.

        :param token_data: token information
        :type token_data: dict
        :rtype: six.text_type

        """
        # NOTE(lbragstad): Only v2.0 token responses include an 'access'
        # attribute.
        # 只有v2接口调用返回字段中才有access字段
        if token_data.get('access'):
            (user_id, expires_at, audit_ids, methods, domain_id, project_id,
             trust_id, access_token_id, federated_info) = (
                self._extract_v2_token_data(token_data))
        # 针对v3接口调用返回字段中没有access字段
        else:
            (user_id, expires_at, audit_ids, methods, domain_id, project_id,
             trust_id, access_token_id, federated_info) = (
                # 返回token信息的额外字段
                self._extract_v3_token_data(token_data))

        # 使用token_formatter格式化返回信息
        return self.token_formatter.create_token(
            user_id,
            expires_at,
            audit_ids,
            methods=methods,
            domain_id=domain_id,
            project_id=project_id,
            trust_id=trust_id,
            federated_info=federated_info,
            access_token_id=access_token_id
        )

    """ 装饰器(decorator)可以给函数动态加上功能,对于类的方法,装饰器一样起作用。Python内置的 @ property装饰器就是负责把一个方法变成属性调用的"""

    @property
    def _supports_bind_authentication(self):
        """Return if the token provider supports bind authentication methods.

         :returns: False

         """
        return False

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值