diff options
Diffstat (limited to 'keystone-moon/keystone/contrib')
53 files changed, 1091 insertions, 1694 deletions
diff --git a/keystone-moon/keystone/contrib/ec2/controllers.py b/keystone-moon/keystone/contrib/ec2/controllers.py index 6e6d3268..78172ec9 100644 --- a/keystone-moon/keystone/contrib/ec2/controllers.py +++ b/keystone-moon/keystone/contrib/ec2/controllers.py @@ -46,7 +46,6 @@ from keystone.common import utils from keystone.common import wsgi from keystone import exception from keystone.i18n import _ -from keystone.models import token_model @dependency.requires('assignment_api', 'catalog_api', 'credential_api', @@ -57,16 +56,30 @@ class Ec2ControllerCommon(object): def check_signature(self, creds_ref, credentials): signer = ec2_utils.Ec2Signer(creds_ref['secret']) signature = signer.generate(credentials) - if utils.auth_str_equal(credentials['signature'], signature): - return - # NOTE(vish): Some libraries don't use the port when signing - # requests, so try again without port. - elif ':' in credentials['signature']: - hostname, _port = credentials['host'].split(':') - credentials['host'] = hostname - signature = signer.generate(credentials) - if not utils.auth_str_equal(credentials.signature, signature): - raise exception.Unauthorized(message='Invalid EC2 signature.') + # NOTE(davechen): credentials.get('signature') is not guaranteed to + # exist, we need check it explicitly. + if credentials.get('signature'): + if utils.auth_str_equal(credentials['signature'], signature): + return True + # NOTE(vish): Some client libraries don't use the port when signing + # requests, so try again without port. + elif ':' in credentials['host']: + hostname, _port = credentials['host'].split(':') + credentials['host'] = hostname + # NOTE(davechen): we need reinitialize 'signer' to avoid + # contaminated status of signature, this is similar with + # other programming language libraries, JAVA for example. + signer = ec2_utils.Ec2Signer(creds_ref['secret']) + signature = signer.generate(credentials) + if utils.auth_str_equal(credentials['signature'], + signature): + return True + raise exception.Unauthorized( + message='Invalid EC2 signature.') + else: + raise exception.Unauthorized( + message='EC2 signature not supplied.') + # Raise the exception when credentials.get('signature') is None else: raise exception.Unauthorized(message='EC2 signature not supplied.') @@ -305,14 +318,7 @@ class Ec2Controller(Ec2ControllerCommon, controller.V2Controller): :raises exception.Forbidden: when token is invalid """ - try: - token_data = self.token_provider_api.validate_token( - context['token_id']) - except exception.TokenNotFound as e: - raise exception.Unauthorized(e) - - token_ref = token_model.KeystoneToken(token_id=context['token_id'], - token_data=token_data) + token_ref = utils.get_token_ref(context) if token_ref.user_id != user_id: raise exception.Forbidden(_('Token belongs to another user')) @@ -329,7 +335,7 @@ class Ec2Controller(Ec2ControllerCommon, controller.V2Controller): # to properly perform policy enforcement. self.assert_admin(context) return True - except exception.Forbidden: + except (exception.Forbidden, exception.Unauthorized): return False def _assert_owner(self, user_id, credential_id): @@ -349,11 +355,11 @@ class Ec2Controller(Ec2ControllerCommon, controller.V2Controller): @dependency.requires('policy_api', 'token_provider_api') class Ec2ControllerV3(Ec2ControllerCommon, controller.V3Controller): - member_name = 'project' + collection_name = 'credentials' + member_name = 'credential' def __init__(self): super(Ec2ControllerV3, self).__init__() - self.get_member_from_driver = self.credential_api.get_credential def _check_credential_owner_and_user_id_match(self, context, prep_info, user_id, credential_id): @@ -385,23 +391,35 @@ class Ec2ControllerV3(Ec2ControllerCommon, controller.V3Controller): @controller.protected(callback=_check_credential_owner_and_user_id_match) def ec2_get_credential(self, context, user_id, credential_id): - return super(Ec2ControllerV3, self).get_credential(user_id, - credential_id) + ref = super(Ec2ControllerV3, self).get_credential(user_id, + credential_id) + return Ec2ControllerV3.wrap_member(context, ref['credential']) @controller.protected() def ec2_list_credentials(self, context, user_id): - return super(Ec2ControllerV3, self).get_credentials(user_id) + refs = super(Ec2ControllerV3, self).get_credentials(user_id) + return Ec2ControllerV3.wrap_collection(context, refs['credentials']) @controller.protected() def ec2_create_credential(self, context, user_id, tenant_id): - return super(Ec2ControllerV3, self).create_credential(context, user_id, - tenant_id) + ref = super(Ec2ControllerV3, self).create_credential(context, user_id, + tenant_id) + return Ec2ControllerV3.wrap_member(context, ref['credential']) @controller.protected(callback=_check_credential_owner_and_user_id_match) def ec2_delete_credential(self, context, user_id, credential_id): return super(Ec2ControllerV3, self).delete_credential(user_id, credential_id) + @classmethod + def _add_self_referential_link(cls, context, ref): + path = '/users/%(user_id)s/credentials/OS-EC2/%(credential_id)s' + url = cls.base_url(context, path) % { + 'user_id': ref['user_id'], + 'credential_id': ref['access']} + ref.setdefault('links', {}) + ref['links']['self'] = url + def render_token_data_response(token_id, token_data): """Render token data HTTP response. diff --git a/keystone-moon/keystone/contrib/endpoint_filter/backends/catalog_sql.py b/keystone-moon/keystone/contrib/endpoint_filter/backends/catalog_sql.py index 6ac3c1ca..22d5796a 100644 --- a/keystone-moon/keystone/contrib/endpoint_filter/backends/catalog_sql.py +++ b/keystone-moon/keystone/contrib/endpoint_filter/backends/catalog_sql.py @@ -13,20 +13,20 @@ # under the License. from oslo_config import cfg -import six from keystone.catalog.backends import sql from keystone.catalog import core as catalog_core from keystone.common import dependency from keystone import exception + CONF = cfg.CONF @dependency.requires('endpoint_filter_api') class EndpointFilterCatalog(sql.Catalog): def get_v3_catalog(self, user_id, project_id): - substitutions = dict(six.iteritems(CONF)) + substitutions = dict(CONF.items()) substitutions.update({'tenant_id': project_id, 'user_id': user_id}) services = {} @@ -66,7 +66,7 @@ class EndpointFilterCatalog(sql.Catalog): # format catalog catalog = [] - for service_id, service in six.iteritems(services): + for service_id, service in services.items(): formatted_service = {} formatted_service['id'] = service['id'] formatted_service['type'] = service['type'] diff --git a/keystone-moon/keystone/contrib/endpoint_filter/backends/sql.py b/keystone-moon/keystone/contrib/endpoint_filter/backends/sql.py index a998423f..53d511e5 100644 --- a/keystone-moon/keystone/contrib/endpoint_filter/backends/sql.py +++ b/keystone-moon/keystone/contrib/endpoint_filter/backends/sql.py @@ -13,6 +13,7 @@ # under the License. from keystone.common import sql +from keystone.contrib import endpoint_filter from keystone import exception from keystone.i18n import _ @@ -52,7 +53,7 @@ class ProjectEndpointGroupMembership(sql.ModelBase, sql.ModelDictMixin): 'project_id'), {}) -class EndpointFilter(object): +class EndpointFilter(endpoint_filter.Driver): @sql.handle_conflicts(conflict_type='project_endpoint') def add_endpoint_to_project(self, endpoint_id, project_id): @@ -150,9 +151,9 @@ class EndpointFilter(object): endpoint_group_ref = self._get_endpoint_group(session, endpoint_group_id) with session.begin(): - session.delete(endpoint_group_ref) self._delete_endpoint_group_association_by_endpoint_group( session, endpoint_group_id) + session.delete(endpoint_group_ref) def get_endpoint_group_in_project(self, endpoint_group_id, project_id): session = sql.get_session() diff --git a/keystone-moon/keystone/contrib/endpoint_filter/controllers.py b/keystone-moon/keystone/contrib/endpoint_filter/controllers.py index dc4ef7a3..eb627c6b 100644 --- a/keystone-moon/keystone/contrib/endpoint_filter/controllers.py +++ b/keystone-moon/keystone/contrib/endpoint_filter/controllers.py @@ -49,7 +49,7 @@ class _ControllerBase(controller.V3Controller): for endpoint in endpoints: is_candidate = True - for key, value in six.iteritems(filters): + for key, value in filters.items(): if endpoint[key] != value: is_candidate = False break diff --git a/keystone-moon/keystone/contrib/endpoint_filter/core.py b/keystone-moon/keystone/contrib/endpoint_filter/core.py index 972b65dd..1cb35b1f 100644 --- a/keystone-moon/keystone/contrib/endpoint_filter/core.py +++ b/keystone-moon/keystone/contrib/endpoint_filter/core.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +"""Main entry point into the Endpoint Filter service.""" + import abc from oslo_config import cfg @@ -56,6 +58,8 @@ class Manager(manager.Manager): """ + driver_namespace = 'keystone.endpoint_filter' + def __init__(self): super(Manager, self).__init__(CONF.endpoint_filter.driver) diff --git a/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/001_add_endpoint_filtering_table.py b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/001_add_endpoint_filtering_table.py index 090e7f47..2aa93a86 100644 --- a/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/001_add_endpoint_filtering_table.py +++ b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/001_add_endpoint_filtering_table.py @@ -36,12 +36,3 @@ def upgrade(migrate_engine): nullable=False)) endpoint_filtering_table.create(migrate_engine, checkfirst=True) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - # Operations to reverse the above upgrade go here. - for table_name in ['project_endpoint']: - table = sql.Table(table_name, meta, autoload=True) - table.drop() diff --git a/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/002_add_endpoint_groups.py b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/002_add_endpoint_groups.py index 5f80160a..2c218b0d 100644 --- a/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/002_add_endpoint_groups.py +++ b/keystone-moon/keystone/contrib/endpoint_filter/migrate_repo/versions/002_add_endpoint_groups.py @@ -39,13 +39,3 @@ def upgrade(migrate_engine): sql.PrimaryKeyConstraint('endpoint_group_id', 'project_id')) project_endpoint_group_table.create(migrate_engine, checkfirst=True) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - # Operations to reverse the above upgrade go here. - for table_name in ['project_endpoint_group', - 'endpoint_group']: - table = sql.Table(table_name, meta, autoload=True) - table.drop() diff --git a/keystone-moon/keystone/contrib/endpoint_filter/routers.py b/keystone-moon/keystone/contrib/endpoint_filter/routers.py index 00c8cd72..285b9df2 100644 --- a/keystone-moon/keystone/contrib/endpoint_filter/routers.py +++ b/keystone-moon/keystone/contrib/endpoint_filter/routers.py @@ -36,28 +36,32 @@ class EndpointFilterExtension(wsgi.V3ExtensionRouter): The API looks like:: - PUT /OS-EP-FILTER/projects/$project_id/endpoints/$endpoint_id - GET /OS-EP-FILTER/projects/$project_id/endpoints/$endpoint_id - HEAD /OS-EP-FILTER/projects/$project_id/endpoints/$endpoint_id - DELETE /OS-EP-FILTER/projects/$project_id/endpoints/$endpoint_id - GET /OS-EP-FILTER/endpoints/$endpoint_id/projects - GET /OS-EP-FILTER/projects/$project_id/endpoints + PUT /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} + GET /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} + HEAD /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} + DELETE /OS-EP-FILTER/projects/{project_id}/endpoints/{endpoint_id} + GET /OS-EP-FILTER/endpoints/{endpoint_id}/projects + GET /OS-EP-FILTER/projects/{project_id}/endpoints + GET /OS-EP-FILTER/projects/{project_id}/endpoint_groups GET /OS-EP-FILTER/endpoint_groups POST /OS-EP-FILTER/endpoint_groups - GET /OS-EP-FILTER/endpoint_groups/$endpoint_group_id - HEAD /OS-EP-FILTER/endpoint_groups/$endpoint_group_id - PATCH /OS-EP-FILTER/endpoint_groups/$endpoint_group_id - DELETE /OS-EP-FILTER/endpoint_groups/$endpoint_group_id - - GET /OS-EP-FILTER/endpoint_groups/$endpoint_group_id/projects - GET /OS-EP-FILTER/endpoint_groups/$endpoint_group_id/endpoints - - PUT /OS-EP-FILTER/endpoint_groups/$endpoint_group/projects/$project_id - GET /OS-EP-FILTER/endpoint_groups/$endpoint_group/projects/$project_id - HEAD /OS-EP-FILTER/endpoint_groups/$endpoint_group/projects/$project_id - DELETE /OS-EP-FILTER/endpoint_groups/$endpoint_group/projects/ - $project_id + GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} + HEAD /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} + PATCH /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} + DELETE /OS-EP-FILTER/endpoint_groups/{endpoint_group_id} + + GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/projects + GET /OS-EP-FILTER/endpoint_groups/{endpoint_group_id}/endpoints + + PUT /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/ + {project_id} + GET /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/ + {project_id} + HEAD /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/ + {project_id} + DELETE /OS-EP-FILTER/endpoint_groups/{endpoint_group}/projects/ + {project_id} """ PATH_PREFIX = '/OS-EP-FILTER' @@ -101,6 +105,15 @@ class EndpointFilterExtension(wsgi.V3ExtensionRouter): }) self._add_resource( mapper, endpoint_group_controller, + path=self.PATH_PREFIX + '/projects/{project_id}/endpoint_groups', + get_action='list_endpoint_groups_for_project', + rel=build_resource_relation( + resource_name='project_endpoint_groups'), + path_vars={ + 'project_id': json_home.Parameters.PROJECT_ID, + }) + self._add_resource( + mapper, endpoint_group_controller, path=self.PATH_PREFIX + '/endpoint_groups', get_action='list_endpoint_groups', post_action='create_endpoint_group', diff --git a/keystone-moon/keystone/contrib/endpoint_policy/__init__.py b/keystone-moon/keystone/contrib/endpoint_policy/__init__.py index 12722dc5..e69de29b 100644 --- a/keystone-moon/keystone/contrib/endpoint_policy/__init__.py +++ b/keystone-moon/keystone/contrib/endpoint_policy/__init__.py @@ -1,15 +0,0 @@ -# Copyright 2014 IBM Corp. -# -# 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. - -from keystone.contrib.endpoint_policy.core import * # noqa diff --git a/keystone-moon/keystone/contrib/endpoint_policy/backends/sql.py b/keystone-moon/keystone/contrib/endpoint_policy/backends/sql.py index 484444f1..54792f30 100644 --- a/keystone-moon/keystone/contrib/endpoint_policy/backends/sql.py +++ b/keystone-moon/keystone/contrib/endpoint_policy/backends/sql.py @@ -1,5 +1,3 @@ -# Copyright 2014 IBM Corp. -# # 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 @@ -12,129 +10,23 @@ # License for the specific language governing permissions and limitations # under the License. -import uuid - -import sqlalchemy - -from keystone.common import sql -from keystone import exception - - -class PolicyAssociation(sql.ModelBase, sql.ModelDictMixin): - __tablename__ = 'policy_association' - attributes = ['policy_id', 'endpoint_id', 'region_id', 'service_id'] - # The id column is never exposed outside this module. It only exists to - # provide a primary key, given that the real columns we would like to use - # (endpoint_id, service_id, region_id) can be null - id = sql.Column(sql.String(64), primary_key=True) - policy_id = sql.Column(sql.String(64), nullable=False) - endpoint_id = sql.Column(sql.String(64), nullable=True) - service_id = sql.Column(sql.String(64), nullable=True) - region_id = sql.Column(sql.String(64), nullable=True) - __table_args__ = (sql.UniqueConstraint('endpoint_id', 'service_id', - 'region_id'), {}) - - def to_dict(self): - """Returns the model's attributes as a dictionary. - - We override the standard method in order to hide the id column, - since this only exists to provide the table with a primary key. - - """ - d = {} - for attr in self.__class__.attributes: - d[attr] = getattr(self, attr) - return d - - -class EndpointPolicy(object): - - def create_policy_association(self, policy_id, endpoint_id=None, - service_id=None, region_id=None): - with sql.transaction() as session: - try: - # See if there is already a row for this association, and if - # so, update it with the new policy_id - query = session.query(PolicyAssociation) - query = query.filter_by(endpoint_id=endpoint_id) - query = query.filter_by(service_id=service_id) - query = query.filter_by(region_id=region_id) - association = query.one() - association.policy_id = policy_id - except sql.NotFound: - association = PolicyAssociation(id=uuid.uuid4().hex, - policy_id=policy_id, - endpoint_id=endpoint_id, - service_id=service_id, - region_id=region_id) - session.add(association) - - def check_policy_association(self, policy_id, endpoint_id=None, - service_id=None, region_id=None): - sql_constraints = sqlalchemy.and_( - PolicyAssociation.policy_id == policy_id, - PolicyAssociation.endpoint_id == endpoint_id, - PolicyAssociation.service_id == service_id, - PolicyAssociation.region_id == region_id) - - # NOTE(henry-nash): Getting a single value to save object - # management overhead. - with sql.transaction() as session: - if session.query(PolicyAssociation.id).filter( - sql_constraints).distinct().count() == 0: - raise exception.PolicyAssociationNotFound() - - def delete_policy_association(self, policy_id, endpoint_id=None, - service_id=None, region_id=None): - with sql.transaction() as session: - query = session.query(PolicyAssociation) - query = query.filter_by(policy_id=policy_id) - query = query.filter_by(endpoint_id=endpoint_id) - query = query.filter_by(service_id=service_id) - query = query.filter_by(region_id=region_id) - query.delete() +import logging - def get_policy_association(self, endpoint_id=None, - service_id=None, region_id=None): - sql_constraints = sqlalchemy.and_( - PolicyAssociation.endpoint_id == endpoint_id, - PolicyAssociation.service_id == service_id, - PolicyAssociation.region_id == region_id) +from oslo_log import versionutils - try: - with sql.transaction() as session: - policy_id = session.query(PolicyAssociation.policy_id).filter( - sql_constraints).distinct().one() - return {'policy_id': policy_id} - except sql.NotFound: - raise exception.PolicyAssociationNotFound() +from keystone.endpoint_policy.backends import sql - def list_associations_for_policy(self, policy_id): - with sql.transaction() as session: - query = session.query(PolicyAssociation) - query = query.filter_by(policy_id=policy_id) - return [ref.to_dict() for ref in query.all()] +LOG = logging.getLogger(__name__) - def delete_association_by_endpoint(self, endpoint_id): - with sql.transaction() as session: - query = session.query(PolicyAssociation) - query = query.filter_by(endpoint_id=endpoint_id) - query.delete() +_OLD = 'keystone.contrib.endpoint_policy.backends.sql.EndpointPolicy' +_NEW = 'keystone.endpoint_policy.backends.sql.EndpointPolicy' - def delete_association_by_service(self, service_id): - with sql.transaction() as session: - query = session.query(PolicyAssociation) - query = query.filter_by(service_id=service_id) - query.delete() - def delete_association_by_region(self, region_id): - with sql.transaction() as session: - query = session.query(PolicyAssociation) - query = query.filter_by(region_id=region_id) - query.delete() +class EndpointPolicy(sql.EndpointPolicy): - def delete_association_by_policy(self, policy_id): - with sql.transaction() as session: - query = session.query(PolicyAssociation) - query = query.filter_by(policy_id=policy_id) - query.delete() + @versionutils.deprecated(versionutils.deprecated.LIBERTY, + in_favor_of=_NEW, + remove_in=1, + what=_OLD) + def __init__(self, *args, **kwargs): + super(EndpointPolicy, self).__init__(*args, **kwargs) diff --git a/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/001_add_endpoint_policy_table.py b/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/001_add_endpoint_policy_table.py index c77e4380..5c22f169 100644 --- a/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/001_add_endpoint_policy_table.py +++ b/keystone-moon/keystone/contrib/endpoint_policy/migrate_repo/versions/001_add_endpoint_policy_table.py @@ -38,11 +38,3 @@ def upgrade(migrate_engine): mysql_charset='utf8') endpoint_policy_table.create(migrate_engine, checkfirst=True) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - # Operations to reverse the above upgrade go here. - table = sql.Table('policy_association', meta, autoload=True) - table.drop() diff --git a/keystone-moon/keystone/contrib/endpoint_policy/routers.py b/keystone-moon/keystone/contrib/endpoint_policy/routers.py index 999d1eed..714d1663 100644 --- a/keystone-moon/keystone/contrib/endpoint_policy/routers.py +++ b/keystone-moon/keystone/contrib/endpoint_policy/routers.py @@ -1,5 +1,3 @@ -# Copyright 2014 IBM Corp. -# # 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 @@ -12,74 +10,23 @@ # License for the specific language governing permissions and limitations # under the License. -import functools - -from keystone.common import json_home -from keystone.common import wsgi -from keystone.contrib.endpoint_policy import controllers +import logging +from oslo_log import versionutils -build_resource_relation = functools.partial( - json_home.build_v3_extension_resource_relation, - extension_name='OS-ENDPOINT-POLICY', extension_version='1.0') +from keystone.common import wsgi +LOG = logging.getLogger(__name__) -class EndpointPolicyExtension(wsgi.V3ExtensionRouter): +_OLD = 'keystone.contrib.endpoint_policy.routers.EndpointPolicyExtension' +_NEW = 'keystone.endpoint_policy.routers.Routers' - PATH_PREFIX = '/OS-ENDPOINT-POLICY' - def add_routes(self, mapper): - endpoint_policy_controller = controllers.EndpointPolicyV3Controller() +class EndpointPolicyExtension(wsgi.Middleware): - self._add_resource( - mapper, endpoint_policy_controller, - path='/endpoints/{endpoint_id}' + self.PATH_PREFIX + '/policy', - get_head_action='get_policy_for_endpoint', - rel=build_resource_relation(resource_name='endpoint_policy'), - path_vars={'endpoint_id': json_home.Parameters.ENDPOINT_ID}) - self._add_resource( - mapper, endpoint_policy_controller, - path='/policies/{policy_id}' + self.PATH_PREFIX + '/endpoints', - get_action='list_endpoints_for_policy', - rel=build_resource_relation(resource_name='policy_endpoints'), - path_vars={'policy_id': json_home.Parameters.POLICY_ID}) - self._add_resource( - mapper, endpoint_policy_controller, - path=('/policies/{policy_id}' + self.PATH_PREFIX + - '/endpoints/{endpoint_id}'), - get_head_action='check_policy_association_for_endpoint', - put_action='create_policy_association_for_endpoint', - delete_action='delete_policy_association_for_endpoint', - rel=build_resource_relation( - resource_name='endpoint_policy_association'), - path_vars={ - 'policy_id': json_home.Parameters.POLICY_ID, - 'endpoint_id': json_home.Parameters.ENDPOINT_ID, - }) - self._add_resource( - mapper, endpoint_policy_controller, - path=('/policies/{policy_id}' + self.PATH_PREFIX + - '/services/{service_id}'), - get_head_action='check_policy_association_for_service', - put_action='create_policy_association_for_service', - delete_action='delete_policy_association_for_service', - rel=build_resource_relation( - resource_name='service_policy_association'), - path_vars={ - 'policy_id': json_home.Parameters.POLICY_ID, - 'service_id': json_home.Parameters.SERVICE_ID, - }) - self._add_resource( - mapper, endpoint_policy_controller, - path=('/policies/{policy_id}' + self.PATH_PREFIX + - '/services/{service_id}/regions/{region_id}'), - get_head_action='check_policy_association_for_region_and_service', - put_action='create_policy_association_for_region_and_service', - delete_action='delete_policy_association_for_region_and_service', - rel=build_resource_relation( - resource_name='region_and_service_policy_association'), - path_vars={ - 'policy_id': json_home.Parameters.POLICY_ID, - 'service_id': json_home.Parameters.SERVICE_ID, - 'region_id': json_home.Parameters.REGION_ID, - }) + @versionutils.deprecated(versionutils.deprecated.LIBERTY, + in_favor_of=_NEW, + remove_in=1, + what=_OLD) + def __init__(self, *args, **kwargs): + super(EndpointPolicyExtension, self).__init__(*args, **kwargs) diff --git a/keystone-moon/keystone/contrib/example/core.py b/keystone-moon/keystone/contrib/example/core.py index 6e85c7f7..e369dc4d 100644 --- a/keystone-moon/keystone/contrib/example/core.py +++ b/keystone-moon/keystone/contrib/example/core.py @@ -12,6 +12,8 @@ # License for the specific language governing permissions and limitations # under the License. +"""Main entry point into this Example service.""" + from oslo_log import log from keystone.common import dependency @@ -24,15 +26,18 @@ from keystone import notifications LOG = log.getLogger(__name__) +@notifications.listener # NOTE(dstanek): only needed if using event_callbacks @dependency.provider('example_api') class ExampleManager(manager.Manager): - """Example Manager. + """Default pivot point for this Example backend. See :mod:`keystone.common.manager.Manager` for more details on how this dynamically calls the backend. """ + driver_namespace = 'keystone.example' + def __init__(self): # The following is an example of event callbacks. In this setup, # ExampleManager's data model is depended on project's data model. @@ -45,8 +50,8 @@ class ExampleManager(manager.Manager): # project_created_callback will be invoked whenever a new project is # created. - # This information is used when the @dependency.provider decorator acts - # on the class. + # This information is used when the @notifications.listener decorator + # acts on the class. self.event_callbacks = { notifications.ACTIONS.deleted: { 'project': [self.project_deleted_callback], diff --git a/keystone-moon/keystone/contrib/example/migrate_repo/versions/001_example_table.py b/keystone-moon/keystone/contrib/example/migrate_repo/versions/001_example_table.py index 10b7ccc7..35061780 100644 --- a/keystone-moon/keystone/contrib/example/migrate_repo/versions/001_example_table.py +++ b/keystone-moon/keystone/contrib/example/migrate_repo/versions/001_example_table.py @@ -30,14 +30,3 @@ def upgrade(migrate_engine): sql.Column('type', sql.String(255)), sql.Column('extra', sql.Text())) service_table.create(migrate_engine, checkfirst=True) - - -def downgrade(migrate_engine): - # Operations to reverse the above upgrade go here. - meta = sql.MetaData() - meta.bind = migrate_engine - - tables = ['example'] - for t in tables: - table = sql.Table(t, meta, autoload=True) - table.drop(migrate_engine, checkfirst=True) diff --git a/keystone-moon/keystone/contrib/federation/backends/sql.py b/keystone-moon/keystone/contrib/federation/backends/sql.py index f2c124d0..ed07c08f 100644 --- a/keystone-moon/keystone/contrib/federation/backends/sql.py +++ b/keystone-moon/keystone/contrib/federation/backends/sql.py @@ -17,6 +17,7 @@ from oslo_serialization import jsonutils from keystone.common import sql from keystone.contrib.federation import core from keystone import exception +from sqlalchemy import orm class FederationProtocolModel(sql.ModelBase, sql.DictBase): @@ -44,13 +45,53 @@ class FederationProtocolModel(sql.ModelBase, sql.DictBase): class IdentityProviderModel(sql.ModelBase, sql.DictBase): __tablename__ = 'identity_provider' - attributes = ['id', 'remote_id', 'enabled', 'description'] - mutable_attributes = frozenset(['description', 'enabled', 'remote_id']) + attributes = ['id', 'enabled', 'description', 'remote_ids'] + mutable_attributes = frozenset(['description', 'enabled', 'remote_ids']) id = sql.Column(sql.String(64), primary_key=True) - remote_id = sql.Column(sql.String(256), nullable=True) enabled = sql.Column(sql.Boolean, nullable=False) description = sql.Column(sql.Text(), nullable=True) + remote_ids = orm.relationship('IdPRemoteIdsModel', + order_by='IdPRemoteIdsModel.remote_id', + cascade='all, delete-orphan') + + @classmethod + def from_dict(cls, dictionary): + new_dictionary = dictionary.copy() + remote_ids_list = new_dictionary.pop('remote_ids', None) + if not remote_ids_list: + remote_ids_list = [] + identity_provider = cls(**new_dictionary) + remote_ids = [] + # NOTE(fmarco76): the remote_ids_list contains only remote ids + # associated with the IdP because of the "relationship" established in + # sqlalchemy and corresponding to the FK in the idp_remote_ids table + for remote in remote_ids_list: + remote_ids.append(IdPRemoteIdsModel(remote_id=remote)) + identity_provider.remote_ids = remote_ids + return identity_provider + + def to_dict(self): + """Return a dictionary with model's attributes.""" + d = dict() + for attr in self.__class__.attributes: + d[attr] = getattr(self, attr) + d['remote_ids'] = [] + for remote in self.remote_ids: + d['remote_ids'].append(remote.remote_id) + return d + + +class IdPRemoteIdsModel(sql.ModelBase, sql.DictBase): + __tablename__ = 'idp_remote_ids' + attributes = ['idp_id', 'remote_id'] + mutable_attributes = frozenset(['idp_id', 'remote_id']) + + idp_id = sql.Column(sql.String(64), + sql.ForeignKey('identity_provider.id', + ondelete='CASCADE')) + remote_id = sql.Column(sql.String(255), + primary_key=True) @classmethod def from_dict(cls, dictionary): @@ -75,6 +116,7 @@ class MappingModel(sql.ModelBase, sql.DictBase): @classmethod def from_dict(cls, dictionary): new_dictionary = dictionary.copy() + new_dictionary['rules'] = jsonutils.dumps(new_dictionary['rules']) return cls(**new_dictionary) def to_dict(self): @@ -82,20 +124,23 @@ class MappingModel(sql.ModelBase, sql.DictBase): d = dict() for attr in self.__class__.attributes: d[attr] = getattr(self, attr) + d['rules'] = jsonutils.loads(d['rules']) return d class ServiceProviderModel(sql.ModelBase, sql.DictBase): __tablename__ = 'service_provider' - attributes = ['auth_url', 'id', 'enabled', 'description', 'sp_url'] + attributes = ['auth_url', 'id', 'enabled', 'description', + 'relay_state_prefix', 'sp_url'] mutable_attributes = frozenset(['auth_url', 'description', 'enabled', - 'sp_url']) + 'relay_state_prefix', 'sp_url']) id = sql.Column(sql.String(64), primary_key=True) enabled = sql.Column(sql.Boolean, nullable=False) description = sql.Column(sql.Text(), nullable=True) auth_url = sql.Column(sql.String(256), nullable=False) sp_url = sql.Column(sql.String(256), nullable=False) + relay_state_prefix = sql.Column(sql.String(256), nullable=False) @classmethod def from_dict(cls, dictionary): @@ -123,6 +168,7 @@ class Federation(core.Driver): def delete_idp(self, idp_id): with sql.transaction() as session: + self._delete_assigned_protocols(session, idp_id) idp_ref = self._get_idp(session, idp_id) session.delete(idp_ref) @@ -133,7 +179,7 @@ class Federation(core.Driver): return idp_ref def _get_idp_from_remote_id(self, session, remote_id): - q = session.query(IdentityProviderModel) + q = session.query(IdPRemoteIdsModel) q = q.filter_by(remote_id=remote_id) try: return q.one() @@ -153,8 +199,8 @@ class Federation(core.Driver): def get_idp_from_remote_id(self, remote_id): with sql.transaction() as session: - idp_ref = self._get_idp_from_remote_id(session, remote_id) - return idp_ref.to_dict() + ref = self._get_idp_from_remote_id(session, remote_id) + return ref.to_dict() def update_idp(self, idp_id, idp): with sql.transaction() as session: @@ -214,6 +260,11 @@ class Federation(core.Driver): key_ref = self._get_protocol(session, idp_id, protocol_id) session.delete(key_ref) + def _delete_assigned_protocols(self, session, idp_id): + query = session.query(FederationProtocolModel) + query = query.filter_by(idp_id=idp_id) + query.delete() + # Mapping CRUD def _get_mapping(self, session, mapping_id): mapping_ref = session.query(MappingModel).get(mapping_id) @@ -225,7 +276,7 @@ class Federation(core.Driver): def create_mapping(self, mapping_id, mapping): ref = {} ref['id'] = mapping_id - ref['rules'] = jsonutils.dumps(mapping.get('rules')) + ref['rules'] = mapping.get('rules') with sql.transaction() as session: mapping_ref = MappingModel.from_dict(ref) session.add(mapping_ref) @@ -250,7 +301,7 @@ class Federation(core.Driver): def update_mapping(self, mapping_id, mapping): ref = {} ref['id'] = mapping_id - ref['rules'] = jsonutils.dumps(mapping.get('rules')) + ref['rules'] = mapping.get('rules') with sql.transaction() as session: mapping_ref = self._get_mapping(session, mapping_id) old_mapping = mapping_ref.to_dict() diff --git a/keystone-moon/keystone/contrib/federation/constants.py b/keystone-moon/keystone/contrib/federation/constants.py new file mode 100644 index 00000000..afb38494 --- /dev/null +++ b/keystone-moon/keystone/contrib/federation/constants.py @@ -0,0 +1,15 @@ +# 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. + +FEDERATION = 'OS-FEDERATION' +IDENTITY_PROVIDER = 'OS-FEDERATION:identity_provider' +PROTOCOL = 'OS-FEDERATION:protocol' diff --git a/keystone-moon/keystone/contrib/federation/controllers.py b/keystone-moon/keystone/contrib/federation/controllers.py index 6066a33f..912d45d5 100644 --- a/keystone-moon/keystone/contrib/federation/controllers.py +++ b/keystone-moon/keystone/contrib/federation/controllers.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""Extensions supporting Federation.""" +"""Workflow logic for the Federation service.""" import string @@ -55,9 +55,9 @@ class IdentityProvider(_ControllerBase): collection_name = 'identity_providers' member_name = 'identity_provider' - _mutable_parameters = frozenset(['description', 'enabled', 'remote_id']) + _mutable_parameters = frozenset(['description', 'enabled', 'remote_ids']) _public_parameters = frozenset(['id', 'enabled', 'description', - 'remote_id', 'links' + 'remote_ids', 'links' ]) @classmethod @@ -247,6 +247,36 @@ class MappingController(_ControllerBase): @dependency.requires('federation_api') class Auth(auth_controllers.Auth): + def _get_sso_origin_host(self, context): + """Validate and return originating dashboard URL. + + Make sure the parameter is specified in the request's URL as well its + value belongs to a list of trusted dashboards. + + :param context: request's context + :raises: exception.ValidationError: ``origin`` query parameter was not + specified. The URL is deemed invalid. + :raises: exception.Unauthorized: URL specified in origin query + parameter does not exist in list of websso trusted dashboards. + :returns: URL with the originating dashboard + + """ + if 'origin' in context['query_string']: + origin = context['query_string'].get('origin') + host = urllib.parse.unquote_plus(origin) + else: + msg = _('Request must have an origin query parameter') + LOG.error(msg) + raise exception.ValidationError(msg) + + if host not in CONF.federation.trusted_dashboard: + msg = _('%(host)s is not a trusted dashboard host') + msg = msg % {'host': host} + LOG.error(msg) + raise exception.Unauthorized(msg) + + return host + def federated_authentication(self, context, identity_provider, protocol): """Authenticate from dedicated url endpoint. @@ -268,33 +298,23 @@ class Auth(auth_controllers.Auth): def federated_sso_auth(self, context, protocol_id): try: - remote_id_name = CONF.federation.remote_id_attribute + remote_id_name = utils.get_remote_id_parameter(protocol_id) remote_id = context['environment'][remote_id_name] except KeyError: msg = _('Missing entity ID from environment') LOG.error(msg) raise exception.Unauthorized(msg) - if 'origin' in context['query_string']: - origin = context['query_string'].get('origin') - host = urllib.parse.unquote_plus(origin) - else: - msg = _('Request must have an origin query parameter') - LOG.error(msg) - raise exception.ValidationError(msg) + host = self._get_sso_origin_host(context) - if host in CONF.federation.trusted_dashboard: - ref = self.federation_api.get_idp_from_remote_id(remote_id) - identity_provider = ref['id'] - res = self.federated_authentication(context, identity_provider, - protocol_id) - token_id = res.headers['X-Subject-Token'] - return self.render_html_response(host, token_id) - else: - msg = _('%(host)s is not a trusted dashboard host') - msg = msg % {'host': host} - LOG.error(msg) - raise exception.Unauthorized(msg) + ref = self.federation_api.get_idp_from_remote_id(remote_id) + # NOTE(stevemar): the returned object is a simple dict that + # contains the idp_id and remote_id. + identity_provider = ref['idp_id'] + res = self.federated_authentication(context, identity_provider, + protocol_id) + token_id = res.headers['X-Subject-Token'] + return self.render_html_response(host, token_id) def render_html_response(self, host, token_id): """Forms an HTML Form from a template with autosubmit.""" @@ -309,45 +329,77 @@ class Auth(auth_controllers.Auth): return webob.Response(body=body, status='200', headerlist=headers) - @validation.validated(schema.saml_create, 'auth') - def create_saml_assertion(self, context, auth): - """Exchange a scoped token for a SAML assertion. - - :param auth: Dictionary that contains a token and service provider id - :returns: SAML Assertion based on properties from the token - """ - + def _create_base_saml_assertion(self, context, auth): issuer = CONF.saml.idp_entity_id sp_id = auth['scope']['service_provider']['id'] service_provider = self.federation_api.get_sp(sp_id) utils.assert_enabled_service_provider_object(service_provider) - sp_url = service_provider.get('sp_url') - auth_url = service_provider.get('auth_url') token_id = auth['identity']['token']['id'] token_data = self.token_provider_api.validate_token(token_id) token_ref = token_model.KeystoneToken(token_id, token_data) - subject = token_ref.user_name - roles = token_ref.role_names if not token_ref.project_scoped: action = _('Use a project scoped token when attempting to create ' 'a SAML assertion') raise exception.ForbiddenAction(action=action) + subject = token_ref.user_name + roles = token_ref.role_names project = token_ref.project_name + # NOTE(rodrigods): the domain name is necessary in order to distinguish + # between projects and users with the same name in different domains. + project_domain_name = token_ref.project_domain_name + subject_domain_name = token_ref.user_domain_name + generator = keystone_idp.SAMLGenerator() - response = generator.samlize_token(issuer, sp_url, subject, roles, - project) + response = generator.samlize_token( + issuer, sp_url, subject, subject_domain_name, + roles, project, project_domain_name) + return (response, service_provider) + + def _build_response_headers(self, service_provider): + return [('Content-Type', 'text/xml'), + ('X-sp-url', six.binary_type(service_provider['sp_url'])), + ('X-auth-url', six.binary_type(service_provider['auth_url']))] + + @validation.validated(schema.saml_create, 'auth') + def create_saml_assertion(self, context, auth): + """Exchange a scoped token for a SAML assertion. + + :param auth: Dictionary that contains a token and service provider ID + :returns: SAML Assertion based on properties from the token + """ + t = self._create_base_saml_assertion(context, auth) + (response, service_provider) = t + + headers = self._build_response_headers(service_provider) return wsgi.render_response(body=response.to_string(), status=('200', 'OK'), - headers=[('Content-Type', 'text/xml'), - ('X-sp-url', - six.binary_type(sp_url)), - ('X-auth-url', - six.binary_type(auth_url))]) + headers=headers) + + @validation.validated(schema.saml_create, 'auth') + def create_ecp_assertion(self, context, auth): + """Exchange a scoped token for an ECP assertion. + + :param auth: Dictionary that contains a token and service provider ID + :returns: ECP Assertion based on properties from the token + """ + + t = self._create_base_saml_assertion(context, auth) + (saml_assertion, service_provider) = t + relay_state_prefix = service_provider.get('relay_state_prefix') + + generator = keystone_idp.ECPGenerator() + ecp_assertion = generator.generate_ecp(saml_assertion, + relay_state_prefix) + + headers = self._build_response_headers(service_provider) + return wsgi.render_response(body=ecp_assertion.to_string(), + status=('200', 'OK'), + headers=headers) @dependency.requires('assignment_api', 'resource_api') @@ -404,15 +456,17 @@ class ServiceProvider(_ControllerBase): member_name = 'service_provider' _mutable_parameters = frozenset(['auth_url', 'description', 'enabled', - 'sp_url']) + 'relay_state_prefix', 'sp_url']) _public_parameters = frozenset(['auth_url', 'id', 'enabled', 'description', - 'links', 'sp_url']) + 'links', 'relay_state_prefix', 'sp_url']) @controller.protected() @validation.validated(schema.service_provider_create, 'service_provider') def create_service_provider(self, context, sp_id, service_provider): service_provider = self._normalize_dict(service_provider) service_provider.setdefault('enabled', False) + service_provider.setdefault('relay_state_prefix', + CONF.saml.relay_state_prefix) ServiceProvider.check_immutable_params(service_provider) sp_ref = self.federation_api.create_sp(sp_id, service_provider) response = ServiceProvider.wrap_member(context, sp_ref) diff --git a/keystone-moon/keystone/contrib/federation/core.py b/keystone-moon/keystone/contrib/federation/core.py index b596cff7..2ab75ecb 100644 --- a/keystone-moon/keystone/contrib/federation/core.py +++ b/keystone-moon/keystone/contrib/federation/core.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""Extension supporting Federation.""" +"""Main entry point into the Federation service.""" import abc @@ -21,6 +21,7 @@ import six from keystone.common import dependency from keystone.common import extension from keystone.common import manager +from keystone.contrib.federation import utils from keystone import exception @@ -41,11 +42,6 @@ EXTENSION_DATA = { extension.register_admin_extension(EXTENSION_DATA['alias'], EXTENSION_DATA) extension.register_public_extension(EXTENSION_DATA['alias'], EXTENSION_DATA) -FEDERATION = 'OS-FEDERATION' -IDENTITY_PROVIDER = 'OS-FEDERATION:identity_provider' -PROTOCOL = 'OS-FEDERATION:protocol' -FEDERATED_DOMAIN_KEYWORD = 'Federated' - @dependency.provider('federation_api') class Manager(manager.Manager): @@ -55,6 +51,9 @@ class Manager(manager.Manager): dynamically calls the backend. """ + + driver_namespace = 'keystone.federation' + def __init__(self): super(Manager, self).__init__(CONF.federation.driver) @@ -84,6 +83,13 @@ class Manager(manager.Manager): service_providers = self.driver.get_enabled_service_providers() return [normalize(sp) for sp in service_providers] + def evaluate(self, idp_id, protocol_id, assertion_data): + mapping = self.get_mapping_from_idp_and_protocol(idp_id, protocol_id) + rules = mapping['rules'] + rule_processor = utils.RuleProcessor(rules) + mapped_properties = rule_processor.process(assertion_data) + return mapped_properties, mapping['id'] + @six.add_metaclass(abc.ABCMeta) class Driver(object): diff --git a/keystone-moon/keystone/contrib/federation/idp.py b/keystone-moon/keystone/contrib/federation/idp.py index bf400135..739fc01a 100644 --- a/keystone-moon/keystone/contrib/federation/idp.py +++ b/keystone-moon/keystone/contrib/federation/idp.py @@ -17,17 +17,24 @@ import uuid from oslo_config import cfg from oslo_log import log +from oslo_utils import fileutils +from oslo_utils import importutils from oslo_utils import timeutils import saml2 +from saml2 import client_base from saml2 import md +from saml2.profile import ecp from saml2 import saml from saml2 import samlp +from saml2.schema import soapenv from saml2 import sigver -import xmldsig +xmldsig = importutils.try_import("saml2.xmldsig") +if not xmldsig: + xmldsig = importutils.try_import("xmldsig") +from keystone.common import utils from keystone import exception from keystone.i18n import _, _LE -from keystone.openstack.common import fileutils LOG = log.getLogger(__name__) @@ -40,8 +47,8 @@ class SAMLGenerator(object): def __init__(self): self.assertion_id = uuid.uuid4().hex - def samlize_token(self, issuer, recipient, user, roles, project, - expires_in=None): + def samlize_token(self, issuer, recipient, user, user_domain_name, roles, + project, project_domain_name, expires_in=None): """Convert Keystone attributes to a SAML assertion. :param issuer: URL of the issuing party @@ -50,10 +57,14 @@ class SAMLGenerator(object): :type recipient: string :param user: User name :type user: string + :param user_domain_name: User Domain name + :type user_domain_name: string :param roles: List of role names :type roles: list :param project: Project name :type project: string + :param project_domain_name: Project Domain name + :type project_domain_name: string :param expires_in: Sets how long the assertion is valid for, in seconds :type expires_in: int @@ -64,8 +75,8 @@ class SAMLGenerator(object): status = self._create_status() saml_issuer = self._create_issuer(issuer) subject = self._create_subject(user, expiration_time, recipient) - attribute_statement = self._create_attribute_statement(user, roles, - project) + attribute_statement = self._create_attribute_statement( + user, user_domain_name, roles, project, project_domain_name) authn_statement = self._create_authn_statement(issuer, expiration_time) signature = self._create_signature() @@ -84,7 +95,7 @@ class SAMLGenerator(object): expires_in = CONF.saml.assertion_expiration_time now = timeutils.utcnow() future = now + datetime.timedelta(seconds=expires_in) - return timeutils.isotime(future, subsecond=True) + return utils.isotime(future, subsecond=True) def _create_status(self): """Create an object that represents a SAML Status. @@ -150,58 +161,64 @@ class SAMLGenerator(object): subject.name_id = name_id return subject - def _create_attribute_statement(self, user, roles, project): + def _create_attribute_statement(self, user, user_domain_name, roles, + project, project_domain_name): """Create an object that represents a SAML AttributeStatement. - <ns0:AttributeStatement - xmlns:ns0="urn:oasis:names:tc:SAML:2.0:assertion" - xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <ns0:AttributeStatement> <ns0:Attribute Name="openstack_user"> <ns0:AttributeValue xsi:type="xs:string">test_user</ns0:AttributeValue> </ns0:Attribute> + <ns0:Attribute Name="openstack_user_domain"> + <ns0:AttributeValue + xsi:type="xs:string">Default</ns0:AttributeValue> + </ns0:Attribute> <ns0:Attribute Name="openstack_roles"> <ns0:AttributeValue xsi:type="xs:string">admin</ns0:AttributeValue> <ns0:AttributeValue xsi:type="xs:string">member</ns0:AttributeValue> </ns0:Attribute> - <ns0:Attribute Name="openstack_projects"> + <ns0:Attribute Name="openstack_project"> <ns0:AttributeValue xsi:type="xs:string">development</ns0:AttributeValue> </ns0:Attribute> + <ns0:Attribute Name="openstack_project_domain"> + <ns0:AttributeValue + xsi:type="xs:string">Default</ns0:AttributeValue> + </ns0:Attribute> </ns0:AttributeStatement> :return: XML <AttributeStatement> object """ - openstack_user = 'openstack_user' - user_attribute = saml.Attribute() - user_attribute.name = openstack_user - user_value = saml.AttributeValue() - user_value.set_text(user) - user_attribute.attribute_value = user_value - - openstack_roles = 'openstack_roles' - roles_attribute = saml.Attribute() - roles_attribute.name = openstack_roles - - for role in roles: - role_value = saml.AttributeValue() - role_value.set_text(role) - roles_attribute.attribute_value.append(role_value) - - openstack_project = 'openstack_project' - project_attribute = saml.Attribute() - project_attribute.name = openstack_project - project_value = saml.AttributeValue() - project_value.set_text(project) - project_attribute.attribute_value = project_value + + def _build_attribute(attribute_name, attribute_values): + attribute = saml.Attribute() + attribute.name = attribute_name + + for value in attribute_values: + attribute_value = saml.AttributeValue() + attribute_value.set_text(value) + attribute.attribute_value.append(attribute_value) + + return attribute + + user_attribute = _build_attribute('openstack_user', [user]) + roles_attribute = _build_attribute('openstack_roles', roles) + project_attribute = _build_attribute('openstack_project', [project]) + project_domain_attribute = _build_attribute( + 'openstack_project_domain', [project_domain_name]) + user_domain_attribute = _build_attribute( + 'openstack_user_domain', [user_domain_name]) attribute_statement = saml.AttributeStatement() attribute_statement.attribute.append(user_attribute) attribute_statement.attribute.append(roles_attribute) attribute_statement.attribute.append(project_attribute) + attribute_statement.attribute.append(project_domain_attribute) + attribute_statement.attribute.append(user_domain_attribute) return attribute_statement def _create_authn_statement(self, issuer, expiration_time): @@ -224,7 +241,7 @@ class SAMLGenerator(object): """ authn_statement = saml.AuthnStatement() - authn_statement.authn_instant = timeutils.isotime() + authn_statement.authn_instant = utils.isotime() authn_statement.session_index = uuid.uuid4().hex authn_statement.session_not_on_or_after = expiration_time @@ -261,7 +278,7 @@ class SAMLGenerator(object): """ assertion = saml.Assertion() assertion.id = self.assertion_id - assertion.issue_instant = timeutils.isotime() + assertion.issue_instant = utils.isotime() assertion.version = '2.0' assertion.issuer = issuer assertion.signature = signature @@ -289,7 +306,7 @@ class SAMLGenerator(object): response = samlp.Response() response.id = uuid.uuid4().hex response.destination = recipient - response.issue_instant = timeutils.isotime() + response.issue_instant = utils.isotime() response.version = '2.0' response.issuer = issuer response.status = status @@ -397,6 +414,7 @@ def _sign_assertion(assertion): command_list = [xmlsec_binary, '--sign', '--privkey-pem', certificates, '--id-attr:ID', 'Assertion'] + file_path = None try: # NOTE(gyee): need to make the namespace prefixes explicit so # they won't get reassigned when we wrap the assertion into @@ -405,15 +423,19 @@ def _sign_assertion(assertion): nspair={'saml': saml2.NAMESPACE, 'xmldsig': xmldsig.NAMESPACE})) command_list.append(file_path) - stdout = subprocess.check_output(command_list) + stdout = subprocess.check_output(command_list, + stderr=subprocess.STDOUT) except Exception as e: msg = _LE('Error when signing assertion, reason: %(reason)s') msg = msg % {'reason': e} + if hasattr(e, 'output'): + msg += ' output: %(output)s' % {'output': e.output} LOG.error(msg) raise exception.SAMLSigningError(reason=e) finally: try: - os.remove(file_path) + if file_path: + os.remove(file_path) except OSError: pass @@ -556,3 +578,31 @@ class MetadataGenerator(object): if value is None: return False return True + + +class ECPGenerator(object): + """A class for generating an ECP assertion.""" + + @staticmethod + def generate_ecp(saml_assertion, relay_state_prefix): + ecp_generator = ECPGenerator() + header = ecp_generator._create_header(relay_state_prefix) + body = ecp_generator._create_body(saml_assertion) + envelope = soapenv.Envelope(header=header, body=body) + return envelope + + def _create_header(self, relay_state_prefix): + relay_state_text = relay_state_prefix + uuid.uuid4().hex + relay_state = ecp.RelayState(actor=client_base.ACTOR, + must_understand='1', + text=relay_state_text) + header = soapenv.Header() + header.extension_elements = ( + [saml2.element_to_extension_element(relay_state)]) + return header + + def _create_body(self, saml_assertion): + body = soapenv.Body() + body.extension_elements = ( + [saml2.element_to_extension_element(saml_assertion)]) + return body diff --git a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/001_add_identity_provider_table.py b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/001_add_identity_provider_table.py index cfb6f2c4..9a4d574b 100644 --- a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/001_add_identity_provider_table.py +++ b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/001_add_identity_provider_table.py @@ -40,12 +40,3 @@ def upgrade(migrate_engine): mysql_charset='utf8') federation_protocol_table.create(migrate_engine, checkfirst=True) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - tables = ['federation_protocol', 'identity_provider'] - for table_name in tables: - table = sql.Table(table_name, meta, autoload=True) - table.drop() diff --git a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/002_add_mapping_tables.py b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/002_add_mapping_tables.py index f827f9a9..9a155f5c 100644 --- a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/002_add_mapping_tables.py +++ b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/002_add_mapping_tables.py @@ -25,13 +25,3 @@ def upgrade(migrate_engine): mysql_engine='InnoDB', mysql_charset='utf8') mapping_table.create(migrate_engine, checkfirst=True) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - # Drop previously created tables - tables = ['mapping'] - for table_name in tables: - table = sql.Table(table_name, meta, autoload=True) - table.drop() diff --git a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/003_mapping_id_nullable_false.py b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/003_mapping_id_nullable_false.py index eb8b2378..1731b0d3 100644 --- a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/003_mapping_id_nullable_false.py +++ b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/003_mapping_id_nullable_false.py @@ -27,9 +27,3 @@ def upgrade(migrate_engine): values(mapping_id='')) migrate_engine.execute(stmt) federation_protocol.c.mapping_id.alter(nullable=False) - - -def downgrade(migrate_engine): - meta = sa.MetaData(bind=migrate_engine) - federation_protocol = sa.Table('federation_protocol', meta, autoload=True) - federation_protocol.c.mapping_id.alter(nullable=True) diff --git a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/004_add_remote_id_column.py b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/004_add_remote_id_column.py index dbe5d1f1..2e0aaf93 100644 --- a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/004_add_remote_id_column.py +++ b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/004_add_remote_id_column.py @@ -21,10 +21,3 @@ def upgrade(migrate_engine): idp_table = utils.get_table(migrate_engine, 'identity_provider') remote_id = sql.Column('remote_id', sql.String(256), nullable=True) idp_table.create_column(remote_id) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - idp_table = utils.get_table(migrate_engine, 'identity_provider') - idp_table.drop_column('remote_id') diff --git a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/005_add_service_provider_table.py b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/005_add_service_provider_table.py index bff6a252..1594f893 100644 --- a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/005_add_service_provider_table.py +++ b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/005_add_service_provider_table.py @@ -29,10 +29,3 @@ def upgrade(migrate_engine): mysql_charset='utf8') sp_table.create(migrate_engine, checkfirst=True) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - table = sql.Table('service_provider', meta, autoload=True) - table.drop() diff --git a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/006_fixup_service_provider_attributes.py b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/006_fixup_service_provider_attributes.py index 8a42ce3a..dc18f548 100644 --- a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/006_fixup_service_provider_attributes.py +++ b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/006_fixup_service_provider_attributes.py @@ -38,11 +38,3 @@ def upgrade(migrate_engine): sp_table.c.auth_url.alter(nullable=False) sp_table.c.sp_url.alter(nullable=False) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - sp_table = sql.Table(_SP_TABLE_NAME, meta, autoload=True) - sp_table.c.auth_url.alter(nullable=True) - sp_table.c.sp_url.alter(nullable=True) diff --git a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/007_add_remote_id_table.py b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/007_add_remote_id_table.py new file mode 100644 index 00000000..cd571245 --- /dev/null +++ b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/007_add_remote_id_table.py @@ -0,0 +1,41 @@ +# 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 sqlalchemy as orm + + +def upgrade(migrate_engine): + meta = orm.MetaData() + meta.bind = migrate_engine + idp_table = orm.Table('identity_provider', meta, autoload=True) + remote_id_table = orm.Table( + 'idp_remote_ids', + meta, + orm.Column('idp_id', + orm.String(64), + orm.ForeignKey('identity_provider.id', + ondelete='CASCADE')), + orm.Column('remote_id', + orm.String(255), + primary_key=True), + mysql_engine='InnoDB', + mysql_charset='utf8') + + remote_id_table.create(migrate_engine, checkfirst=True) + + select = orm.sql.select([idp_table.c.id, idp_table.c.remote_id]) + for identity in migrate_engine.execute(select): + remote_idp_entry = {'idp_id': identity.id, + 'remote_id': identity.remote_id} + remote_id_table.insert(remote_idp_entry).execute() + + idp_table.drop_column('remote_id') diff --git a/keystone-moon/keystone/contrib/federation/migrate_repo/versions/008_add_relay_state_to_sp.py b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/008_add_relay_state_to_sp.py new file mode 100644 index 00000000..150dcfed --- /dev/null +++ b/keystone-moon/keystone/contrib/federation/migrate_repo/versions/008_add_relay_state_to_sp.py @@ -0,0 +1,39 @@ +# 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. + +from oslo_config import cfg +from oslo_db.sqlalchemy import utils +import sqlalchemy as sql + + +CONF = cfg.CONF +_SP_TABLE_NAME = 'service_provider' +_RELAY_STATE_PREFIX = 'relay_state_prefix' + + +def upgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + + idp_table = utils.get_table(migrate_engine, _SP_TABLE_NAME) + relay_state_prefix_default = CONF.saml.relay_state_prefix + relay_state_prefix = sql.Column(_RELAY_STATE_PREFIX, sql.String(256), + nullable=False, + server_default=relay_state_prefix_default) + idp_table.create_column(relay_state_prefix) + + +def downgrade(migrate_engine): + meta = sql.MetaData() + meta.bind = migrate_engine + idp_table = utils.get_table(migrate_engine, _SP_TABLE_NAME) + idp_table.drop_column(_RELAY_STATE_PREFIX) diff --git a/keystone-moon/keystone/contrib/federation/routers.py b/keystone-moon/keystone/contrib/federation/routers.py index 9a6224b7..d8fa8175 100644 --- a/keystone-moon/keystone/contrib/federation/routers.py +++ b/keystone-moon/keystone/contrib/federation/routers.py @@ -36,44 +36,45 @@ class FederationExtension(wsgi.V3ExtensionRouter): The API looks like:: - PUT /OS-FEDERATION/identity_providers/$identity_provider + PUT /OS-FEDERATION/identity_providers/{idp_id} GET /OS-FEDERATION/identity_providers - GET /OS-FEDERATION/identity_providers/$identity_provider - DELETE /OS-FEDERATION/identity_providers/$identity_provider - PATCH /OS-FEDERATION/identity_providers/$identity_provider + GET /OS-FEDERATION/identity_providers/{idp_id} + DELETE /OS-FEDERATION/identity_providers/{idp_id} + PATCH /OS-FEDERATION/identity_providers/{idp_id} PUT /OS-FEDERATION/identity_providers/ - $identity_provider/protocols/$protocol + {idp_id}/protocols/{protocol_id} GET /OS-FEDERATION/identity_providers/ - $identity_provider/protocols + {idp_id}/protocols GET /OS-FEDERATION/identity_providers/ - $identity_provider/protocols/$protocol + {idp_id}/protocols/{protocol_id} PATCH /OS-FEDERATION/identity_providers/ - $identity_provider/protocols/$protocol + {idp_id}/protocols/{protocol_id} DELETE /OS-FEDERATION/identity_providers/ - $identity_provider/protocols/$protocol + {idp_id}/protocols/{protocol_id} PUT /OS-FEDERATION/mappings GET /OS-FEDERATION/mappings - PATCH /OS-FEDERATION/mappings/$mapping_id - GET /OS-FEDERATION/mappings/$mapping_id - DELETE /OS-FEDERATION/mappings/$mapping_id + PATCH /OS-FEDERATION/mappings/{mapping_id} + GET /OS-FEDERATION/mappings/{mapping_id} + DELETE /OS-FEDERATION/mappings/{mapping_id} GET /OS-FEDERATION/projects GET /OS-FEDERATION/domains - PUT /OS-FEDERATION/service_providers/$service_provider + PUT /OS-FEDERATION/service_providers/{sp_id} GET /OS-FEDERATION/service_providers - GET /OS-FEDERATION/service_providers/$service_provider - DELETE /OS-FEDERATION/service_providers/$service_provider - PATCH /OS-FEDERATION/service_providers/$service_provider + GET /OS-FEDERATION/service_providers/{sp_id} + DELETE /OS-FEDERATION/service_providers/{sp_id} + PATCH /OS-FEDERATION/service_providers/{sp_id} - GET /OS-FEDERATION/identity_providers/$identity_provider/ - protocols/$protocol/auth - POST /OS-FEDERATION/identity_providers/$identity_provider/ - protocols/$protocol/auth + GET /OS-FEDERATION/identity_providers/{identity_provider}/ + protocols/{protocol}/auth + POST /OS-FEDERATION/identity_providers/{identity_provider}/ + protocols/{protocol}/auth POST /auth/OS-FEDERATION/saml2 + POST /auth/OS-FEDERATION/saml2/ecp GET /OS-FEDERATION/saml2/metadata GET /auth/OS-FEDERATION/websso/{protocol_id} @@ -191,6 +192,8 @@ class FederationExtension(wsgi.V3ExtensionRouter): path=self._construct_url('projects'), get_action='list_projects_for_groups', rel=build_resource_relation(resource_name='projects')) + + # Auth operations self._add_resource( mapper, auth_controller, path=self._construct_url('identity_providers/{identity_provider}/' @@ -202,8 +205,6 @@ class FederationExtension(wsgi.V3ExtensionRouter): 'identity_provider': IDP_ID_PARAMETER_RELATION, 'protocol': PROTOCOL_ID_PARAMETER_RELATION, }) - - # Auth operations self._add_resource( mapper, auth_controller, path='/auth' + self._construct_url('saml2'), @@ -211,6 +212,11 @@ class FederationExtension(wsgi.V3ExtensionRouter): rel=build_resource_relation(resource_name='saml2')) self._add_resource( mapper, auth_controller, + path='/auth' + self._construct_url('saml2/ecp'), + post_action='create_ecp_assertion', + rel=build_resource_relation(resource_name='ecp')) + self._add_resource( + mapper, auth_controller, path='/auth' + self._construct_url('websso/{protocol_id}'), get_post_action='federated_sso_auth', rel=build_resource_relation(resource_name='websso'), diff --git a/keystone-moon/keystone/contrib/federation/schema.py b/keystone-moon/keystone/contrib/federation/schema.py index 645e1129..17818a98 100644 --- a/keystone-moon/keystone/contrib/federation/schema.py +++ b/keystone-moon/keystone/contrib/federation/schema.py @@ -58,7 +58,8 @@ _service_provider_properties = { 'auth_url': parameter_types.url, 'sp_url': parameter_types.url, 'description': validation.nullable(parameter_types.description), - 'enabled': parameter_types.boolean + 'enabled': parameter_types.boolean, + 'relay_state_prefix': validation.nullable(parameter_types.description) } service_provider_create = { diff --git a/keystone-moon/keystone/contrib/federation/utils.py b/keystone-moon/keystone/contrib/federation/utils.py index 939fe9a0..b0db3cdd 100644 --- a/keystone-moon/keystone/contrib/federation/utils.py +++ b/keystone-moon/keystone/contrib/federation/utils.py @@ -21,7 +21,6 @@ from oslo_log import log from oslo_utils import timeutils import six -from keystone.contrib import federation from keystone import exception from keystone.i18n import _, _LW @@ -191,14 +190,37 @@ def validate_groups_cardinality(group_ids, mapping_id): raise exception.MissingGroups(mapping_id=mapping_id) -def validate_idp(idp, assertion): - """Check if the IdP providing the assertion is the one registered for - the mapping +def get_remote_id_parameter(protocol): + # NOTE(marco-fargetta): Since we support any protocol ID, we attempt to + # retrieve the remote_id_attribute of the protocol ID. If it's not + # registered in the config, then register the option and try again. + # This allows the user to register protocols other than oidc and saml2. + remote_id_parameter = None + try: + remote_id_parameter = CONF[protocol]['remote_id_attribute'] + except AttributeError: + CONF.register_opt(cfg.StrOpt('remote_id_attribute'), + group=protocol) + try: + remote_id_parameter = CONF[protocol]['remote_id_attribute'] + except AttributeError: + pass + if not remote_id_parameter: + LOG.debug('Cannot find "remote_id_attribute" in configuration ' + 'group %s. Trying default location in ' + 'group federation.', protocol) + remote_id_parameter = CONF.federation.remote_id_attribute + + return remote_id_parameter + + +def validate_idp(idp, protocol, assertion): + """Validate the IdP providing the assertion is registered for the mapping. """ - remote_id_parameter = CONF.federation.remote_id_attribute - if not remote_id_parameter or not idp['remote_id']: - LOG.warning(_LW('Impossible to identify the IdP %s '), - idp['id']) + + remote_id_parameter = get_remote_id_parameter(protocol) + if not remote_id_parameter or not idp['remote_ids']: + LOG.debug('Impossible to identify the IdP %s ', idp['id']) # If nothing is defined, the administrator may want to # allow the mapping of every IdP return @@ -206,10 +228,9 @@ def validate_idp(idp, assertion): idp_remote_identifier = assertion[remote_id_parameter] except KeyError: msg = _('Could not find Identity Provider identifier in ' - 'environment, check [federation] remote_id_attribute ' - 'for details.') + 'environment') raise exception.ValidationError(msg) - if idp_remote_identifier != idp['remote_id']: + if idp_remote_identifier not in idp['remote_ids']: msg = _('Incoming identity provider identifier not included ' 'among the accepted identifiers.') raise exception.Forbidden(msg) @@ -265,7 +286,7 @@ def validate_groups(group_ids, mapping_id, identity_api): # TODO(marek-denis): Optimize this function, so the number of calls to the # backend are minimized. def transform_to_group_ids(group_names, mapping_id, - identity_api, assignment_api): + identity_api, resource_api): """Transform groups identitified by name/domain to their ids Function accepts list of groups identified by a name and domain giving @@ -296,7 +317,7 @@ def transform_to_group_ids(group_names, mapping_id, :type mapping_id: str :param identity_api: identity_api object - :param assignment_api: assignment_api object + :param resource_api: resource manager object :returns: generator object with group ids @@ -317,7 +338,7 @@ def transform_to_group_ids(group_names, mapping_id, """ domain_id = (domain.get('id') or - assignment_api.get_domain_by_name( + resource_api.get_domain_by_name( domain.get('name')).get('id')) return domain_id @@ -334,7 +355,7 @@ def transform_to_group_ids(group_names, mapping_id, def get_assertion_params_from_env(context): LOG.debug('Environment variables: %s', context['environment']) prefix = CONF.federation.assertion_prefix - for k, v in context['environment'].items(): + for k, v in list(context['environment'].items()): if k.startswith(prefix): yield (k, v) @@ -487,8 +508,8 @@ class RuleProcessor(object): """ def extract_groups(groups_by_domain): - for groups in groups_by_domain.values(): - for group in {g['name']: g for g in groups}.values(): + for groups in list(groups_by_domain.values()): + for group in list({g['name']: g for g in groups}.values()): yield group def normalize_user(user): @@ -506,8 +527,7 @@ class RuleProcessor(object): if user_type == UserType.EPHEMERAL: user['domain'] = { - 'id': (CONF.federation.federated_domain_name or - federation.FEDERATED_DOMAIN_KEYWORD) + 'id': CONF.federation.federated_domain_name } # initialize the group_ids as a set to eliminate duplicates @@ -586,7 +606,7 @@ class RuleProcessor(object): LOG.debug('direct_maps: %s', direct_maps) LOG.debug('local: %s', local) new = {} - for k, v in six.iteritems(local): + for k, v in local.items(): if isinstance(v, dict): new_value = self._update_local_mapping(v, direct_maps) else: @@ -644,7 +664,7 @@ class RuleProcessor(object): } :returns: identity values used to update local - :rtype: keystone.contrib.federation.utils.DirectMaps + :rtype: keystone.contrib.federation.utils.DirectMaps or None """ @@ -686,10 +706,10 @@ class RuleProcessor(object): # If a blacklist or whitelist is used, we want to map to the # whole list instead of just its values separately. - if blacklisted_values: + if blacklisted_values is not None: direct_map_values = [v for v in direct_map_values if v not in blacklisted_values] - elif whitelisted_values: + elif whitelisted_values is not None: direct_map_values = [v for v in direct_map_values if v in whitelisted_values] diff --git a/keystone-moon/keystone/contrib/moon/algorithms.py b/keystone-moon/keystone/contrib/moon/algorithms.py index 8644e02d..30305fc1 100644 --- a/keystone-moon/keystone/contrib/moon/algorithms.py +++ b/keystone-moon/keystone/contrib/moon/algorithms.py @@ -22,18 +22,19 @@ sub_meta_rule_dict = { } rule_dict = [ - ["high", "vm_admin", "medium"], - ["high", "vm_admin", "low"], - ["medium", "vm_admin", "low"], - ["high", "vm_access", "high"], - ["high", "vm_access", "medium"], - ["high", "vm_access", "low"], - ["medium", "vm_access", "medium"], - ["medium", "vm_access", "low"], - ["low", "vm_access", "low"] + ["high", "vm_admin", "medium", True], + ["high", "vm_admin", "low", True], + ["medium", "vm_admin", "low", True], + ["high", "vm_access", "high", True], + ["high", "vm_access", "medium", True], + ["high", "vm_access", "low", True], + ["medium", "vm_access", "medium", True], + ["medium", "vm_access", "low", True], + ["low", "vm_access", "low", True] ] """ + def inclusion(authz_buffer, sub_meta_rule_dict, rule_list): _cat = [] for subject_cat in sub_meta_rule_dict['subject_categories']: @@ -46,14 +47,10 @@ def inclusion(authz_buffer, sub_meta_rule_dict, rule_list): if object_cat in authz_buffer['object_assignments']: _cat.append(authz_buffer['object_assignments'][object_cat]) - print("authz_buffer", authz_buffer) - print("rule_list", rule_list) - print("_cat", _cat) for _element in itertools.product(*_cat): # Add the boolean at the end _element = list(_element) _element.append(True) - print("_element", _element) if _element in rule_list: return True @@ -66,6 +63,13 @@ def comparison(authz_buffer, sub_meta_rule_dict, rule_list): def all_true(decision_buffer): for _rule in decision_buffer: - if decision_buffer[_rule] is False: + if decision_buffer[_rule] == False: return False - return True
\ No newline at end of file + return True + + +def one_true(decision_buffer): + for _rule in decision_buffer: + if decision_buffer[_rule] == True: + return True + return False diff --git a/keystone-moon/keystone/contrib/moon/backends/memory.py b/keystone-moon/keystone/contrib/moon/backends/memory.py index 675240e5..7a996847 100644 --- a/keystone-moon/keystone/contrib/moon/backends/memory.py +++ b/keystone-moon/keystone/contrib/moon/backends/memory.py @@ -6,6 +6,7 @@ from uuid import uuid4 from glob import glob import os +import json from keystone import config from keystone.contrib.moon.core import ConfigurationDriver @@ -19,12 +20,12 @@ class ConfigurationConnector(ConfigurationDriver): super(ConfigurationConnector, self).__init__() self.aggregation_algorithms_dict = dict() self.aggregation_algorithms_dict[uuid4().hex] = {'name': 'all_true', 'description': 'all_true'} + self.aggregation_algorithms_dict[uuid4().hex] = {'name': 'one_true', 'description': 'one_true'} self.sub_meta_rule_algorithms_dict = dict() self.sub_meta_rule_algorithms_dict[uuid4().hex] = {'name': 'inclusion', 'description': 'inclusion'} self.sub_meta_rule_algorithms_dict[uuid4().hex] = {'name': 'comparison', 'description': 'comparison'} def get_policy_templates_dict(self): - # TODO (dthom): this function should return a dictionary of all policy templates as: """ :return: { template_id1: {name: template_name, description: template_description}, @@ -33,11 +34,15 @@ class ConfigurationConnector(ConfigurationDriver): } """ nodes = glob(os.path.join(CONF.moon.policy_directory, "*")) - return { - "authz_templates": [os.path.basename(n) for n in nodes if os.path.isdir(n)] - } - - def get_aggregation_algorithm_dict(self): + templates = dict() + for node in nodes: + templates[os.path.basename(node)] = dict() + metadata = json.load(open(os.path.join(node, "metadata.json"))) + templates[os.path.basename(node)]["name"] = metadata["name"] + templates[os.path.basename(node)]["description"] = metadata["description"] + return templates + + def get_aggregation_algorithms_dict(self): return self.aggregation_algorithms_dict def get_sub_meta_rule_algorithms_dict(self): diff --git a/keystone-moon/keystone/contrib/moon/backends/sql.py b/keystone-moon/keystone/contrib/moon/backends/sql.py index 7a75af39..cb64c1f7 100644 --- a/keystone-moon/keystone/contrib/moon/backends/sql.py +++ b/keystone-moon/keystone/contrib/moon/backends/sql.py @@ -887,7 +887,7 @@ class IntraExtensionConnector(IntraExtensionDriver): def set_aggregation_algorithm_dict(self, intra_extension_id, aggregation_algorithm_id, aggregation_algorithm_dict): with sql.transaction() as session: query = session.query(AggregationAlgorithm) - query = query.filter_by(intra_extension_id=intra_extension_id, id=aggregation_algorithm_id) + query = query.filter_by(intra_extension_id=intra_extension_id) ref = query.first() new_ref = AggregationAlgorithm.from_dict( { @@ -896,21 +896,18 @@ class IntraExtensionConnector(IntraExtensionDriver): 'intra_extension_id': intra_extension_id } ) - if not ref: - session.add(new_ref) - else: - for attr in AggregationAlgorithm.attributes: - if attr != 'id': - setattr(ref, attr, getattr(new_ref, attr)) + if ref: + session.delete(ref) + session.add(new_ref) session.flush() return self.get_aggregation_algorithm_dict(intra_extension_id) - # def del_aggregation_algorithm(self, intra_extension_id, aggregation_algorithm_id): - # with sql.transaction() as session: - # query = session.query(AggregationAlgorithm) - # query = query.filter_by(intra_extension_id=intra_extension_id, id=aggregation_algorithm_id) - # ref = query.first() - # session.delete(ref) + def del_aggregation_algorithm(self, intra_extension_id, aggregation_algorithm_id): + with sql.transaction() as session: + query = session.query(AggregationAlgorithm) + query = query.filter_by(intra_extension_id=intra_extension_id, id=aggregation_algorithm_id) + ref = query.first() + session.delete(ref) # Getter and Setter for sub_meta_rule diff --git a/keystone-moon/keystone/contrib/moon/core.py b/keystone-moon/keystone/contrib/moon/core.py index d82c9fcc..4a68cdaa 100644 --- a/keystone-moon/keystone/contrib/moon/core.py +++ b/keystone-moon/keystone/contrib/moon/core.py @@ -25,9 +25,9 @@ from keystone.contrib.moon.algorithms import * CONF = config.CONF LOG = log.getLogger(__name__) -ADMIN_ID = None # default user_id for internal invocation -ROOT_EXTENSION_ID = None -ROOT_EXTENSION_MODEL = "policy_root" +# ADMIN_ID = None # default user_id for internal invocation +# ROOT_EXTENSION_ID = None +# ROOT_EXTENSION_MODEL = "policy_root" _OPTS = [ @@ -52,9 +52,9 @@ _OPTS = [ cfg.StrOpt('policy_directory', default='/etc/keystone/policies', help='Local directory where all policies are stored.'), - cfg.StrOpt('super_extension_directory', - default='/etc/keystone/super_extension', - help='Local directory where SuperExtension configuration is stored.'), + cfg.StrOpt('root_policy_directory', + default='policy_root', + help='Local directory where Root IntraExtension configuration is stored.'), ] CONF.register_opts(_OPTS, group='moon') @@ -108,29 +108,29 @@ def enforce(action_names, object_name, **extra): _action_name_list = action_names _object_name = object_name - def get_root_extension(self, args, kwargs): - if not ROOT_EXTENSION_ID: - global ROOT_EXTENSION_MODEL, ROOT_EXTENSION_ID, ADMIN_ID - try: - # if it is the first time we passed here, the root extension may be not initialized - # specially during unittest. So we raise RootExtensionNotInitialized to authorize the - # current creation process - if 'intra_extension_dict' in kwargs: - intra_extension_dict = kwargs['intra_extension_dict'] - else: - intra_extension_dict = args[2] - if isinstance(intra_extension_dict, dict) and \ - "model" in intra_extension_dict and \ - intra_extension_dict["model"] == "policy_root": - raise RootExtensionNotInitialized() - except KeyError: - pass - return ROOT_EXTENSION_ID + # def get_root_extension(self, args, kwargs): + # if not ROOT_EXTENSION_ID: + # global ROOT_EXTENSION_MODEL, ROOT_EXTENSION_ID, ADMIN_ID + # try: + # # if it is the first time we passed here, the root extension may be not initialized + # # specially during unittest. So we raise RootExtensionNotInitialized to authorize the + # # current creation process + # if 'intra_extension_dict' in kwargs: + # intra_extension_dict = kwargs['intra_extension_dict'] + # else: + # intra_extension_dict = args[2] + # if isinstance(intra_extension_dict, dict) and \ + # "model" in intra_extension_dict and \ + # intra_extension_dict["model"] == "policy_root": + # raise RootExtensionNotInitialized() + # except KeyError: + # pass + # return ROOT_EXTENSION_ID def wrap(func): def wrapped(*args, **kwargs): - global ADMIN_ID, ROOT_EXTENSION_ID + # global ADMIN_ID, ROOT_EXTENSION_ID returned_value_for_func = None self = args[0] try: @@ -140,46 +140,42 @@ def enforce(action_names, object_name, **extra): intra_extension_id = None intra_admin_extension_id = None - try: - intra_root_extension_id = get_root_extension(self, args, kwargs) - # FIXME (asteroide): intra_root_extension_id is not used at all... - except RootExtensionNotInitialized: - # Root extension is not initialized, the current requested function must be the creation - # of this root extension - returned_value_for_func = func(*args, **kwargs) - # after the creation, we must update ROOT_EXTENSION_ID and ADMIN_ID - intra_extensions_dict = self.admin_api.driver.get_intra_extensions_dict() - for ext in intra_extensions_dict: - if intra_extensions_dict[ext]["model"] == ROOT_EXTENSION_MODEL: - ROOT_EXTENSION_ID = ext - break - if not ROOT_EXTENSION_ID: - raise RootExtensionUnknown() - subjects_dict = self.admin_api.driver.get_subjects_dict(returned_value_for_func['id']) - for subject_id in subjects_dict: - if subjects_dict[subject_id]["name"] == "admin": - ADMIN_ID = subject_id - break - if not ADMIN_ID: - raise RootExtensionUnknown() - # if all is OK, return values from func (creation of the root extension) - return returned_value_for_func + # try: + intra_root_extension_id = self.root_api.get_root_extension_id() + # except RootExtensionNotInitialized: + # # Root extension is not initialized, the current requested function must be the creation + # # of this root extension + # returned_value_for_func = func(*args, **kwargs) + # # after the creation, we must update ROOT_EXTENSION_ID and ADMIN_ID + # intra_extensions_dict = self.admin_api.driver.get_intra_extensions_dict() + # for ext in intra_extensions_dict: + # if intra_extensions_dict[ext]["model"] == ROOT_EXTENSION_MODEL: + # ROOT_EXTENSION_ID = ext + # break + # if not ROOT_EXTENSION_ID: + # raise RootExtensionUnknown() + # subjects_dict = self.admin_api.driver.get_subjects_dict(returned_value_for_func['id']) + # for subject_id in subjects_dict: + # if subjects_dict[subject_id]["name"] == "admin": + # ADMIN_ID = subject_id + # break + # if not ADMIN_ID: + # raise RootExtensionUnknown() + # # if all is OK, return values from func (creation of the root extension) + # return returned_value_for_func try: intra_extension_id = args[2] except IndexError: - print("IndexError", kwargs) if 'intra_extension_id' in kwargs: intra_extension_id = kwargs['intra_extension_id'] else: - print("in else", intra_root_extension_id) intra_extension_id = intra_root_extension_id - if ADMIN_ID and user_id == ADMIN_ID: + if user_id == self.root_api.get_root_admin_id(): # TODO: check if there is no security hole here returned_value_for_func = func(*args, **kwargs) else: intra_extensions_dict = self.admin_api.driver.get_intra_extensions_dict() - print(intra_extension_id, intra_extensions_dict) if intra_extension_id not in intra_extensions_dict: raise IntraExtensionUnknown() tenants_dict = self.tenant_api.driver.get_tenants_dict() @@ -213,7 +209,10 @@ def enforce(action_names, object_name, **extra): # if we found the object in intra_root_extension_id, so we change the intra_admin_extension_id # into intra_root_extension_id and we modify the ID of the subject subjects_dict = self.admin_api.driver.get_subjects_dict(intra_admin_extension_id) - subject_name = subjects_dict[user_id]["name"] + try: + subject_name = subjects_dict[user_id]["name"] + except KeyError: + raise SubjectUnknown() intra_admin_extension_id = intra_root_extension_id subjects_dict = self.admin_api.driver.get_subjects_dict(intra_admin_extension_id) user_id = None @@ -221,7 +220,7 @@ def enforce(action_names, object_name, **extra): if subjects_dict[_subject_id]["name"] == subject_name: user_id = _subject_id if not user_id: - raise SubjectUnknown("Subject Unknown for Root intraExtension...") + raise SubjectUnknown("Subject {} Unknown for Root IntraExtension...".format(subject_name)) if type(_action_name_list) in (str, unicode): action_name_list = (_action_name_list, ) else: @@ -256,9 +255,11 @@ def enforce(action_names, object_name, **extra): @dependency.provider('configuration_api') -@dependency.requires('moonlog_api', 'admin_api', 'tenant_api') +@dependency.requires('moonlog_api', 'admin_api', 'tenant_api', 'root_api') class ConfigurationManager(manager.Manager): + driver_namespace = 'keystone.moon.configuration' + def __init__(self): super(ConfigurationManager, self).__init__(CONF.moon.configuration_driver) @@ -278,7 +279,7 @@ class ConfigurationManager(manager.Manager): def get_policy_template_id_from_name(self, user_id, policy_template_name): policy_templates_dict = self.driver.get_policy_templates_dict() for policy_template_id in policy_templates_dict: - if policy_templates_dict[policy_template_id]['name'] is policy_template_name: + if policy_templates_dict[policy_template_id]['name'] == policy_template_name: return policy_template_id return None @@ -298,7 +299,7 @@ class ConfigurationManager(manager.Manager): def get_aggregation_algorithm_id_from_name(self, user_id, aggregation_algorithm_name): aggregation_algorithms_dict = self.driver.get_aggregation_algorithms_dict() for aggregation_algorithm_id in aggregation_algorithms_dict: - if aggregation_algorithms_dict[aggregation_algorithm_id]['name'] is aggregation_algorithm_name: + if aggregation_algorithms_dict[aggregation_algorithm_id]['name'] == aggregation_algorithm_name: return aggregation_algorithm_id return None @@ -318,15 +319,17 @@ class ConfigurationManager(manager.Manager): def get_sub_meta_rule_algorithm_id_from_name(self, sub_meta_rule_algorithm_name): sub_meta_rule_algorithms_dict = self.driver.get_sub_meta_rule_algorithms_dict() for sub_meta_rule_algorithm_id in sub_meta_rule_algorithms_dict: - if sub_meta_rule_algorithms_dict[sub_meta_rule_algorithm_id]['name'] is sub_meta_rule_algorithm_name: + if sub_meta_rule_algorithms_dict[sub_meta_rule_algorithm_id]['name'] == sub_meta_rule_algorithm_name: return sub_meta_rule_algorithm_id return None @dependency.provider('tenant_api') -@dependency.requires('moonlog_api', 'admin_api', 'configuration_api') +@dependency.requires('moonlog_api', 'admin_api', 'configuration_api', 'root_api', 'resource_api') class TenantManager(manager.Manager): + driver_namespace = 'keystone.moon.tenant' + def __init__(self): super(TenantManager, self).__init__(CONF.moon.tenant_driver) @@ -348,38 +351,66 @@ class TenantManager(manager.Manager): """ return self.driver.get_tenants_dict() + def __get_keystone_tenant_dict(self, tenant_id="", tenant_name=""): + tenants = self.resource_api.list_projects() + for tenant in tenants: + if tenant_id and tenant_id == tenant['id']: + return tenant + if tenant_name and tenant_name == tenant['name']: + return tenant + if not tenant_id: + tenant_id = uuid4().hex + if not tenant_name: + tenant_name = tenant_id + tenant = { + "id": tenant_id, + "name": tenant_name, + "description": "Auto generated tenant from Moon platform", + "enabled": True, + "domain_id": "default" + } + keystone_tenant = self.resource_api.create_project(tenant["id"], tenant) + return keystone_tenant + @filter_input @enforce(("read", "write"), "tenants") def add_tenant_dict(self, user_id, tenant_dict): tenants_dict = self.driver.get_tenants_dict() for tenant_id in tenants_dict: - if tenants_dict[tenant_id]['name'] is tenant_dict['name']: + if tenants_dict[tenant_id]['name'] == tenant_dict['name']: raise TenantAddedNameExisting() + # Check (and eventually sync) Keystone tenant + if 'id' not in tenant_dict: + tenant_dict['id'] = None + keystone_tenant = self.__get_keystone_tenant_dict(tenant_dict['id'], tenant_dict['name']) + tenant_dict.update(keystone_tenant) # Sync users between intra_authz_extension and intra_admin_extension if tenant_dict['intra_admin_extension_id']: if not tenant_dict['intra_authz_extension_id']: raise TenantNoIntraAuthzExtension() - authz_subjects_dict = self.admin_api.get_subjects_dict(ADMIN_ID, tenant_dict['intra_authz_extension_id']) - admin_subjects_dict = self.admin_api.get_subjects_dict(ADMIN_ID, tenant_dict['intra_admin_extension_id']) - for _subject_id in authz_subjects_dict: - if _subject_id not in admin_subjects_dict: - self.admin_api.add_subject_dict(ADMIN_ID, tenant_dict['intra_admin_extension_id'], authz_subjects_dict[_subject_id]) - for _subject_id in admin_subjects_dict: - if _subject_id not in authz_subjects_dict: - self.admin_api.add_subject_dict(ADMIN_ID, tenant_dict['intra_authz_extension_id'], admin_subjects_dict[_subject_id]) - - # TODO (dthom): check whether we can replace the below code by the above one - # authz_subjects_dict = self.admin_api.get_subjects_dict(ADMIN_ID, tenant_dict['intra_authz_extension_id']) - # authz_subject_names_list = [authz_subjects_dict[subject_id]["name"] for subject_id in authz_subjects_dict] - # admin_subjects_dict = self.admin_api.get_subjects_dict(ADMIN_ID, tenant_dict['intra_admin_extension_id']) - # admin_subject_names_list = [admin_subjects_dict[subject_id]["name"] for subject_id in admin_subjects_dict] + # authz_subjects_dict = self.admin_api.get_subjects_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_authz_extension_id']) + # admin_subjects_dict = self.admin_api.get_subjects_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_admin_extension_id']) # for _subject_id in authz_subjects_dict: - # if authz_subjects_dict[_subject_id]["name"] not in admin_subject_names_list: - # self.admin_api.add_subject_dict(ADMIN_ID, tenant_dict['intra_admin_extension_id'], authz_subjects_dict[_subject_id]) + # if _subject_id not in admin_subjects_dict: + # self.admin_api.add_subject_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_admin_extension_id'], authz_subjects_dict[_subject_id]) # for _subject_id in admin_subjects_dict: - # if admin_subjects_dict[_subject_id]["name"] not in authz_subject_names_list: - # self.admin_api.add_subject_dict(ADMIN_ID, tenant_dict['intra_authz_extension_id'], admin_subjects_dict[_subject_id]) + # if _subject_id not in authz_subjects_dict: + # self.admin_api.add_subject_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_authz_extension_id'], admin_subjects_dict[_subject_id]) + + # TODO (ateroide): check whether we can replace the below code by the above one + # NOTE (ateroide): at a first glance: no, subject_id changes depending on which intra_extesion is used + # we must use name which is constant. + authz_subjects_dict = self.admin_api.get_subjects_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_authz_extension_id']) + authz_subject_names_list = [authz_subjects_dict[subject_id]["name"] for subject_id in authz_subjects_dict] + admin_subjects_dict = self.admin_api.get_subjects_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_admin_extension_id']) + admin_subject_names_list = [admin_subjects_dict[subject_id]["name"] for subject_id in admin_subjects_dict] + for _subject_id in authz_subjects_dict: + if authz_subjects_dict[_subject_id]["name"] not in admin_subject_names_list: + self.admin_api.add_subject_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_admin_extension_id'], authz_subjects_dict[_subject_id]) + for _subject_id in admin_subjects_dict: + if admin_subjects_dict[_subject_id]["name"] not in authz_subject_names_list: + self.admin_api.add_subject_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_authz_extension_id'], admin_subjects_dict[_subject_id]) return self.driver.add_tenant_dict(tenant_dict['id'], tenant_dict) @@ -409,52 +440,24 @@ class TenantManager(manager.Manager): if tenant_dict['intra_admin_extension_id']: if not tenant_dict['intra_authz_extension_id']: raise TenantNoIntraAuthzExtension - authz_subjects_dict = self.admin_api.get_subjects_dict(ADMIN_ID, tenant_dict['intra_authz_extension_id']) - admin_subjects_dict = self.admin_api.get_subjects_dict(ADMIN_ID, tenant_dict['intra_admin_extension_id']) + authz_subjects_dict = self.admin_api.get_subjects_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_authz_extension_id']) + authz_subject_names_list = [authz_subjects_dict[subject_id]["name"] for subject_id in authz_subjects_dict] + admin_subjects_dict = self.admin_api.get_subjects_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_admin_extension_id']) + admin_subject_names_list = [admin_subjects_dict[subject_id]["name"] for subject_id in admin_subjects_dict] for _subject_id in authz_subjects_dict: - if _subject_id not in admin_subjects_dict: - self.admin_api.add_subject_dict(ADMIN_ID, tenant_dict['intra_admin_extension_id'], authz_subjects_dict[_subject_id]) + if authz_subjects_dict[_subject_id]["name"] not in admin_subject_names_list: + self.admin_api.add_subject_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_admin_extension_id'], authz_subjects_dict[_subject_id]) for _subject_id in admin_subjects_dict: - if _subject_id not in authz_subjects_dict: - self.admin_api.add_subject_dict(ADMIN_ID, tenant_dict['intra_authz_extension_id'], admin_subjects_dict[_subject_id]) + if admin_subjects_dict[_subject_id]["name"] not in authz_subject_names_list: + self.admin_api.add_subject_dict(self.root_api.get_root_admin_id(), tenant_dict['intra_authz_extension_id'], admin_subjects_dict[_subject_id]) return self.driver.set_tenant_dict(tenant_id, tenant_dict) - # TODO (dthom): move the following 2 functions to perimeter functions - @filter_input - def get_subject_dict_from_keystone_id(self, tenant_id, intra_extension_id, keystone_id): - tenants_dict = self.driver.get_tenants_dict() - if tenant_id not in tenants_dict: - raise TenantUnknown() - if intra_extension_id not in (tenants_dict[tenant_id]['intra_authz_extension_id'], - tenants_dict[tenant_id]['intra_admin_extension_id'], ): - raise IntraExtensionUnknown() - # Note (asteroide): We used ADMIN_ID because the user requesting this information may only know his keystone_id - # and not the subject ID in the requested intra_extension. - subjects_dict = self.admin_api.get_subjects_dict(ADMIN_ID, intra_extension_id) - for subject_id in subjects_dict: - if keystone_id is subjects_dict[subject_id]['keystone_id']: - return {subject_id: subjects_dict[subject_id]} - - @filter_input - def get_subject_dict_from_keystone_name(self, tenant_id, intra_extension_id, keystone_name): - tenants_dict = self.driver.get_tenants_dict() - if tenant_id not in tenants_dict: - raise TenantUnknown() - if intra_extension_id not in (tenants_dict[tenant_id]['intra_authz_extension_id'], - tenants_dict[tenant_id]['intra_admin_extension_id'], ): - raise IntraExtensionUnknown() - # Note (asteroide): We used ADMIN_ID because the user requesting this information may only know his - # keystone_name and not the subject ID in the requested intra_extension. - subjects_dict = self.admin_api.get_subjects_dict(ADMIN_ID, intra_extension_id) - for subject_id in subjects_dict: - if keystone_name is subjects_dict[subject_id]['keystone_name']: - return {subject_id: subjects_dict[subject_id]} - - -@dependency.requires('identity_api', 'tenant_api', 'configuration_api', 'authz_api', 'admin_api', 'moonlog_api') +@dependency.requires('identity_api', 'tenant_api', 'configuration_api', 'authz_api', 'admin_api', 'moonlog_api', 'root_api') class IntraExtensionManager(manager.Manager): + driver_namespace = 'keystone.moon.intraextension' + def __init__(self): driver = CONF.moon.intraextension_driver super(IntraExtensionManager, self).__init__(driver) @@ -501,6 +504,7 @@ class IntraExtensionManager(manager.Manager): authz_buffer['subject_assignments'] = dict() authz_buffer['object_assignments'] = dict() authz_buffer['action_assignments'] = dict() + for _subject_category in meta_data_dict['subject_categories']: authz_buffer['subject_assignments'][_subject_category] = list(subject_assignment_dict[_subject_category]) for _object_category in meta_data_dict['object_categories']: @@ -543,6 +547,8 @@ class IntraExtensionManager(manager.Manager): aggregation_algorithm_id = aggregation_algorithm_dict.keys()[0] if aggregation_algorithm_dict[aggregation_algorithm_id]['name'] == 'all_true': decision = all_true(decision_buffer) + elif aggregation_algorithm_dict[aggregation_algorithm_id]['name'] == 'one_true': + decision = one_true(decision_buffer) if not decision: raise AuthzException("{} {}-{}-{}".format(intra_extension_id, subject_id, action_id, object_id)) return decision @@ -607,7 +613,12 @@ class IntraExtensionManager(manager.Manager): subject_dict = dict() # We suppose that all subjects can be mapped to a true user in Keystone for _subject in json_perimeter['subjects']: - keystone_user = self.identity_api.get_user_by_name(_subject, "default") + try: + keystone_user = self.identity_api.get_user_by_name(_subject, "default") + except exception.UserNotFound: + # TODO (asteroide): must add a configuration option to allow that exception + # maybe a debug option for unittest + keystone_user = {'id': "", 'name': _subject} subject_id = uuid4().hex subject_dict[subject_id] = keystone_user subject_dict[subject_id]['keystone_id'] = keystone_user["id"] @@ -774,8 +785,6 @@ class IntraExtensionManager(manager.Manager): sub_rule_id = self.driver.get_uuid_from_name(intra_extension_dict["id"], sub_rule_name, self.driver.SUB_META_RULE) - # print(sub_rule_name) - # print(self.get_sub_meta_rule_relations("admin", ie["id"])) # if sub_rule_name not in self.get_sub_meta_rule_relations("admin", ie["id"])["sub_meta_rule_relations"]: # raise IntraExtensionException("Bad sub_rule_name name {} in rules".format(sub_rule_name)) rules[sub_rule_id] = list() @@ -833,6 +842,32 @@ class IntraExtensionManager(manager.Manager): self.__load_rule_file(ie_dict, template_dir) return ref + def load_root_intra_extension_dict(self, policy_template): + # Note (asteroide): Only one root Extension is authorized + # and this extension is created at the very beginning of the server + # so we don't need to use enforce here + for key in self.driver.get_intra_extensions_dict(): + # Note (asteroide): if there is at least one Intra Extension, it implies that + # the Root Intra Extension had already been created... + return + ie_dict = dict() + ie_dict['id'] = uuid4().hex + ie_dict["name"] = "policy_root" + ie_dict["model"] = filter_input(policy_template) + ie_dict["genre"] = "admin" + ie_dict["description"] = "policy_root" + ref = self.driver.set_intra_extension_dict(ie_dict['id'], ie_dict) + self.moonlog_api.debug("Creation of IE: {}".format(ref)) + # read the template given by "model" and populate default variables + template_dir = os.path.join(CONF.moon.policy_directory, ie_dict["model"]) + self.__load_metadata_file(ie_dict, template_dir) + self.__load_perimeter_file(ie_dict, template_dir) + self.__load_scope_file(ie_dict, template_dir) + self.__load_assignment_file(ie_dict, template_dir) + self.__load_metarule_file(ie_dict, template_dir) + self.__load_rule_file(ie_dict, template_dir) + return ref + @enforce("read", "intra_extensions") def get_intra_extension_dict(self, user_id, intra_extension_id): """ @@ -858,7 +893,7 @@ class IntraExtensionManager(manager.Manager): for rule_id in self.driver.get_rules_dict(intra_extension_id, sub_meta_rule_id): self.driver.del_rule(intra_extension_id, sub_meta_rule_id, rule_id) self.driver.del_sub_meta_rule(intra_extension_id, sub_meta_rule_id) - for aggregation_algorithm_id in self.driver.get_aggregation_algorithms_dict(intra_extension_id): + for aggregation_algorithm_id in self.driver.get_aggregation_algorithm_dict(intra_extension_id): self.driver.del_aggregation_algorithm(intra_extension_id, aggregation_algorithm_id) for subject_id in self.driver.get_subjects_dict(intra_extension_id): self.driver.del_subject(intra_extension_id, subject_id) @@ -1049,6 +1084,7 @@ class IntraExtensionManager(manager.Manager): def add_subject_dict(self, user_id, intra_extension_id, subject_dict): subjects_dict = self.driver.get_subjects_dict(intra_extension_id) for subject_id in subjects_dict: + print(subjects_dict[subject_id]["name"], subject_dict['name']) if subjects_dict[subject_id]["name"] == subject_dict['name']: raise SubjectNameExisting() # Next line will raise an error if user is not present in Keystone database @@ -1091,6 +1127,37 @@ class IntraExtensionManager(manager.Manager): return self.driver.set_subject_dict(intra_extension_id, subject_dict["id"], subject_dict) @filter_input + def get_subject_dict_from_keystone_id(self, tenant_id, intra_extension_id, keystone_id): + tenants_dict = self.tenant_api.driver.get_tenants_dict() + if tenant_id not in tenants_dict: + raise TenantUnknown() + if intra_extension_id not in (tenants_dict[tenant_id]['intra_authz_extension_id'], + tenants_dict[tenant_id]['intra_admin_extension_id'], ): + raise IntraExtensionUnknown() + # Note (asteroide): We used self.root_api.get_root_admin_id() because the user requesting this information + # may only know his keystone_id and not the subject ID in the requested intra_extension. + subjects_dict = self.get_subjects_dict(self.root_api.get_root_admin_id(), intra_extension_id) + for subject_id in subjects_dict: + if keystone_id == subjects_dict[subject_id]['keystone_id']: + return {subject_id: subjects_dict[subject_id]} + + @filter_input + def get_subject_dict_from_keystone_name(self, tenant_id, intra_extension_id, keystone_name): + tenants_dict = self.tenant_api.driver.get_tenants_dict() + if tenant_id not in tenants_dict: + raise TenantUnknown() + if intra_extension_id not in (tenants_dict[tenant_id]['intra_authz_extension_id'], + tenants_dict[tenant_id]['intra_admin_extension_id'], ): + raise IntraExtensionUnknown() + # Note (asteroide): We used self.root_api.get_root_admin_id() because the user requesting this information + # may only know his keystone_name and not the subject ID in the requested intra_extension. + subjects_dict = self.get_subjects_dict(self.root_api.get_root_admin_id(), intra_extension_id) + for subject_id in subjects_dict: + if keystone_name == subjects_dict[subject_id]['keystone_name']: + return {subject_id: subjects_dict[subject_id]} + + + @filter_input @enforce("read", "objects") def get_objects_dict(self, user_id, intra_extension_id): return self.driver.get_objects_dict(intra_extension_id) @@ -1539,7 +1606,7 @@ class IntraExtensionManager(manager.Manager): @enforce(("read", "write"), "aggregation_algorithm") def set_aggregation_algorithm_dict(self, user_id, intra_extension_id, aggregation_algorithm_id, aggregation_algorithm_dict): if aggregation_algorithm_id: - if aggregation_algorithm_id not in self.configuration_api.get_aggregation_algorithms_dict(ADMIN_ID): + if aggregation_algorithm_id not in self.configuration_api.get_aggregation_algorithms_dict(self.root_api.get_root_admin_id()): raise AggregationAlgorithmUnknown() else: aggregation_algorithm_id = uuid4().hex @@ -1577,7 +1644,9 @@ class IntraExtensionManager(manager.Manager): sub_meta_rule_dict['action_categories'] == sub_meta_rules_dict[_sub_meta_rule_id]["action_categories"] and \ sub_meta_rule_dict['algorithm'] == sub_meta_rules_dict[_sub_meta_rule_id]["algorithm"]: raise SubMetaRuleExisting() - if sub_meta_rule_dict['algorithm'] not in self.configuration_api.get_sub_meta_rule_algorithms_dict(user_id): + algorithm_names = map(lambda x: x['name'], + self.configuration_api.get_sub_meta_rule_algorithms_dict(user_id).values()) + if sub_meta_rule_dict['algorithm'] not in algorithm_names: raise SubMetaRuleAlgorithmNotExisting() sub_meta_rule_id = uuid4().hex # TODO (dthom): add new sub-meta-rule to rule dict @@ -1682,10 +1751,10 @@ class IntraExtensionAuthzManager(IntraExtensionManager): elif genre == "admin": genre = "intra_admin_extension_id" - tenants_dict = self.tenant_api.get_tenants_dict(ADMIN_ID) + tenants_dict = self.tenant_api.get_tenants_dict(self.root_api.get_root_admin_id()) tenant_id = None for _tenant_id in tenants_dict: - if tenants_dict[_tenant_id]["name"] is tenant_name: + if tenants_dict[_tenant_id]["name"] == tenant_name: tenant_id = _tenant_id break if not tenant_id: @@ -1697,8 +1766,9 @@ class IntraExtensionAuthzManager(IntraExtensionManager): subjects_dict = self.driver.get_subjects_dict(intra_extension_id) subject_id = None for _subject_id in subjects_dict: - if subjects_dict[_subject_id]['keystone_name'] is subject_name: - subject_id = subjects_dict[_subject_id]['keystone_id'] + if subjects_dict[_subject_id]['keystone_name'] == subject_name: + # subject_id = subjects_dict[_subject_id]['keystone_id'] + subject_id = _subject_id break if not subject_id: raise SubjectUnknown() @@ -1725,7 +1795,7 @@ class IntraExtensionAuthzManager(IntraExtensionManager): def add_subject_dict(self, user_id, intra_extension_id, subject_dict): subject = super(IntraExtensionAuthzManager, self).add_subject_dict(user_id, intra_extension_id, subject_dict) subject_id, subject_value = subject.iteritems().next() - tenants_dict = self.tenant_api.get_tenants_dict(ADMIN_ID) + tenants_dict = self.tenant_api.get_tenants_dict(self.root_api.get_root_admin_id()) for tenant_id in tenants_dict: if tenants_dict[tenant_id]["intra_authz_extension_id"] == intra_extension_id: _subjects = self.driver.get_subjects_dict(tenants_dict[tenant_id]["intra_admin_extension_id"]) @@ -1742,7 +1812,7 @@ class IntraExtensionAuthzManager(IntraExtensionManager): def del_subject(self, user_id, intra_extension_id, subject_id): subject_name = self.driver.get_subjects_dict(intra_extension_id)[subject_id]["name"] super(IntraExtensionAuthzManager, self).del_subject(user_id, intra_extension_id, subject_id) - tenants_dict = self.tenant_api.get_tenants_dict(ADMIN_ID) + tenants_dict = self.tenant_api.get_tenants_dict(self.root_api.get_root_admin_id()) for tenant_id in tenants_dict: if tenants_dict[tenant_id]["intra_authz_extension_id"] == intra_extension_id: subject_id = self.driver.get_uuid_from_name(tenants_dict[tenant_id]["intra_admin_extension_id"], @@ -1760,7 +1830,7 @@ class IntraExtensionAuthzManager(IntraExtensionManager): def set_subject_dict(self, user_id, intra_extension_id, subject_id, subject_dict): subject = super(IntraExtensionAuthzManager, self).set_subject_dict(user_id, intra_extension_id, subject_dict) subject_id, subject_value = subject.iteritems().next() - tenants_dict = self.tenant_api.get_tenants_dict(ADMIN_ID) + tenants_dict = self.tenant_api.get_tenants_dict(self.root_api.get_root_admin_id()) for tenant_id in tenants_dict: if tenants_dict[tenant_id]["intra_authz_extension_id"] == intra_extension_id: self.driver.set_subject_dict(tenants_dict[tenant_id]["intra_admin_extension_id"], uuid4().hex, subject_value) @@ -1770,110 +1840,110 @@ class IntraExtensionAuthzManager(IntraExtensionManager): break return subject - # def add_subject_category(self, user_id, intra_extension_id, subject_category_dict): - # raise AuthzException() - # - # def del_subject_category(self, user_id, intra_extension_id, subject_category_id): - # raise AuthzException() - # - # def set_subject_category(self, user_id, intra_extension_id, subject_category_id, subject_category_dict): - # raise AuthzException() - # - # def add_object_category(self, user_id, intra_extension_id, object_category_dict): - # raise AuthzException() - # - # def del_object_category(self, user_id, intra_extension_id, object_category_id): - # raise AuthzException() - # - # def add_action_category(self, user_id, intra_extension_id, action_category_name): - # raise AuthzException() - # - # def del_action_category(self, user_id, intra_extension_id, action_category_id): - # raise AuthzException() - # - # def add_object_dict(self, user_id, intra_extension_id, object_name): - # raise AuthzException() - # - # def set_object_dict(self, user_id, intra_extension_id, object_id, object_dict): - # raise AuthzException() - # - # def del_object(self, user_id, intra_extension_id, object_id): - # raise AuthzException() - # - # def add_action_dict(self, user_id, intra_extension_id, action_name): - # raise AuthzException() - # - # def set_action_dict(self, user_id, intra_extension_id, action_id, action_dict): - # raise AuthzException() - # - # def del_action(self, user_id, intra_extension_id, action_id): - # raise AuthzException() - # - # def add_subject_scope_dict(self, user_id, intra_extension_id, subject_category_id, subject_scope_dict): - # raise AuthzException() - # - # def del_subject_scope(self, user_id, intra_extension_id, subject_category_id, subject_scope_id): - # raise AuthzException() - # - # def set_subject_scope_dict(self, user_id, intra_extension_id, subject_category_id, subject_scope_id, subject_scope_name): - # raise AuthzException() - # - # def add_object_scope_dict(self, user_id, intra_extension_id, object_category_id, object_scope_name): - # raise AuthzException() - # - # def del_object_scope(self, user_id, intra_extension_id, object_category_id, object_scope_id): - # raise AuthzException() - # - # def set_object_scope_dict(self, user_id, intra_extension_id, object_category_id, object_scope_id, object_scope_name): - # raise AuthzException() - # - # def add_action_scope_dict(self, user_id, intra_extension_id, action_category_id, action_scope_name): - # raise AuthzException() - # - # def del_action_scope(self, user_id, intra_extension_id, action_category_id, action_scope_id): - # raise AuthzException() - # - # def add_subject_assignment_list(self, user_id, intra_extension_id, subject_id, subject_category_id, subject_scope_id): - # raise AuthzException() - # - # def del_subject_assignment(self, user_id, intra_extension_id, subject_id, subject_category_id, subject_scope_id): - # raise AuthzException() - # - # def add_object_assignment_list(self, user_id, intra_extension_id, object_id, object_category_id, object_scope_id): - # raise AuthzException() - # - # def del_object_assignment(self, user_id, intra_extension_id, object_id, object_category_id, object_scope_id): - # raise AuthzException() - # - # def add_action_assignment_list(self, user_id, intra_extension_id, action_id, action_category_id, action_scope_id): - # raise AuthzException() - # - # def del_action_assignment(self, user_id, intra_extension_id, action_id, action_category_id, action_scope_id): - # raise AuthzException() - # - # def set_aggregation_algorithm_dict(self, user_id, intra_extension_id, aggregation_algorithm_id, aggregation_algorithm_dict): - # raise AuthzException() - # - # def del_aggregation_algorithm_dict(self, user_id, intra_extension_id, aggregation_algorithm_id): - # raise AuthzException() - # - # def add_sub_meta_rule_dict(self, user_id, intra_extension_id, sub_meta_rule_dict): - # raise AuthzException() - # - # def del_sub_meta_rule(self, user_id, intra_extension_id, sub_meta_rule_id): - # raise AuthzException() - # - # def set_sub_meta_rule_dict(self, user_id, intra_extension_id, sub_meta_rule_id, sub_meta_rule_dict): - # raise AuthzException() - # - # def add_rule_dict(self, user_id, intra_extension_id, sub_meta_rule_id, rule_list): - # raise AuthzException() - # - # def del_rule(self, user_id, intra_extension_id, sub_meta_rule_id, rule_id): - # raise AuthzException() - # - # def set_rule_dict(self, user_id, intra_extension_id, sub_meta_rule_id, rule_id, rule_list): - # raise AuthzException() + def add_subject_category(self, user_id, intra_extension_id, subject_category_dict): + raise AuthzException() + + def del_subject_category(self, user_id, intra_extension_id, subject_category_id): + raise AuthzException() + + def set_subject_category(self, user_id, intra_extension_id, subject_category_id, subject_category_dict): + raise AuthzException() + + def add_object_category(self, user_id, intra_extension_id, object_category_dict): + raise AuthzException() + + def del_object_category(self, user_id, intra_extension_id, object_category_id): + raise AuthzException() + + def add_action_category(self, user_id, intra_extension_id, action_category_name): + raise AuthzException() + + def del_action_category(self, user_id, intra_extension_id, action_category_id): + raise AuthzException() + + def add_object_dict(self, user_id, intra_extension_id, object_name): + raise AuthzException() + + def set_object_dict(self, user_id, intra_extension_id, object_id, object_dict): + raise AuthzException() + + def del_object(self, user_id, intra_extension_id, object_id): + raise AuthzException() + + def add_action_dict(self, user_id, intra_extension_id, action_name): + raise AuthzException() + + def set_action_dict(self, user_id, intra_extension_id, action_id, action_dict): + raise AuthzException() + + def del_action(self, user_id, intra_extension_id, action_id): + raise AuthzException() + + def add_subject_scope_dict(self, user_id, intra_extension_id, subject_category_id, subject_scope_dict): + raise AuthzException() + + def del_subject_scope(self, user_id, intra_extension_id, subject_category_id, subject_scope_id): + raise AuthzException() + + def set_subject_scope_dict(self, user_id, intra_extension_id, subject_category_id, subject_scope_id, subject_scope_name): + raise AuthzException() + + def add_object_scope_dict(self, user_id, intra_extension_id, object_category_id, object_scope_name): + raise AuthzException() + + def del_object_scope(self, user_id, intra_extension_id, object_category_id, object_scope_id): + raise AuthzException() + + def set_object_scope_dict(self, user_id, intra_extension_id, object_category_id, object_scope_id, object_scope_name): + raise AuthzException() + + def add_action_scope_dict(self, user_id, intra_extension_id, action_category_id, action_scope_name): + raise AuthzException() + + def del_action_scope(self, user_id, intra_extension_id, action_category_id, action_scope_id): + raise AuthzException() + + def add_subject_assignment_list(self, user_id, intra_extension_id, subject_id, subject_category_id, subject_scope_id): + raise AuthzException() + + def del_subject_assignment(self, user_id, intra_extension_id, subject_id, subject_category_id, subject_scope_id): + raise AuthzException() + + def add_object_assignment_list(self, user_id, intra_extension_id, object_id, object_category_id, object_scope_id): + raise AuthzException() + + def del_object_assignment(self, user_id, intra_extension_id, object_id, object_category_id, object_scope_id): + raise AuthzException() + + def add_action_assignment_list(self, user_id, intra_extension_id, action_id, action_category_id, action_scope_id): + raise AuthzException() + + def del_action_assignment(self, user_id, intra_extension_id, action_id, action_category_id, action_scope_id): + raise AuthzException() + + def set_aggregation_algorithm_dict(self, user_id, intra_extension_id, aggregation_algorithm_id, aggregation_algorithm_dict): + raise AuthzException() + + def del_aggregation_algorithm_dict(self, user_id, intra_extension_id, aggregation_algorithm_id): + raise AuthzException() + + def add_sub_meta_rule_dict(self, user_id, intra_extension_id, sub_meta_rule_dict): + raise AuthzException() + + def del_sub_meta_rule(self, user_id, intra_extension_id, sub_meta_rule_id): + raise AuthzException() + + def set_sub_meta_rule_dict(self, user_id, intra_extension_id, sub_meta_rule_id, sub_meta_rule_dict): + raise AuthzException() + + def add_rule_dict(self, user_id, intra_extension_id, sub_meta_rule_id, rule_list): + raise AuthzException() + + def del_rule(self, user_id, intra_extension_id, sub_meta_rule_id, rule_id): + raise AuthzException() + + def set_rule_dict(self, user_id, intra_extension_id, sub_meta_rule_id, rule_id, rule_list): + raise AuthzException() @dependency.provider('admin_api') @@ -1885,7 +1955,7 @@ class IntraExtensionAdminManager(IntraExtensionManager): def add_subject_dict(self, user_id, intra_extension_id, subject_dict): subject = super(IntraExtensionAdminManager, self).add_subject_dict(user_id, intra_extension_id, subject_dict) subject_id, subject_value = subject.iteritems().next() - tenants_dict = self.tenant_api.get_tenants_dict(ADMIN_ID) + tenants_dict = self.tenant_api.get_tenants_dict(self.root_api.get_root_admin_id()) for tenant_id in tenants_dict: if tenants_dict[tenant_id]["intra_authz_extension_id"] == intra_extension_id: _subjects = self.driver.get_subjects_dict(tenants_dict[tenant_id]["intra_admin_extension_id"]) @@ -1902,7 +1972,7 @@ class IntraExtensionAdminManager(IntraExtensionManager): def del_subject(self, user_id, intra_extension_id, subject_id): subject_name = self.driver.get_subjects_dict(intra_extension_id)[subject_id]["name"] super(IntraExtensionAdminManager, self).del_subject(user_id, intra_extension_id, subject_id) - tenants_dict = self.tenant_api.get_tenants_dict(ADMIN_ID) + tenants_dict = self.tenant_api.get_tenants_dict(self.root_api.get_root_admin_id()) for tenant_id in tenants_dict: if tenants_dict[tenant_id]["intra_authz_extension_id"] == intra_extension_id: subject_id = self.driver.get_uuid_from_name(tenants_dict[tenant_id]["intra_admin_extension_id"], @@ -1920,7 +1990,7 @@ class IntraExtensionAdminManager(IntraExtensionManager): def set_subject_dict(self, user_id, intra_extension_id, subject_id, subject_dict): subject = super(IntraExtensionAdminManager, self).set_subject_dict(user_id, intra_extension_id, subject_dict) subject_id, subject_value = subject.iteritems().next() - tenants_dict = self.tenant_api.get_tenants_dict(ADMIN_ID) + tenants_dict = self.tenant_api.get_tenants_dict(self.root_api.get_root_admin_id()) for tenant_id in tenants_dict: if tenants_dict[tenant_id]["intra_authz_extension_id"] == intra_extension_id: self.driver.set_subject_dict(tenants_dict[tenant_id]["intra_admin_extension_id"], uuid4().hex, subject_value) @@ -1931,29 +2001,78 @@ class IntraExtensionAdminManager(IntraExtensionManager): return subject def add_object_dict(self, user_id, intra_extension_id, object_name): - raise ObjectsWriteNoAuthorized() + if "admin" == self.get_intra_extension_dict(self.root_api.get_root_admin_id(), intra_extension_id)['genre']: + raise ObjectsWriteNoAuthorized() + return super(IntraExtensionAdminManager, self).add_object_dict(user_id, intra_extension_id, object_name) def set_object_dict(self, user_id, intra_extension_id, object_id, object_dict): - raise ObjectsWriteNoAuthorized() + if "admin" == self.get_intra_extension_dict(self.root_api.get_root_admin_id(), intra_extension_id)['genre']: + raise ObjectsWriteNoAuthorized() + return super(IntraExtensionAdminManager, self).set_object_dict(user_id, intra_extension_id, object_id, object_dict) def del_object(self, user_id, intra_extension_id, object_id): - raise ObjectsWriteNoAuthorized() + if "admin" == self.get_intra_extension_dict(self.root_api.get_root_admin_id(), intra_extension_id)['genre']: + raise ObjectsWriteNoAuthorized() + return super(IntraExtensionAdminManager, self).del_object(user_id, intra_extension_id, object_id) def add_action_dict(self, user_id, intra_extension_id, action_name): - raise ActionsWriteNoAuthorized() + if "admin" == self.get_intra_extension_dict(self.root_api.get_root_admin_id(), intra_extension_id)['genre']: + raise ActionsWriteNoAuthorized() + return super(IntraExtensionAdminManager, self).add_action_dict(user_id, intra_extension_id, action_name) def set_action_dict(self, user_id, intra_extension_id, action_id, action_dict): - raise ActionsWriteNoAuthorized() + if "admin" == self.get_intra_extension_dict(self.root_api.get_root_admin_id(), intra_extension_id)['genre']: + raise ActionsWriteNoAuthorized() + return super(IntraExtensionAdminManager, self).set_action_dict(user_id, intra_extension_id, action_id, action_dict) def del_action(self, user_id, intra_extension_id, action_id): - raise ActionsWriteNoAuthorized() + if "admin" == self.get_intra_extension_dict(self.root_api.get_root_admin_id(), intra_extension_id)['genre']: + raise ActionsWriteNoAuthorized() + return super(IntraExtensionAdminManager, self).del_action(user_id, intra_extension_id, action_id) + + +@dependency.provider('root_api') +@dependency.requires('moonlog_api', 'admin_api', 'tenant_api') +class IntraExtensionRootManager(IntraExtensionManager): + + def __init__(self): + super(IntraExtensionRootManager, self).__init__() + extensions = self.admin_api.driver.get_intra_extensions_dict() + for extension_id, extension_dict in extensions.iteritems(): + if extension_dict["model"] == CONF.moon.root_policy_directory: + self.root_extension_id = extension_id + else: + extension = self.admin_api.load_root_intra_extension_dict(CONF.moon.root_policy_directory) + self.root_extension_id = extension['id'] + self.root_admin_id = self.__compute_admin_id_for_root_extension() + + def get_root_extension_dict(self): + """ + + :return: {id: {"name": "xxx"}} + """ + return {self.root_extension_id: self.admin_api.driver.get_intra_extensions_dict()[self.root_extension_id]} + + def __compute_admin_id_for_root_extension(self): + for subject_id, subject_dict in self.admin_api.driver.get_subjects_dict(self.root_extension_id).iteritems(): + if subject_dict["name"] == "admin": + return subject_id + raise RootExtensionNotInitialized() + + def get_root_extension_id(self): + return self.root_extension_id + + def get_root_admin_id(self): + return self.root_admin_id @dependency.provider('moonlog_api') # Next line is mandatory in order to force keystone to process dependencies. -@dependency.requires('identity_api', 'tenant_api', 'configuration_api', 'authz_api', 'admin_api') +@dependency.requires('identity_api', 'tenant_api', 'configuration_api', 'authz_api', 'admin_api', 'root_api') class LogManager(manager.Manager): + driver_namespace = 'keystone.moon.log' + def __init__(self): driver = CONF.moon.log_driver super(LogManager, self).__init__(driver) diff --git a/keystone-moon/keystone/contrib/moon/extension.py b/keystone-moon/keystone/contrib/moon/extension.py deleted file mode 100644 index efee55c5..00000000 --- a/keystone-moon/keystone/contrib/moon/extension.py +++ /dev/null @@ -1,740 +0,0 @@ -# Copyright 2015 Open Platform for NFV Project, Inc. and its contributors -# This software is distributed under the terms and conditions of the 'Apache-2.0' -# license which can be found in the file 'LICENSE' in this package distribution -# or at 'http://www.apache.org/licenses/LICENSE-2.0'. - -import os.path -import copy -import json -import itertools -from uuid import uuid4 -import logging - -LOG = logging.getLogger("moon.authz") - - -class Metadata: - - def __init__(self): - self.__name = '' - self.__model = '' - self.__genre = '' - self.__description = '' - self.__subject_categories = list() - self.__object_categories = list() - self.__meta_rule = dict() - self.__meta_rule['sub_meta_rules'] = list() - self.__meta_rule['aggregation'] = '' - - def load_from_json(self, extension_setting_dir): - metadata_path = os.path.join(extension_setting_dir, 'metadata.json') - f = open(metadata_path) - json_metadata = json.load(f) - self.__name = json_metadata['name'] - self.__model = json_metadata['model'] - self.__genre = json_metadata['genre'] - self.__description = json_metadata['description'] - self.__subject_categories = copy.deepcopy(json_metadata['subject_categories']) - self.__object_categories = copy.deepcopy(json_metadata['object_categories']) - self.__meta_rule = copy.deepcopy(json_metadata['meta_rule']) - - def get_name(self): - return self.__name - - def get_genre(self): - return self.__genre - - def get_model(self): - return self.__model - - def get_subject_categories(self): - return self.__subject_categories - - def get_object_categories(self): - return self.__object_categories - - def get_meta_rule(self): - return self.__meta_rule - - def get_meta_rule_aggregation(self): - return self.__meta_rule['aggregation'] - - def get_data(self): - data = dict() - data["name"] = self.get_name() - data["model"] = self.__model - data["genre"] = self.__genre - data["description"] = self.__description - data["subject_categories"] = self.get_subject_categories() - data["object_categories"] = self.get_object_categories() - data["meta_rule"] = dict(self.get_meta_rule()) - return data - - def set_data(self, data): - self.__name = data["name"] - self.__model = data["model"] - self.__genre = data["genre"] - self.__description = data["description"] - self.__subject_categories = list(data["subject_categories"]) - self.__object_categories = list(data["object_categories"]) - self.__meta_rule = dict(data["meta_rule"]) - - -class Configuration: - def __init__(self): - self.__subject_category_values = dict() - # examples: { "role": {"admin", "dev", }, } - self.__object_category_values = dict() - self.__rules = list() - - def load_from_json(self, extension_setting_dir): - configuration_path = os.path.join(extension_setting_dir, 'configuration.json') - f = open(configuration_path) - json_configuration = json.load(f) - self.__subject_category_values = copy.deepcopy(json_configuration['subject_category_values']) - self.__object_category_values = copy.deepcopy(json_configuration['object_category_values']) - self.__rules = copy.deepcopy(json_configuration['rules']) # TODO: currently a list, will be a dict with sub-meta-rule as key - - def get_subject_category_values(self): - return self.__subject_category_values - - def get_object_category_values(self): - return self.__object_category_values - - def get_rules(self): - return self.__rules - - def get_data(self): - data = dict() - data["subject_category_values"] = self.get_subject_category_values() - data["object_category_values"] = self.get_object_category_values() - data["rules"] = self.get_rules() - return data - - def set_data(self, data): - self.__subject_category_values = list(data["subject_category_values"]) - self.__object_category_values = list(data["object_category_values"]) - self.__rules = list(data["rules"]) - - -class Perimeter: - def __init__(self): - self.__subjects = list() - self.__objects = list() - - def load_from_json(self, extension_setting_dir): - perimeter_path = os.path.join(extension_setting_dir, 'perimeter.json') - f = open(perimeter_path) - json_perimeter = json.load(f) - self.__subjects = copy.deepcopy(json_perimeter['subjects']) - self.__objects = copy.deepcopy(json_perimeter['objects']) - # print(self.__subjects) - # print(self.__objects) - - def get_subjects(self): - return self.__subjects - - def get_objects(self): - return self.__objects - - def get_data(self): - data = dict() - data["subjects"] = self.get_subjects() - data["objects"] = self.get_objects() - return data - - def set_data(self, data): - self.__subjects = list(data["subjects"]) - self.__objects = list(data["objects"]) - - -class Assignment: - def __init__(self): - self.__subject_category_assignments = dict() - # examples: { "role": {"user1": {"dev"}, "user2": {"admin",}}, } TODO: limit to one value for each attr - self.__object_category_assignments = dict() - - def load_from_json(self, extension_setting_dir): - assignment_path = os.path.join(extension_setting_dir, 'assignment.json') - f = open(assignment_path) - json_assignment = json.load(f) - - self.__subject_category_assignments = dict(copy.deepcopy(json_assignment['subject_category_assignments'])) - self.__object_category_assignments = dict(copy.deepcopy(json_assignment['object_category_assignments'])) - - def get_subject_category_assignments(self): - return self.__subject_category_assignments - - def get_object_category_assignments(self): - return self.__object_category_assignments - - def get_data(self): - data = dict() - data["subject_category_assignments"] = self.get_subject_category_assignments() - data["object_category_assignments"] = self.get_object_category_assignments() - return data - - def set_data(self, data): - self.__subject_category_assignments = list(data["subject_category_assignments"]) - self.__object_category_assignments = list(data["object_category_assignments"]) - - -class AuthzData: - def __init__(self, sub, obj, act): - self.validation = "False" # "OK, KO, Out of Scope" # "auth": False, - self.subject = sub - self.object = str(obj) - self.action = str(act) - self.type = "" # intra-tenant, inter-tenant, Out of Scope - self.subject_attrs = dict() - self.object_attrs = dict() - self.requesting_tenant = "" # "subject_tenant": subject_tenant, - self.requested_tenant = "" # "object_tenant": object_tenant, - - def __str__(self): - return """AuthzData: - validation={} - subject={} - object={} - action={} - """.format(self.validation, self.subject, self.object, self.action) - - -class Extension: - def __init__(self): - self.metadata = Metadata() - self.configuration = Configuration() - self.perimeter = Perimeter() - self.assignment = Assignment() - - def load_from_json(self, extension_setting_dir): - self.metadata.load_from_json(extension_setting_dir) - self.configuration.load_from_json(extension_setting_dir) - self.perimeter.load_from_json(extension_setting_dir) - self.assignment.load_from_json(extension_setting_dir) - - def get_name(self): - return self.metadata.get_name() - - def get_genre(self): - return self.metadata.get_genre() - - def authz(self, sub, obj, act): - authz_data = AuthzData(sub, obj, act) - # authz_logger.warning('extension/authz request: [sub {}, obj {}, act {}]'.format(sub, obj, act)) - - if authz_data.subject in self.perimeter.get_subjects() and authz_data.object in self.perimeter.get_objects(): - - for subject_category in self.metadata.get_subject_categories(): - authz_data.subject_attrs[subject_category] = copy.copy( - # self.assignment.get_subject_category_attr(subject_category, sub) - self.assignment.get_subject_category_assignments()[subject_category][sub] - ) - # authz_logger.warning('extension/authz subject attribute: [subject attr: {}]'.format( - # #self.assignment.get_subject_category_attr(subject_category, sub)) - # self.assignment.get_subject_category_assignments()[subject_category][sub]) - # ) - - for object_category in self.metadata.get_object_categories(): - if object_category == 'action': - authz_data.object_attrs[object_category] = [act] - # authz_logger.warning('extension/authz object attribute: [object attr: {}]'.format([act])) - else: - authz_data.object_attrs[object_category] = copy.copy( - self.assignment.get_object_category_assignments()[object_category][obj] - ) - # authz_logger.warning('extension/authz object attribute: [object attr: {}]'.format( - # self.assignment.get_object_category_assignments()[object_category][obj]) - # ) - - _aggregation_data = dict() - - for sub_meta_rule in self.metadata.get_meta_rule()["sub_meta_rules"].values(): - _tmp_relation_args = list() - - for sub_subject_category in sub_meta_rule["subject_categories"]: - _tmp_relation_args.append(authz_data.subject_attrs[sub_subject_category]) - - for sub_object_category in sub_meta_rule["object_categories"]: - _tmp_relation_args.append(authz_data.object_attrs[sub_object_category]) - - _relation_args = list(itertools.product(*_tmp_relation_args)) - - if sub_meta_rule['relation'] == 'relation_super': # TODO: replace by Prolog Engine - _aggregation_data['relation_super'] = dict() - _aggregation_data['relation_super']['result'] = False - for _relation_arg in _relation_args: - if list(_relation_arg) in self.configuration.get_rules()[sub_meta_rule['relation']]: - # authz_logger.warning( - # 'extension/authz relation super OK: [sub_sl: {}, obj_sl: {}, action: {}]'.format( - # _relation_arg[0], _relation_arg[1], _relation_arg[2] - # ) - # ) - _aggregation_data['relation_super']['result'] = True - break - _aggregation_data['relation_super']['status'] = 'finished' - - elif sub_meta_rule['relation'] == 'permission': - _aggregation_data['permission'] = dict() - _aggregation_data['permission']['result'] = False - for _relation_arg in _relation_args: - if list(_relation_arg) in self.configuration.get_rules()[sub_meta_rule['relation']]: - # authz_logger.warning( - # 'extension/authz relation permission OK: [role: {}, object: {}, action: {}]'.format( - # _relation_arg[0], _relation_arg[1], _relation_arg[2] - # ) - # ) - _aggregation_data['permission']['result'] = True - break - _aggregation_data['permission']['status'] = 'finished' - - if self.metadata.get_meta_rule_aggregation() == 'and_true_aggregation': - authz_data.validation = "OK" - for relation in _aggregation_data: - if _aggregation_data[relation]['status'] == 'finished' \ - and _aggregation_data[relation]['result'] == False: - authz_data.validation = "KO" - else: - authz_data.validation = 'Out of Scope' - - return authz_data.validation - - # ---------------- metadate api ---------------- - - def get_subject_categories(self): - return self.metadata.get_subject_categories() - - def add_subject_category(self, category_id): - if category_id in self.get_subject_categories(): - return "[ERROR] Add Subject Category: Subject Category Exists" - else: - self.get_subject_categories().append(category_id) - self.configuration.get_subject_category_values()[category_id] = list() - self.assignment.get_subject_category_assignments()[category_id] = dict() - return self.get_subject_categories() - - def del_subject_category(self, category_id): - if category_id in self.get_subject_categories(): - self.configuration.get_subject_category_values().pop(category_id) - self.assignment.get_subject_category_assignments().pop(category_id) - self.get_subject_categories().remove(category_id) - return self.get_subject_categories() - else: - return "[ERROR] Del Subject Category: Subject Category Unknown" - - def get_object_categories(self): - return self.metadata.get_object_categories() - - def add_object_category(self, category_id): - if category_id in self.get_object_categories(): - return "[ERROR] Add Object Category: Object Category Exists" - else: - self.get_object_categories().append(category_id) - self.configuration.get_object_category_values()[category_id] = list() - self.assignment.get_object_category_assignments()[category_id] = dict() - return self.get_object_categories() - - def del_object_category(self, category_id): - if category_id in self.get_object_categories(): - self.configuration.get_object_category_values().pop(category_id) - self.assignment.get_object_category_assignments().pop(category_id) - self.get_object_categories().remove(category_id) - return self.get_object_categories() - else: - return "[ERROR] Del Object Category: Object Category Unknown" - - def get_meta_rule(self): - return self.metadata.get_meta_rule() - - # ---------------- configuration api ---------------- - - def get_subject_category_values(self, category_id): - return self.configuration.get_subject_category_values()[category_id] - - def add_subject_category_value(self, category_id, category_value): - if category_value in self.configuration.get_subject_category_values()[category_id]: - return "[ERROR] Add Subject Category Value: Subject Category Value Exists" - else: - self.configuration.get_subject_category_values()[category_id].append(category_value) - return self.configuration.get_subject_category_values()[category_id] - - def del_subject_category_value(self, category_id, category_value): - if category_value in self.configuration.get_subject_category_values()[category_id]: - self.configuration.get_subject_category_values()[category_id].remove(category_value) - return self.configuration.get_subject_category_values()[category_id] - else: - return "[ERROR] Del Subject Category Value: Subject Category Value Unknown" - - def get_object_category_values(self, category_id): - return self.configuration.get_object_category_values()[category_id] - - def add_object_category_value(self, category_id, category_value): - if category_value in self.configuration.get_object_category_values()[category_id]: - return "[ERROR] Add Object Category Value: Object Category Value Exists" - else: - self.configuration.get_object_category_values()[category_id].append(category_value) - return self.configuration.get_object_category_values()[category_id] - - def del_object_category_value(self, category_id, category_value): - if category_value in self.configuration.get_object_category_values()[category_id]: - self.configuration.get_object_category_values()[category_id].remove(category_value) - return self.configuration.get_object_category_values()[category_id] - else: - return "[ERROR] Del Object Category Value: Object Category Value Unknown" - - def get_meta_rules(self): - return self.metadata.get_meta_rule() - - def _build_rule_from_list(self, relation, rule): - rule = list(rule) - _rule = dict() - _rule["sub_cat_value"] = dict() - _rule["obj_cat_value"] = dict() - if relation in self.metadata.get_meta_rule()["sub_meta_rules"]: - _rule["sub_cat_value"][relation] = dict() - _rule["obj_cat_value"][relation] = dict() - for s_category in self.metadata.get_meta_rule()["sub_meta_rules"][relation]["subject_categories"]: - _rule["sub_cat_value"][relation][s_category] = rule.pop(0) - for o_category in self.metadata.get_meta_rule()["sub_meta_rules"][relation]["object_categories"]: - _rule["obj_cat_value"][relation][o_category] = rule.pop(0) - return _rule - - def get_rules(self, full=False): - if not full: - return self.configuration.get_rules() - rules = dict() - for key in self.configuration.get_rules(): - rules[key] = map(lambda x: self._build_rule_from_list(key, x), self.configuration.get_rules()[key]) - return rules - - def add_rule(self, sub_cat_value_dict, obj_cat_value_dict): - for _relation in self.metadata.get_meta_rule()["sub_meta_rules"]: - _sub_rule = list() - for sub_subject_category in self.metadata.get_meta_rule()["sub_meta_rules"][_relation]["subject_categories"]: - try: - if sub_cat_value_dict[_relation][sub_subject_category] \ - in self.configuration.get_subject_category_values()[sub_subject_category]: - _sub_rule.append(sub_cat_value_dict[_relation][sub_subject_category]) - else: - return "[Error] Add Rule: Subject Category Value Unknown" - except KeyError as e: - # DThom: sometimes relation attribute is buggy, I don't know why... - print(e) - - #BUG: when adding a new category in rules despite it was previously adding - # data = { - # "sub_cat_value": - # {"relation_super": - # {"subject_security_level": "high", "AMH_CAT": "AMH_VAL"} - # }, - # "obj_cat_value": - # {"relation_super": - # {"object_security_level": "medium"} - # } - # } - # traceback = """ - # Traceback (most recent call last): - # File "/moon/gui/views_json.py", line 20, in wrapped - # result = function(*args, **kwargs) - # File "/moon/gui/views_json.py", line 429, in rules - # obj_cat_value=filter_input(data["obj_cat_value"])) - # File "/usr/local/lib/python2.7/dist-packages/moon/core/pap/core.py", line 380, in add_rule - # obj_cat_value) - # File "/usr/local/lib/python2.7/dist-packages/moon/core/pdp/extension.py", line 414, in add_rule - # if obj_cat_value_dict[_relation][sub_object_category] \ - # KeyError: u'action' - # """ - for sub_object_category in self.metadata.get_meta_rule()["sub_meta_rules"][_relation]["object_categories"]: - if obj_cat_value_dict[_relation][sub_object_category] \ - in self.configuration.get_object_category_values()[sub_object_category]: - _sub_rule.append(obj_cat_value_dict[_relation][sub_object_category]) - else: - return "[Error] Add Rule: Object Category Value Unknown" - - if _sub_rule in self.configuration.get_rules()[_relation]: - return "[Error] Add Rule: Rule Exists" - else: - self.configuration.get_rules()[_relation].append(_sub_rule) - return { - sub_cat_value_dict.keys()[0]: ({ - "sub_cat_value": copy.deepcopy(sub_cat_value_dict), - "obj_cat_value": copy.deepcopy(obj_cat_value_dict) - }, ) - } - return self.configuration.get_rules() - - def del_rule(self, sub_cat_value_dict, obj_cat_value_dict): - for _relation in self.metadata.get_meta_rule()["sub_meta_rules"]: - _sub_rule = list() - for sub_subject_category in self.metadata.get_meta_rule()["sub_meta_rules"][_relation]["subject_categories"]: - _sub_rule.append(sub_cat_value_dict[_relation][sub_subject_category]) - - for sub_object_category in self.metadata.get_meta_rule()["sub_meta_rules"][_relation]["object_categories"]: - _sub_rule.append(obj_cat_value_dict[_relation][sub_object_category]) - - if _sub_rule in self.configuration.get_rules()[_relation]: - self.configuration.get_rules()[_relation].remove(_sub_rule) - else: - return "[Error] Del Rule: Rule Unknown" - return self.configuration.get_rules() - - # ---------------- perimeter api ---------------- - - def get_subjects(self): - return self.perimeter.get_subjects() - - def get_objects(self): - return self.perimeter.get_objects() - - def add_subject(self, subject_id): - if subject_id in self.perimeter.get_subjects(): - return "[ERROR] Add Subject: Subject Exists" - else: - self.perimeter.get_subjects().append(subject_id) - return self.perimeter.get_subjects() - - def del_subject(self, subject_id): - if subject_id in self.perimeter.get_subjects(): - self.perimeter.get_subjects().remove(subject_id) - return self.perimeter.get_subjects() - else: - return "[ERROR] Del Subject: Subject Unknown" - - def add_object(self, object_id): - if object_id in self.perimeter.get_objects(): - return "[ERROR] Add Object: Object Exists" - else: - self.perimeter.get_objects().append(object_id) - return self.perimeter.get_objects() - - def del_object(self, object_id): - if object_id in self.perimeter.get_objects(): - self.perimeter.get_objects().remove(object_id) - return self.perimeter.get_objects() - else: - return "[ERROR] Del Object: Object Unknown" - - # ---------------- assignment api ---------------- - - def get_subject_assignments(self, category_id): - if category_id in self.metadata.get_subject_categories(): - return self.assignment.get_subject_category_assignments()[category_id] - else: - return "[ERROR] Get Subject Assignment: Subject Category Unknown" - - def add_subject_assignment(self, category_id, subject_id, category_value): - if category_id in self.metadata.get_subject_categories(): - if subject_id in self.perimeter.get_subjects(): - if category_value in self.configuration.get_subject_category_values()[category_id]: - if category_id in self.assignment.get_subject_category_assignments().keys(): - if subject_id in self.assignment.get_subject_category_assignments()[category_id].keys(): - if category_value in self.assignment.get_subject_category_assignments()[category_id][subject_id]: - return "[ERROR] Add Subject Assignment: Subject Assignment Exists" - else: - self.assignment.get_subject_category_assignments()[category_id][subject_id].extend([category_value]) - else: - self.assignment.get_subject_category_assignments()[category_id][subject_id] = [category_value] - else: - self.assignment.get_subject_category_assignments()[category_id] = {subject_id: [category_value]} - return self.assignment.get_subject_category_assignments() - else: - return "[ERROR] Add Subject Assignment: Subject Category Value Unknown" - else: - return "[ERROR] Add Subject Assignment: Subject Unknown" - else: - return "[ERROR] Add Subject Assignment: Subject Category Unknown" - - def del_subject_assignment(self, category_id, subject_id, category_value): - if category_id in self.metadata.get_subject_categories(): - if subject_id in self.perimeter.get_subjects(): - if category_value in self.configuration.get_subject_category_values()[category_id]: - if len(self.assignment.get_subject_category_assignments()[category_id][subject_id]) >= 2: - self.assignment.get_subject_category_assignments()[category_id][subject_id].remove(category_value) - else: - self.assignment.get_subject_category_assignments()[category_id].pop(subject_id) - return self.assignment.get_subject_category_assignments() - else: - return "[ERROR] Del Subject Assignment: Assignment Unknown" - else: - return "[ERROR] Del Subject Assignment: Subject Unknown" - else: - return "[ERROR] Del Subject Assignment: Subject Category Unknown" - - def get_object_assignments(self, category_id): - if category_id in self.metadata.get_object_categories(): - return self.assignment.get_object_category_assignments()[category_id] - else: - return "[ERROR] Get Object Assignment: Object Category Unknown" - - def add_object_assignment(self, category_id, object_id, category_value): - if category_id in self.metadata.get_object_categories(): - if object_id in self.perimeter.get_objects(): - if category_value in self.configuration.get_object_category_values()[category_id]: - if category_id in self.assignment.get_object_category_assignments().keys(): - if object_id in self.assignment.get_object_category_assignments()[category_id].keys(): - if category_value in self.assignment.get_object_category_assignments()[category_id][object_id]: - return "[ERROR] Add Object Assignment: Object Assignment Exists" - else: - self.assignment.get_object_category_assignments()[category_id][object_id].extend([category_value]) - else: - self.assignment.get_object_category_assignments()[category_id][object_id] = [category_value] - else: - self.assignment.get_object_category_assignments()[category_id] = {object_id: [category_value]} - return self.assignment.get_object_category_assignments() - else: - return "[ERROR] Add Object Assignment: Object Category Value Unknown" - else: - return "[ERROR] Add Object Assignment: Object Unknown" - else: - return "[ERROR] Add Object Assignment: Object Category Unknown" - - def del_object_assignment(self, category_id, object_id, category_value): - if category_id in self.metadata.get_object_categories(): - if object_id in self.perimeter.get_objects(): - if category_value in self.configuration.get_object_category_values()[category_id]: - if len(self.assignment.get_object_category_assignments()[category_id][object_id]) >= 2: - self.assignment.get_object_category_assignments()[category_id][object_id].remove(category_value) - else: - self.assignment.get_object_category_assignments()[category_id].pop(object_id) - return self.assignment.get_object_category_assignments() - else: - return "[ERROR] Del Object Assignment: Assignment Unknown" - else: - return "[ERROR] Del Object Assignment: Object Unknown" - else: - return "[ERROR] Del Object Assignment: Object Category Unknown" - - # ---------------- inter-extension API ---------------- - - def create_requesting_collaboration(self, sub_list, vent_uuid, act): - _sub_cat_values = dict() - _obj_cat_values = dict() - - if type(self.add_object(vent_uuid)) is not list: - return "[Error] Create Requesting Collaboration: No Success" - for _relation in self.get_meta_rule()["sub_meta_rules"]: - for _sub_cat_id in self.get_meta_rule()["sub_meta_rules"][_relation]["subject_categories"]: - _sub_cat_value = str(uuid4()) - if type(self.add_subject_category_value(_sub_cat_id, _sub_cat_value)) is not list: - return "[Error] Create Requesting Collaboration: No Success" - _sub_cat_values[_relation] = {_sub_cat_id: _sub_cat_value} - for _sub in sub_list: - if type(self.add_subject_assignment(_sub_cat_id, _sub, _sub_cat_value)) is not dict: - return "[Error] Create Requesting Collaboration: No Success" - - for _obj_cat_id in self.get_meta_rule()["sub_meta_rules"][_relation]["object_categories"]: - if _obj_cat_id == 'action': - _obj_cat_values[_relation][_obj_cat_id] = act - else: - _obj_cat_value = str(uuid4()) - if type(self.add_object_category_value(_obj_cat_id, _obj_cat_value)) is not list: - return "[Error] Create Requesting Collaboration: No Success" - if type(self.add_object_assignment(_obj_cat_id, vent_uuid, _obj_cat_value)) is not dict: - return "[Error] Create Requesting Collaboration: No Success" - _obj_cat_values[_relation] = {_obj_cat_id: _obj_cat_value} - - _rule = self.add_rule(_sub_cat_values, _obj_cat_values) - if type(_rule) is not dict: - return "[Error] Create Requesting Collaboration: No Success" - return {"subject_category_value_dict": _sub_cat_values, "object_category_value_dict": _obj_cat_values, - "rule": _rule} - - def destroy_requesting_collaboration(self, sub_list, vent_uuid, sub_cat_value_dict, obj_cat_value_dict): - for _relation in self.get_meta_rule()["sub_meta_rules"]: - for _sub_cat_id in self.get_meta_rule()["sub_meta_rules"][_relation]["subject_categories"]: - for _sub in sub_list: - if type(self.del_subject_assignment(_sub_cat_id, _sub, sub_cat_value_dict[_relation][_sub_cat_id]))\ - is not dict: - return "[Error] Destroy Requesting Collaboration: No Success" - if type(self.del_subject_category_value(_sub_cat_id, sub_cat_value_dict[_relation][_sub_cat_id])) \ - is not list: - return "[Error] Destroy Requesting Collaboration: No Success" - - for _obj_cat_id in self.get_meta_rule()["sub_meta_rules"][_relation]["object_categories"]: - if _obj_cat_id == "action": - pass # TODO: reconsidering the action as object attribute - else: - if type(self.del_object_assignment(_obj_cat_id, vent_uuid, obj_cat_value_dict[_relation][_obj_cat_id])) is not dict: - return "[Error] Destroy Requesting Collaboration: No Success" - if type(self.del_object_category_value(_obj_cat_id, obj_cat_value_dict[_relation][_obj_cat_id])) is not list: - return "[Error] Destroy Requesting Collaboration: No Success" - - if type(self.del_rule(sub_cat_value_dict, obj_cat_value_dict)) is not dict: - return "[Error] Destroy Requesting Collaboration: No Success" - if type(self.del_object(vent_uuid)) is not list: - return "[Error] Destroy Requesting Collaboration: No Success" - return "[Destroy Requesting Collaboration] OK" - - def create_requested_collaboration(self, vent_uuid, obj_list, act): - _sub_cat_values = dict() - _obj_cat_values = dict() - - if type(self.add_subject(vent_uuid)) is not list: - return "[Error] Create Requested Collaboration: No Success" - - for _relation in self.get_meta_rule()["sub_meta_rules"]: - for _sub_cat_id in self.get_meta_rule()["sub_meta_rules"][_relation]["subject_categories"]: - _sub_cat_value = str(uuid4()) - if type(self.add_subject_category_value(_sub_cat_id, _sub_cat_value)) is not list: - return "[Error] Create Requested Collaboration: No Success" - _sub_cat_values[_relation] = {_sub_cat_id: _sub_cat_value} - if type(self.add_subject_assignment(_sub_cat_id, vent_uuid, _sub_cat_value)) is not dict: - return "[Error] Create Requested Collaboration: No Success" - - for _obj_cat_id in self.get_meta_rule()["sub_meta_rules"][_relation]["object_categories"]: - if _obj_cat_id == 'action': - _obj_cat_values[_relation][_obj_cat_id] = act - else: - _obj_cat_value = str(uuid4()) - if type(self.add_object_category_value(_obj_cat_id, _obj_cat_value)) is not list: - return "[Error] Create Requested Collaboration: No Success" - _obj_cat_values[_relation] = {_obj_cat_id: _obj_cat_value} - for _obj in obj_list: - if type(self.add_object_assignment(_obj_cat_id, _obj, _obj_cat_value)) is not dict: - return "[Error] Create Requested Collaboration: No Success" - - _rule = self.add_rule(_sub_cat_values, _obj_cat_values) - if type(_rule) is not dict: - return "[Error] Create Requested Collaboration: No Success" - return {"subject_category_value_dict": _sub_cat_values, "object_category_value_dict": _obj_cat_values, - "rule": _rule} - - def destroy_requested_collaboration(self, vent_uuid, obj_list, sub_cat_value_dict, obj_cat_value_dict): - for _relation in self.get_meta_rule()["sub_meta_rules"]: - for _sub_cat_id in self.get_meta_rule()["sub_meta_rules"][_relation]["subject_categories"]: - if type(self.del_subject_assignment(_sub_cat_id, vent_uuid, sub_cat_value_dict[_relation][_sub_cat_id])) is not dict: - return "[Error] Destroy Requested Collaboration: No Success" - if type(self.del_subject_category_value(_sub_cat_id, sub_cat_value_dict[_relation][_sub_cat_id])) is not list: - return "[Error] Destroy Requested Collaboration: No Success" - - for _obj_cat_id in self.get_meta_rule()["sub_meta_rules"][_relation]["object_categories"]: - if _obj_cat_id == "action": - pass # TODO: reconsidering the action as object attribute - else: - for _obj in obj_list: - if type(self.del_object_assignment(_obj_cat_id, _obj, obj_cat_value_dict[_relation][_obj_cat_id])) is not dict: - return "[Error] Destroy Requested Collaboration: No Success" - if type(self.del_object_category_value(_obj_cat_id, obj_cat_value_dict[_relation][_obj_cat_id])) is not list: - return "[Error] Destroy Requested Collaboration: No Success" - - if type(self.del_rule(sub_cat_value_dict, obj_cat_value_dict)) is not dict: - return "[Error] Destroy Requested Collaboration: No Success" - if type(self.del_subject(vent_uuid)) is not list: - return "[Error] Destroy Requested Collaboration: No Success" - return "[Destroy Requested Collaboration] OK" - - # ---------------- sync_db api ---------------- - - def get_data(self): - data = dict() - data["metadata"] = self.metadata.get_data() - data["configuration"] = self.configuration.get_data() - data["perimeter"] = self.perimeter.get_data() - data["assignment"] = self.assignment.get_data() - return data - - def set_data(self, extension_data): - self.metadata.set_data(extension_data["metadata"]) - self.configuration.set_data(extension_data["configuration"]) - self.perimeter.set_data(extension_data["perimeter"]) - self.assignment.set_data(extension_data["assignment"]) diff --git a/keystone-moon/keystone/contrib/moon/migrate_repo/versions/001_moon.py b/keystone-moon/keystone/contrib/moon/migrate_repo/versions/001_moon.py index af4d80bb..4fd26bef 100644 --- a/keystone-moon/keystone/contrib/moon/migrate_repo/versions/001_moon.py +++ b/keystone-moon/keystone/contrib/moon/migrate_repo/versions/001_moon.py @@ -21,11 +21,11 @@ def upgrade(migrate_engine): mysql_charset='utf8') intra_extension_table.create(migrate_engine, checkfirst=True) - intra_extension_table.insert().values(id=uuid4().hex, intra_extension={ - 'name': "Root Extension", - 'description': "The root intra extension", - 'model': 'admin' - }) + # intra_extension_table.insert().values(id=uuid4().hex, intra_extension={ + # 'name': "Root Extension", + # 'description': "The root intra extension", + # 'model': 'admin' + # }) tenant_table = sql.Table( 'tenants', @@ -195,8 +195,6 @@ def upgrade(migrate_engine): mysql_charset='utf8') rules_table.create(migrate_engine, checkfirst=True) - # TODO: load root_extension - def downgrade(migrate_engine): meta = sql.MetaData() diff --git a/keystone-moon/keystone/contrib/moon/routers.py b/keystone-moon/keystone/contrib/moon/routers.py index 4da3b991..63915092 100644 --- a/keystone-moon/keystone/contrib/moon/routers.py +++ b/keystone-moon/keystone/contrib/moon/routers.py @@ -9,7 +9,7 @@ from keystone.contrib.moon import controllers from keystone.common import wsgi -class Routers(wsgi.RoutersBase): +class Routers(wsgi.V3ExtensionRouter): """API Endpoints for the Moon extension. """ diff --git a/keystone-moon/keystone/contrib/oauth1/backends/sql.py b/keystone-moon/keystone/contrib/oauth1/backends/sql.py index c6ab6e5a..a7876756 100644 --- a/keystone-moon/keystone/contrib/oauth1/backends/sql.py +++ b/keystone-moon/keystone/contrib/oauth1/backends/sql.py @@ -18,9 +18,9 @@ import uuid from oslo_serialization import jsonutils from oslo_utils import timeutils -import six from keystone.common import sql +from keystone.common import utils from keystone.contrib.oauth1 import core from keystone import exception from keystone.i18n import _ @@ -58,7 +58,7 @@ class RequestToken(sql.ModelBase, sql.DictBase): return cls(**user_dict) def to_dict(self): - return dict(six.iteritems(self)) + return dict(self.items()) class AccessToken(sql.ModelBase, sql.DictBase): @@ -81,7 +81,7 @@ class AccessToken(sql.ModelBase, sql.DictBase): return cls(**user_dict) def to_dict(self): - return dict(six.iteritems(self)) + return dict(self.items()) class OAuth1(object): @@ -163,7 +163,7 @@ class OAuth1(object): if token_duration: now = timeutils.utcnow() future = now + datetime.timedelta(seconds=token_duration) - expiry_date = timeutils.isotime(future, subsecond=True) + expiry_date = utils.isotime(future, subsecond=True) ref = {} ref['id'] = request_token_id @@ -225,7 +225,7 @@ class OAuth1(object): if token_duration: now = timeutils.utcnow() future = now + datetime.timedelta(seconds=token_duration) - expiry_date = timeutils.isotime(future, subsecond=True) + expiry_date = utils.isotime(future, subsecond=True) # add Access Token ref = {} diff --git a/keystone-moon/keystone/contrib/oauth1/controllers.py b/keystone-moon/keystone/contrib/oauth1/controllers.py index fb5d0bc2..d12fc96b 100644 --- a/keystone-moon/keystone/contrib/oauth1/controllers.py +++ b/keystone-moon/keystone/contrib/oauth1/controllers.py @@ -20,12 +20,12 @@ from oslo_utils import timeutils from keystone.common import controller from keystone.common import dependency +from keystone.common import utils from keystone.common import wsgi from keystone.contrib.oauth1 import core as oauth1 from keystone.contrib.oauth1 import validator from keystone import exception from keystone.i18n import _ -from keystone.models import token_model from keystone import notifications @@ -84,10 +84,7 @@ class ConsumerCrudV3(controller.V3Controller): @controller.protected() def delete_consumer(self, context, consumer_id): - user_token_ref = token_model.KeystoneToken( - token_id=context['token_id'], - token_data=self.token_provider_api.validate_token( - context['token_id'])) + user_token_ref = utils.get_token_ref(context) payload = {'user_id': user_token_ref.user_id, 'consumer_id': consumer_id} _emit_user_oauth_consumer_token_invalidate(payload) @@ -382,10 +379,7 @@ class OAuthControllerV3(controller.V3Controller): authed_roles.add(role['id']) # verify the authorizing user has the roles - user_token = token_model.KeystoneToken( - token_id=context['token_id'], - token_data=self.token_provider_api.validate_token( - context['token_id'])) + user_token = utils.get_token_ref(context) user_id = user_token.user_id project_id = req_token['requested_project_id'] user_roles = self.assignment_api.get_roles_for_user_and_project( diff --git a/keystone-moon/keystone/contrib/oauth1/core.py b/keystone-moon/keystone/contrib/oauth1/core.py index eeb3e114..d7f64dc4 100644 --- a/keystone-moon/keystone/contrib/oauth1/core.py +++ b/keystone-moon/keystone/contrib/oauth1/core.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. -"""Extensions supporting OAuth1.""" +"""Main entry point into the OAuth1 service.""" from __future__ import absolute_import @@ -151,6 +151,9 @@ class Manager(manager.Manager): dynamically calls the backend. """ + + driver_namespace = 'keystone.oauth1' + _ACCESS_TOKEN = "OS-OAUTH1:access_token" _REQUEST_TOKEN = "OS-OAUTH1:request_token" _CONSUMER = "OS-OAUTH1:consumer" diff --git a/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/001_add_oauth_tables.py b/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/001_add_oauth_tables.py index a4fbf155..e0305351 100644 --- a/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/001_add_oauth_tables.py +++ b/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/001_add_oauth_tables.py @@ -55,13 +55,3 @@ def upgrade(migrate_engine): sql.Column('consumer_id', sql.String(64), nullable=False), sql.Column('expires_at', sql.String(64), nullable=True)) access_token_table.create(migrate_engine, checkfirst=True) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - # Operations to reverse the above upgrade go here. - tables = ['consumer', 'request_token', 'access_token'] - for table_name in tables: - table = sql.Table(table_name, meta, autoload=True) - table.drop() diff --git a/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/002_fix_oauth_tables_fk.py b/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/002_fix_oauth_tables_fk.py index d39df8d5..174120e8 100644 --- a/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/002_fix_oauth_tables_fk.py +++ b/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/002_fix_oauth_tables_fk.py @@ -35,20 +35,3 @@ def upgrade(migrate_engine): 'ref_column': consumer_table.c.id}] if meta.bind != 'sqlite': migration_helpers.add_constraints(constraints) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - consumer_table = sql.Table('consumer', meta, autoload=True) - request_token_table = sql.Table('request_token', meta, autoload=True) - access_token_table = sql.Table('access_token', meta, autoload=True) - - constraints = [{'table': request_token_table, - 'fk_column': 'consumer_id', - 'ref_column': consumer_table.c.id}, - {'table': access_token_table, - 'fk_column': 'consumer_id', - 'ref_column': consumer_table.c.id}] - if migrate_engine.name != 'sqlite': - migration_helpers.remove_constraints(constraints) diff --git a/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/003_consumer_description_nullalbe.py b/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/003_consumer_description_nullalbe.py index e1cf8843..cf6ffb7c 100644 --- a/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/003_consumer_description_nullalbe.py +++ b/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/003_consumer_description_nullalbe.py @@ -20,10 +20,3 @@ def upgrade(migrate_engine): meta.bind = migrate_engine user_table = sql.Table('consumer', meta, autoload=True) user_table.c.description.alter(nullable=True) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - user_table = sql.Table('consumer', meta, autoload=True) - user_table.c.description.alter(nullable=False) diff --git a/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/004_request_token_roles_nullable.py b/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/004_request_token_roles_nullable.py index 6f1e2e81..6934eb6f 100644 --- a/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/004_request_token_roles_nullable.py +++ b/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/004_request_token_roles_nullable.py @@ -23,13 +23,3 @@ def upgrade(migrate_engine): request_token_table.c.requested_roles.alter(name="role_ids") access_token_table = sql.Table('access_token', meta, autoload=True) access_token_table.c.requested_roles.alter(name="role_ids") - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - request_token_table = sql.Table('request_token', meta, autoload=True) - request_token_table.c.role_ids.alter(nullable=False) - request_token_table.c.role_ids.alter(name="requested_roles") - access_token_table = sql.Table('access_token', meta, autoload=True) - access_token_table.c.role_ids.alter(name="requested_roles") diff --git a/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/005_consumer_id_index.py b/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/005_consumer_id_index.py index 428971f8..0627d21c 100644 --- a/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/005_consumer_id_index.py +++ b/keystone-moon/keystone/contrib/oauth1/migrate_repo/versions/005_consumer_id_index.py @@ -26,17 +26,10 @@ def upgrade(migrate_engine): # indexes create automatically. That those indexes will have different # names, depending on version of MySQL used. We shoud make this naming # consistent, by reverting index name to a consistent condition. - if any(i for i in table.indexes if i.columns.keys() == ['consumer_id'] + if any(i for i in table.indexes if + list(i.columns.keys()) == ['consumer_id'] and i.name != 'consumer_id'): # NOTE(i159): by this action will be made re-creation of an index # with the new name. This can be considered as renaming under the # MySQL rules. sa.Index('consumer_id', table.c.consumer_id).create() - - -def downgrade(migrate_engine): - # NOTE(i159): index exists only in MySQL schemas, and got an inconsistent - # name only when MySQL 5.5 renamed it after re-creation - # (during migrations). So we just fixed inconsistency, there is no - # necessity to revert it. - pass diff --git a/keystone-moon/keystone/contrib/oauth1/routers.py b/keystone-moon/keystone/contrib/oauth1/routers.py index 35619ede..4b772eb5 100644 --- a/keystone-moon/keystone/contrib/oauth1/routers.py +++ b/keystone-moon/keystone/contrib/oauth1/routers.py @@ -44,17 +44,17 @@ class OAuth1Extension(wsgi.V3ExtensionRouter): # Basic admin-only consumer crud POST /OS-OAUTH1/consumers GET /OS-OAUTH1/consumers - PATCH /OS-OAUTH1/consumers/$consumer_id - GET /OS-OAUTH1/consumers/$consumer_id - DELETE /OS-OAUTH1/consumers/$consumer_id + PATCH /OS-OAUTH1/consumers/{consumer_id} + GET /OS-OAUTH1/consumers/{consumer_id} + DELETE /OS-OAUTH1/consumers/{consumer_id} # User access token crud - GET /users/$user_id/OS-OAUTH1/access_tokens - GET /users/$user_id/OS-OAUTH1/access_tokens/$access_token_id + GET /users/{user_id}/OS-OAUTH1/access_tokens + GET /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id} GET /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id}/roles GET /users/{user_id}/OS-OAUTH1/access_tokens /{access_token_id}/roles/{role_id} - DELETE /users/$user_id/OS-OAUTH1/access_tokens/$access_token_id + DELETE /users/{user_id}/OS-OAUTH1/access_tokens/{access_token_id} # OAuth interfaces POST /OS-OAUTH1/request_token # create a request token diff --git a/keystone-moon/keystone/contrib/revoke/backends/kvs.py b/keystone-moon/keystone/contrib/revoke/backends/kvs.py index cc41fbee..349ed6e3 100644 --- a/keystone-moon/keystone/contrib/revoke/backends/kvs.py +++ b/keystone-moon/keystone/contrib/revoke/backends/kvs.py @@ -13,12 +13,12 @@ import datetime from oslo_config import cfg +from oslo_log import versionutils from oslo_utils import timeutils from keystone.common import kvs from keystone.contrib import revoke from keystone import exception -from keystone.openstack.common import versionutils CONF = cfg.CONF @@ -45,29 +45,30 @@ class Revoke(revoke.Driver): except exception.NotFound: return [] - def _prune_expired_events_and_get(self, last_fetch=None, new_event=None): - pruned = [] + def list_events(self, last_fetch=None): results = [] + + with self._store.get_lock(_EVENT_KEY): + events = self._list_events() + + for event in events: + revoked_at = event.revoked_at + if last_fetch is None or revoked_at > last_fetch: + results.append(event) + return results + + def revoke(self, event): + pruned = [] expire_delta = datetime.timedelta(seconds=CONF.token.expiration) oldest = timeutils.utcnow() - expire_delta - # TODO(ayoung): Store the time of the oldest event so that the - # prune process can be skipped if none of the events have timed out. + with self._store.get_lock(_EVENT_KEY) as lock: events = self._list_events() - if new_event is not None: - events.append(new_event) + if event: + events.append(event) for event in events: revoked_at = event.revoked_at if revoked_at > oldest: pruned.append(event) - if last_fetch is None or revoked_at > last_fetch: - results.append(event) self._store.set(_EVENT_KEY, pruned, lock) - return results - - def list_events(self, last_fetch=None): - return self._prune_expired_events_and_get(last_fetch=last_fetch) - - def revoke(self, event): - self._prune_expired_events_and_get(new_event=event) diff --git a/keystone-moon/keystone/contrib/revoke/backends/sql.py b/keystone-moon/keystone/contrib/revoke/backends/sql.py index 1b0cde1e..dd7fdd19 100644 --- a/keystone-moon/keystone/contrib/revoke/backends/sql.py +++ b/keystone-moon/keystone/contrib/revoke/backends/sql.py @@ -33,7 +33,7 @@ class RevocationEvent(sql.ModelBase, sql.ModelDictMixin): access_token_id = sql.Column(sql.String(64)) issued_before = sql.Column(sql.DateTime(), nullable=False) expires_at = sql.Column(sql.DateTime()) - revoked_at = sql.Column(sql.DateTime(), nullable=False) + revoked_at = sql.Column(sql.DateTime(), nullable=False, index=True) audit_id = sql.Column(sql.String(32)) audit_chain_id = sql.Column(sql.String(32)) @@ -81,7 +81,6 @@ class Revoke(revoke.Driver): session.flush() def list_events(self, last_fetch=None): - self._prune_expired_events() session = sql.get_session() query = session.query(RevocationEvent).order_by( RevocationEvent.revoked_at) @@ -102,3 +101,4 @@ class Revoke(revoke.Driver): session = sql.get_session() with session.begin(): session.add(record) + self._prune_expired_events() diff --git a/keystone-moon/keystone/contrib/revoke/core.py b/keystone-moon/keystone/contrib/revoke/core.py index c7335690..e1ab87c8 100644 --- a/keystone-moon/keystone/contrib/revoke/core.py +++ b/keystone-moon/keystone/contrib/revoke/core.py @@ -10,11 +10,14 @@ # License for the specific language governing permissions and limitations # under the License. +"""Main entry point into the Revoke service.""" + import abc import datetime from oslo_config import cfg from oslo_log import log +from oslo_log import versionutils from oslo_utils import timeutils import six @@ -26,7 +29,6 @@ from keystone.contrib.revoke import model from keystone import exception from keystone.i18n import _ from keystone import notifications -from keystone.openstack.common import versionutils CONF = cfg.CONF @@ -64,12 +66,17 @@ def revoked_before_cutoff_time(): @dependency.provider('revoke_api') class Manager(manager.Manager): - """Revoke API Manager. + """Default pivot point for the Revoke backend. Performs common logic for recording revocations. + See :mod:`keystone.common.manager.Manager` for more details on + how this dynamically calls the backend. + """ + driver_namespace = 'keystone.revoke' + def __init__(self): super(Manager, self).__init__(CONF.revoke.driver) self._register_listeners() @@ -109,11 +116,12 @@ class Manager(manager.Manager): self.revoke( model.RevokeEvent(access_token_id=payload['resource_info'])) - def _group_callback(self, service, resource_type, operation, payload): - user_ids = (u['id'] for u in self.identity_api.list_users_in_group( - payload['resource_info'])) - for uid in user_ids: - self.revoke(model.RevokeEvent(user_id=uid)) + def _role_assignment_callback(self, service, resource_type, operation, + payload): + info = payload['resource_info'] + self.revoke_by_grant(role_id=info['role_id'], user_id=info['user_id'], + domain_id=info.get('domain_id'), + project_id=info.get('project_id')) def _register_listeners(self): callbacks = { @@ -124,6 +132,7 @@ class Manager(manager.Manager): ['role', self._role_callback], ['user', self._user_callback], ['project', self._project_callback], + ['role_assignment', self._role_assignment_callback] ], notifications.ACTIONS.disabled: [ ['user', self._user_callback], @@ -136,7 +145,7 @@ class Manager(manager.Manager): ] } - for event, cb_info in six.iteritems(callbacks): + for event, cb_info in callbacks.items(): for resource_type, callback_fns in cb_info: notifications.register_event_callback(event, resource_type, callback_fns) diff --git a/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/001_revoke_table.py b/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/001_revoke_table.py index 7927ce0c..8b59010e 100644 --- a/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/001_revoke_table.py +++ b/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/001_revoke_table.py @@ -34,14 +34,3 @@ def upgrade(migrate_engine): sql.Column('expires_at', sql.DateTime()), sql.Column('revoked_at', sql.DateTime(), index=True, nullable=False)) service_table.create(migrate_engine, checkfirst=True) - - -def downgrade(migrate_engine): - # Operations to reverse the above upgrade go here. - meta = sql.MetaData() - meta.bind = migrate_engine - - tables = ['revocation_event'] - for t in tables: - table = sql.Table(t, meta, autoload=True) - table.drop(migrate_engine, checkfirst=True) diff --git a/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py b/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py index bee6fb2a..b6d821d7 100644 --- a/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py +++ b/keystone-moon/keystone/contrib/revoke/migrate_repo/versions/002_add_audit_id_and_chain_to_revoke_table.py @@ -26,12 +26,3 @@ def upgrade(migrate_engine): nullable=True) event_table.create_column(audit_id_column) event_table.create_column(audit_chain_column) - - -def downgrade(migrate_engine): - meta = sql.MetaData() - meta.bind = migrate_engine - - event_table = sql.Table(_TABLE_NAME, meta, autoload=True) - event_table.drop_column('audit_id') - event_table.drop_column('audit_chain_id') diff --git a/keystone-moon/keystone/contrib/revoke/model.py b/keystone-moon/keystone/contrib/revoke/model.py index 5e92042d..1a23d57d 100644 --- a/keystone-moon/keystone/contrib/revoke/model.py +++ b/keystone-moon/keystone/contrib/revoke/model.py @@ -11,6 +11,9 @@ # under the License. from oslo_utils import timeutils +from six.moves import map + +from keystone.common import utils # The set of attributes common between the RevokeEvent @@ -43,6 +46,15 @@ _TOKEN_KEYS = ['identity_domain_id', 'trustor_id', 'trustee_id'] +# Alternative names to be checked in token for every field in +# revoke tree. +ALTERNATIVES = { + 'user_id': ['user_id', 'trustor_id', 'trustee_id'], + 'domain_id': ['identity_domain_id', 'assignment_domain_id'], + # For a domain-scoped token, the domain is in assignment_domain_id. + 'domain_scope_id': ['assignment_domain_id', ], +} + REVOKE_KEYS = _NAMES + _EVENT_ARGS @@ -100,10 +112,10 @@ class RevokeEvent(object): if self.consumer_id is not None: event['OS-OAUTH1:access_token_id'] = self.access_token_id if self.expires_at is not None: - event['expires_at'] = timeutils.isotime(self.expires_at) + event['expires_at'] = utils.isotime(self.expires_at) if self.issued_before is not None: - event['issued_before'] = timeutils.isotime(self.issued_before, - subsecond=True) + event['issued_before'] = utils.isotime(self.issued_before, + subsecond=True) return event def key_for_name(self, name): @@ -111,7 +123,7 @@ class RevokeEvent(object): def attr_keys(event): - return map(event.key_for_name, _EVENT_NAMES) + return list(map(event.key_for_name, _EVENT_NAMES)) class RevokeTree(object): @@ -176,7 +188,52 @@ class RevokeTree(object): del parent[key] def add_events(self, revoke_events): - return map(self.add_event, revoke_events or []) + return list(map(self.add_event, revoke_events or [])) + + @staticmethod + def _next_level_keys(name, token_data): + """Generate keys based on current field name and token data + + Generate all keys to look for in the next iteration of revocation + event tree traversal. + """ + yield '*' + if name == 'role_id': + # Roles are very special since a token has a list of them. + # If the revocation event matches any one of them, + # revoke the token. + for role_id in token_data.get('roles', []): + yield role_id + else: + # For other fields we try to get any branch that concur + # with any alternative field in the token. + for alt_name in ALTERNATIVES.get(name, [name]): + yield token_data[alt_name] + + def _search(self, revoke_map, names, token_data): + """Search for revocation event by token_data + + Traverse the revocation events tree looking for event matching token + data issued after the token. + """ + if not names: + # The last (leaf) level is checked in a special way because we + # verify issued_at field differently. + try: + return revoke_map['issued_before'] > token_data['issued_at'] + except KeyError: + return False + + name, remaining_names = names[0], names[1:] + + for key in self._next_level_keys(name, token_data): + subtree = revoke_map.get('%s=%s' % (name, key)) + if subtree and self._search(subtree, remaining_names, token_data): + return True + + # If we made it out of the loop then no element in revocation tree + # corresponds to our token and it is good. + return False def is_revoked(self, token_data): """Check if a token matches the revocation event @@ -195,58 +252,7 @@ class RevokeTree(object): 'consumer_id', 'access_token_id' """ - # Alternative names to be checked in token for every field in - # revoke tree. - alternatives = { - 'user_id': ['user_id', 'trustor_id', 'trustee_id'], - 'domain_id': ['identity_domain_id', 'assignment_domain_id'], - # For a domain-scoped token, the domain is in assignment_domain_id. - 'domain_scope_id': ['assignment_domain_id', ], - } - # Contains current forest (collection of trees) to be checked. - partial_matches = [self.revoke_map] - # We iterate over every layer of our revoke tree (except the last one). - for name in _EVENT_NAMES: - # bundle is the set of partial matches for the next level down - # the tree - bundle = [] - wildcard = '%s=*' % (name,) - # For every tree in current forest. - for tree in partial_matches: - # If there is wildcard node on current level we take it. - bundle.append(tree.get(wildcard)) - if name == 'role_id': - # Roles are very special since a token has a list of them. - # If the revocation event matches any one of them, - # revoke the token. - for role_id in token_data.get('roles', []): - bundle.append(tree.get('role_id=%s' % role_id)) - else: - # For other fields we try to get any branch that concur - # with any alternative field in the token. - for alt_name in alternatives.get(name, [name]): - bundle.append( - tree.get('%s=%s' % (name, token_data[alt_name]))) - # tree.get returns `None` if there is no match, so `bundle.append` - # adds a 'None' entry. This call remoes the `None` entries. - partial_matches = [x for x in bundle if x is not None] - if not partial_matches: - # If we end up with no branches to follow means that the token - # is definitely not in the revoke tree and all further - # iterations will be for nothing. - return False - - # The last (leaf) level is checked in a special way because we verify - # issued_at field differently. - for leaf in partial_matches: - try: - if leaf['issued_before'] > token_data['issued_at']: - return True - except KeyError: - pass - # If we made it out of the loop then no element in revocation tree - # corresponds to our token and it is good. - return False + return self._search(self.revoke_map, _EVENT_NAMES, token_data) def build_token_values_v2(access, default_domain_id): diff --git a/keystone-moon/keystone/contrib/s3/core.py b/keystone-moon/keystone/contrib/s3/core.py index 34095bf4..d3e06acc 100644 --- a/keystone-moon/keystone/contrib/s3/core.py +++ b/keystone-moon/keystone/contrib/s3/core.py @@ -25,6 +25,8 @@ import base64 import hashlib import hmac +import six + from keystone.common import extension from keystone.common import json_home from keystone.common import utils @@ -32,6 +34,7 @@ from keystone.common import wsgi from keystone.contrib.ec2 import controllers from keystone import exception + EXTENSION_DATA = { 'name': 'OpenStack S3 API', 'namespace': 'http://docs.openstack.org/identity/api/ext/' @@ -65,9 +68,15 @@ class S3Extension(wsgi.V3ExtensionRouter): class S3Controller(controllers.Ec2Controller): def check_signature(self, creds_ref, credentials): msg = base64.urlsafe_b64decode(str(credentials['token'])) - key = str(creds_ref['secret']) - signed = base64.encodestring( - hmac.new(key, msg, hashlib.sha1).digest()).strip() + key = str(creds_ref['secret']).encode('utf-8') + + if six.PY2: + b64_encode = base64.encodestring + else: + b64_encode = base64.encodebytes + + signed = b64_encode( + hmac.new(key, msg, hashlib.sha1).digest()).decode('utf-8').strip() if not utils.auth_str_equal(credentials['signature'], signed): raise exception.Unauthorized('Credential signature mismatch') |