Add mapping function to keystone
Add a mapping implementation for the federation extension. This will allow a set of rules to be added, that map relationships between identity providers and keystone api spec: https://review.openstack.org/#/c/59848/ Implements: bp mapping-distributed-admin Change-Id: Iab93dc6759f195fecd88dcdf1c635c81ad59165a
This commit is contained in:
parent
20840199e6
commit
e6db2cf978
|
@ -118,5 +118,11 @@
|
|||
"identity:update_protocol": "rule:admin_required",
|
||||
"identity:get_protocol": "rule:admin_required",
|
||||
"identity:list_protocols": "rule:admin_required",
|
||||
"identity:delete_protocol": "rule:admin_required"
|
||||
"identity:delete_protocol": "rule:admin_required",
|
||||
|
||||
"identity:create_mapping": "rule:admin_required",
|
||||
"identity:get_mapping": "rule:admin_required",
|
||||
"identity:list_mappings": "rule:admin_required",
|
||||
"identity:delete_mapping": "rule:admin_required",
|
||||
"identity:update_mapping": "rule:admin_required"
|
||||
}
|
||||
|
|
|
@ -124,5 +124,11 @@
|
|||
"identity:update_protocol": "rule:admin_required",
|
||||
"identity:get_protocol": "rule:admin_required",
|
||||
"identity:list_protocols": "rule:admin_required",
|
||||
"identity:delete_protocol": "rule:admin_required"
|
||||
"identity:delete_protocol": "rule:admin_required",
|
||||
|
||||
"identity:create_mapping": "rule:admin_required",
|
||||
"identity:get_mapping": "rule:admin_required",
|
||||
"identity:list_mappings": "rule:admin_required",
|
||||
"identity:delete_mapping": "rule:admin_required",
|
||||
"identity:update_mapping": "rule:admin_required"
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ from keystone.common import sql
|
|||
from keystone.common.sql import migration
|
||||
from keystone.contrib.federation import core
|
||||
from keystone import exception
|
||||
from keystone.openstack.common import jsonutils
|
||||
|
||||
|
||||
class FederationProtocolModel(sql.ModelBase, sql.DictBase):
|
||||
|
@ -66,13 +67,32 @@ class IdentityProviderModel(sql.ModelBase, sql.DictBase):
|
|||
return d
|
||||
|
||||
|
||||
class MappingModel(sql.ModelBase, sql.DictBase):
|
||||
__tablename__ = 'mapping'
|
||||
attributes = ['id', 'rules']
|
||||
|
||||
id = sql.Column(sql.String(64), primary_key=True)
|
||||
rules = sql.Column(sql.JsonBlob(), nullable=False)
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dictionary):
|
||||
new_dictionary = dictionary.copy()
|
||||
return cls(**new_dictionary)
|
||||
|
||||
def to_dict(self):
|
||||
"""Return a dictionary with model's attributes."""
|
||||
d = dict()
|
||||
for attr in self.__class__.attributes:
|
||||
d[attr] = getattr(self, attr)
|
||||
return d
|
||||
|
||||
|
||||
class Federation(sql.Base, core.Driver):
|
||||
|
||||
def db_sync(self):
|
||||
migration.db_sync()
|
||||
|
||||
# Identity Provider CRUD
|
||||
|
||||
@sql.handle_conflicts(conflict_type='identity_provider')
|
||||
def create_idp(self, idp_id, idp):
|
||||
session = self.get_session()
|
||||
|
@ -121,7 +141,6 @@ class Federation(sql.Base, core.Driver):
|
|||
return idp_ref.to_dict()
|
||||
|
||||
# Protocol CRUD
|
||||
|
||||
def _get_protocol(self, session, idp_id, protocol_id):
|
||||
q = session.query(FederationProtocolModel)
|
||||
q = q.filter_by(id=protocol_id, idp_id=idp_id)
|
||||
|
@ -180,3 +199,54 @@ class Federation(sql.Base, core.Driver):
|
|||
q = q.filter_by(id=protocol_id, idp_id=idp_id)
|
||||
q.delete(synchronize_session=False)
|
||||
session.delete(key_ref)
|
||||
|
||||
# Mapping CRUD
|
||||
def _get_mapping(self, session, mapping_id):
|
||||
mapping_ref = session.query(MappingModel).get(mapping_id)
|
||||
if not mapping_ref:
|
||||
raise exception.MappingNotFound(mapping_id=mapping_id)
|
||||
return mapping_ref
|
||||
|
||||
@sql.handle_conflicts(conflict_type='mapping')
|
||||
def create_mapping(self, mapping_id, mapping):
|
||||
session = self.get_session()
|
||||
ref = {}
|
||||
ref['id'] = mapping_id
|
||||
ref['rules'] = jsonutils.dumps(mapping.get('rules'))
|
||||
with session.begin():
|
||||
mapping_ref = MappingModel.from_dict(ref)
|
||||
session.add(mapping_ref)
|
||||
return mapping_ref.to_dict()
|
||||
|
||||
def delete_mapping(self, mapping_id):
|
||||
session = self.get_session()
|
||||
with session.begin():
|
||||
mapping_ref = self._get_mapping(session, mapping_id)
|
||||
session.delete(mapping_ref)
|
||||
|
||||
def list_mappings(self):
|
||||
session = self.get_session()
|
||||
with session.begin():
|
||||
mappings = session.query(MappingModel)
|
||||
return [x.to_dict() for x in mappings]
|
||||
|
||||
def get_mapping(self, mapping_id):
|
||||
session = self.get_session()
|
||||
with session.begin():
|
||||
mapping_ref = self._get_mapping(session, mapping_id)
|
||||
return mapping_ref.to_dict()
|
||||
|
||||
@sql.handle_conflicts(conflict_type='mapping')
|
||||
def update_mapping(self, mapping_id, mapping):
|
||||
ref = {}
|
||||
ref['id'] = mapping_id
|
||||
ref['rules'] = jsonutils.dumps(mapping.get('rules'))
|
||||
session = self.get_session()
|
||||
with session.begin():
|
||||
mapping_ref = self._get_mapping(session, mapping_id)
|
||||
old_mapping = mapping_ref.to_dict()
|
||||
old_mapping.update(ref)
|
||||
new_mapping = MappingModel.from_dict(old_mapping)
|
||||
for attr in MappingModel.attributes:
|
||||
setattr(mapping_ref, attr, getattr(new_mapping, attr))
|
||||
return mapping_ref.to_dict()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# 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
|
||||
|
@ -20,8 +19,10 @@ from keystone.common import controller
|
|||
from keystone.common import dependency
|
||||
from keystone.common import wsgi
|
||||
from keystone import config
|
||||
from keystone.contrib.federation import utils
|
||||
from keystone import exception
|
||||
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
|
@ -103,7 +104,7 @@ class IdentityProvider(controller.V3Controller):
|
|||
|
||||
@classmethod
|
||||
def _add_related_links(cls, ref):
|
||||
"""Add URLs for entitities related with Identity Provider.
|
||||
"""Add URLs for entities related with Identity Provider.
|
||||
|
||||
Add URLs pointing to:
|
||||
- protocols tied to the Identity Provider
|
||||
|
@ -266,3 +267,43 @@ class FederationProtocol(IdentityProvider):
|
|||
@controller.protected()
|
||||
def delete_protocol(self, context, idp_id, protocol_id):
|
||||
self.federation_api.delete_protocol(idp_id, protocol_id)
|
||||
|
||||
|
||||
@dependency.requires('federation_api')
|
||||
class MappingController(controller.V3Controller):
|
||||
collection_name = 'mappings'
|
||||
member_name = 'mapping'
|
||||
|
||||
@classmethod
|
||||
def base_url(cls, path=None):
|
||||
path = '/OS-FEDERATION/' + cls.collection_name
|
||||
return controller.V3Controller.base_url(path)
|
||||
|
||||
@controller.protected()
|
||||
def create_mapping(self, context, mapping_id, mapping):
|
||||
ref = self._normalize_dict(mapping)
|
||||
utils.validate_mapping_structure(ref)
|
||||
mapping_ref = self.federation_api.create_mapping(mapping_id, ref)
|
||||
response = MappingController.wrap_member(context, mapping_ref)
|
||||
return wsgi.render_response(body=response, status=('201', 'Created'))
|
||||
|
||||
@controller.protected()
|
||||
def list_mappings(self, context):
|
||||
ref = self.federation_api.list_mappings()
|
||||
return MappingController.wrap_collection(context, ref)
|
||||
|
||||
@controller.protected()
|
||||
def get_mapping(self, context, mapping_id):
|
||||
ref = self.federation_api.get_mapping(mapping_id)
|
||||
return MappingController.wrap_member(context, ref)
|
||||
|
||||
@controller.protected()
|
||||
def delete_mapping(self, context, mapping_id):
|
||||
self.federation_api.delete_mapping(mapping_id)
|
||||
|
||||
@controller.protected()
|
||||
def update_mapping(self, context, mapping_id, mapping):
|
||||
mapping = self._normalize_dict(mapping)
|
||||
utils.validate_mapping_structure(mapping)
|
||||
mapping_ref = self.federation_api.update_mapping(mapping_id, mapping)
|
||||
return MappingController.wrap_member(context, mapping_ref)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# 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
|
||||
|
@ -145,3 +144,59 @@ class Driver(object):
|
|||
|
||||
"""
|
||||
raise exception.NotImplemented()
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_mapping(self, mapping_ref):
|
||||
"""Create a mapping.
|
||||
|
||||
:param mapping_ref: mapping ref with mapping name
|
||||
:type mapping_ref: dict
|
||||
:returns: mapping_ref
|
||||
|
||||
"""
|
||||
raise exception.NotImplemented()
|
||||
|
||||
@abc.abstractmethod
|
||||
def delete_mapping(self, mapping_id):
|
||||
"""Delete a mapping.
|
||||
|
||||
:param mapping_id: id of mapping to delete
|
||||
:type mapping_ref: string
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
raise exception.NotImplemented()
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_mapping(self, mapping_id, mapping_ref):
|
||||
"""Update a mapping.
|
||||
|
||||
:param mapping_id: id of mapping to update
|
||||
:type mapping_id: string
|
||||
:param mapping_ref: new mapping ref
|
||||
:type mapping_ref: dict
|
||||
:returns: mapping_ref
|
||||
|
||||
"""
|
||||
raise exception.NotImplemented()
|
||||
|
||||
@abc.abstractmethod
|
||||
def list_mappings(self):
|
||||
"""List all mappings.
|
||||
|
||||
returns: list of mappings
|
||||
|
||||
"""
|
||||
raise exception.NotImplemented()
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_mapping(self, mapping_id):
|
||||
"""Get a mapping, returns the mapping based
|
||||
on mapping_id.
|
||||
|
||||
:param mapping_id: id of mapping to get
|
||||
:type mapping_ref: string
|
||||
:returns: mapping_ref
|
||||
|
||||
"""
|
||||
raise exception.NotImplemented()
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# 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
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
#
|
||||
# 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 sql
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = sql.MetaData()
|
||||
meta.bind = migrate_engine
|
||||
|
||||
mapping_table = sql.Table(
|
||||
'mapping',
|
||||
meta,
|
||||
sql.Column('id', sql.String(64), primary_key=True),
|
||||
sql.Column('rules', sql.Text(), nullable=False))
|
||||
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()
|
|
@ -1,6 +1,5 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# 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
|
||||
|
@ -40,6 +39,12 @@ class FederationExtension(wsgi.ExtensionRouter):
|
|||
DELETE /OS-FEDERATION/identity_providers/
|
||||
$identity_provider/protocols/$protocol
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
|
||||
def _construct_url(self, suffix):
|
||||
|
@ -48,6 +53,7 @@ class FederationExtension(wsgi.ExtensionRouter):
|
|||
def add_routes(self, mapper):
|
||||
idp_controller = controllers.IdentityProvider()
|
||||
protocol_controller = controllers.FederationProtocol()
|
||||
mapping_controller = controllers.MappingController()
|
||||
|
||||
# Identity Provider CRUD operations
|
||||
|
||||
|
@ -117,3 +123,35 @@ class FederationExtension(wsgi.ExtensionRouter):
|
|||
controller=protocol_controller,
|
||||
action='delete_protocol',
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
# Mapping CRUD operations
|
||||
|
||||
mapper.connect(
|
||||
self._construct_url('mappings/{mapping_id}'),
|
||||
controller=mapping_controller,
|
||||
action='create_mapping',
|
||||
conditions=dict(method=['PUT']))
|
||||
|
||||
mapper.connect(
|
||||
self._construct_url('mappings'),
|
||||
controller=mapping_controller,
|
||||
action='list_mappings',
|
||||
conditions=dict(method=['GET']))
|
||||
|
||||
mapper.connect(
|
||||
self._construct_url('mappings/{mapping_id}'),
|
||||
controller=mapping_controller,
|
||||
action='get_mapping',
|
||||
conditions=dict(method=['GET']))
|
||||
|
||||
mapper.connect(
|
||||
self._construct_url('mappings/{mapping_id}'),
|
||||
controller=mapping_controller,
|
||||
action='delete_mapping',
|
||||
conditions=dict(method=['DELETE']))
|
||||
|
||||
mapper.connect(
|
||||
self._construct_url('mappings/{mapping_id}'),
|
||||
controller=mapping_controller,
|
||||
action='update_mapping',
|
||||
conditions=dict(method=['PATCH']))
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Utilities for Federation Extension."""
|
||||
|
||||
import jsonschema
|
||||
|
||||
from keystone import exception
|
||||
|
||||
|
||||
MAPPING_SCHEMA = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"rules": {
|
||||
"minItems": 1,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"local": {
|
||||
"type": "array"
|
||||
},
|
||||
"remote": {
|
||||
"minItems": 1,
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/empty"},
|
||||
{"$ref": "#/definitions/any_one_of"},
|
||||
{"$ref": "#/definitions/not_any_of"}
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"empty": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"required": ['type'],
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
},
|
||||
"additionalProperties": False,
|
||||
},
|
||||
"any_one_of": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ['type', 'any_one_of'],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"any_one_of": {
|
||||
"type": "array"
|
||||
},
|
||||
"regex": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"not_any_of": {
|
||||
"type": "object",
|
||||
"additionalProperties": False,
|
||||
"required": ['type', 'not_any_of'],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"not_any_of": {
|
||||
"type": "array"
|
||||
},
|
||||
"regex": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def validate_mapping_structure(ref):
|
||||
v = jsonschema.Draft4Validator(MAPPING_SCHEMA)
|
||||
|
||||
messages = ''
|
||||
for error in sorted(v.iter_errors(ref), key=str):
|
||||
messages = messages + error.message + "\n"
|
||||
|
||||
if messages:
|
||||
raise exception.ValidationError(messages)
|
|
@ -222,6 +222,10 @@ class GroupNotFound(NotFound):
|
|||
message_format = _("Could not find group, %(group_id)s.")
|
||||
|
||||
|
||||
class MappingNotFound(NotFound):
|
||||
message_format = _("Could not find mapping, %(mapping_id)s.")
|
||||
|
||||
|
||||
class TrustNotFound(NotFound):
|
||||
message_format = _("Could not find trust, %(trust_id)s.")
|
||||
|
||||
|
|
|
@ -0,0 +1,211 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
#
|
||||
# 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.
|
||||
|
||||
MAPPING_SMALL = {
|
||||
'name': 'large mapping',
|
||||
"rules": [
|
||||
{
|
||||
"local": [
|
||||
{
|
||||
"group": {
|
||||
"id": "0cd5e9"
|
||||
}
|
||||
}
|
||||
],
|
||||
"remote": [
|
||||
{
|
||||
"type": "orgPersonType",
|
||||
"not_any_of": [
|
||||
"Contractor",
|
||||
"SubContractor"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"local": [
|
||||
{
|
||||
"group": {
|
||||
"id": "85a868"
|
||||
}
|
||||
}
|
||||
],
|
||||
"remote": [
|
||||
{
|
||||
"type": "orgPersonType",
|
||||
"any_one_of": [
|
||||
"Contractor",
|
||||
"SubContractor"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
MAPPING_LARGE = {
|
||||
"rules": [
|
||||
{
|
||||
"local": [
|
||||
{
|
||||
"user": {
|
||||
"name": "$0 $1",
|
||||
"email": "$2"
|
||||
}
|
||||
}
|
||||
],
|
||||
"remote": [
|
||||
{
|
||||
"type": "FirstName"
|
||||
},
|
||||
{
|
||||
"type": "LastName"
|
||||
},
|
||||
{
|
||||
"type": "Email"
|
||||
},
|
||||
{
|
||||
"type": "Group",
|
||||
"any_one_of": [
|
||||
"Admin",
|
||||
"God"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"local": [
|
||||
{
|
||||
"user": {
|
||||
"name": "$0",
|
||||
"email": "$1"
|
||||
}
|
||||
}
|
||||
],
|
||||
"remote": [
|
||||
{
|
||||
"type": "UserName"
|
||||
},
|
||||
{
|
||||
"type": "Email"
|
||||
},
|
||||
{
|
||||
"type": "Group",
|
||||
"any_one_of": [
|
||||
"Customer"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"local": [
|
||||
{
|
||||
"group": {
|
||||
"id": "123"
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": {
|
||||
"id": "xyz"
|
||||
}
|
||||
}
|
||||
],
|
||||
"remote": [
|
||||
{
|
||||
"type": "Group",
|
||||
"any_one_of": [
|
||||
"Special"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "Email",
|
||||
"any_one_of": [
|
||||
"^@example.com$"
|
||||
],
|
||||
"regex": True
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
MAPPING_BAD_REQ = {
|
||||
"rules": [
|
||||
{
|
||||
"local": [
|
||||
{
|
||||
"user": "name"
|
||||
}
|
||||
],
|
||||
"remote": [
|
||||
{
|
||||
"type": "UserName",
|
||||
"bad_requirement": [
|
||||
"Young"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
MAPPING_BAD_VALUE = {
|
||||
"rules": [
|
||||
{
|
||||
"local": [
|
||||
{
|
||||
"user": "name"
|
||||
}
|
||||
],
|
||||
"remote": [
|
||||
{
|
||||
"type": "UserName",
|
||||
"any_one_of": "should_be_list"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
MAPPING_NO_RULES = {
|
||||
'rules': []
|
||||
}
|
||||
|
||||
MAPPING_NO_REMOTE = {
|
||||
"rules": [
|
||||
{
|
||||
"local": [
|
||||
{
|
||||
"user": "name"
|
||||
}
|
||||
],
|
||||
"remote": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
MAPPING_MISSING_LOCAL = {
|
||||
"rules": [
|
||||
{
|
||||
"remote": [
|
||||
{
|
||||
"type": "UserName",
|
||||
"any_one_of": "should_be_list"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -144,12 +144,16 @@ class FederationExtension(test_sql_upgrade.SqlMigrateBase):
|
|||
super(FederationExtension, self).__init__(*args, **kwargs)
|
||||
self.identity_provider = 'identity_provider'
|
||||
self.federation_protocol = 'federation_protocol'
|
||||
self.mapping = 'mapping'
|
||||
|
||||
def repo_package(self):
|
||||
return federation
|
||||
|
||||
def test_upgrade(self):
|
||||
self.assertTableDoesNotExist(self.identity_provider)
|
||||
self.assertTableDoesNotExist(self.federation_protocol)
|
||||
self.assertTableDoesNotExist(self.mapping)
|
||||
|
||||
self.upgrade(1, repository=self.repo_path)
|
||||
self.assertTableColumns(self.identity_provider,
|
||||
['id',
|
||||
|
@ -161,13 +165,20 @@ class FederationExtension(test_sql_upgrade.SqlMigrateBase):
|
|||
'idp_id',
|
||||
'mapping_id'])
|
||||
|
||||
self.upgrade(2, repository=self.repo_path)
|
||||
self.assertTableColumns(self.mapping,
|
||||
['id', 'rules'])
|
||||
|
||||
def test_downgrade(self):
|
||||
self.upgrade(1, repository=self.repo_path)
|
||||
self.upgrade(2, repository=self.repo_path)
|
||||
self.assertTableColumns(self.identity_provider,
|
||||
['id', 'enabled', 'description'])
|
||||
self.assertTableColumns(self.federation_protocol,
|
||||
['id', 'idp_id', 'mapping_id'])
|
||||
self.assertTableColumns(self.mapping,
|
||||
['id', 'rules'])
|
||||
|
||||
self.downgrade(0, repository=self.repo_path)
|
||||
self.assertTableDoesNotExist(self.identity_provider)
|
||||
self.assertTableDoesNotExist(self.federation_protocol)
|
||||
self.assertTableDoesNotExist(self.mapping)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
#
|
||||
# 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
|
||||
|
@ -22,7 +21,9 @@ from keystone.common.sql import migration
|
|||
from keystone import config
|
||||
from keystone import contrib
|
||||
from keystone.openstack.common import importutils
|
||||
from keystone.openstack.common import jsonutils
|
||||
from keystone.openstack.common import log
|
||||
from keystone.tests import mapping_fixtures
|
||||
from keystone.tests import test_v3
|
||||
|
||||
|
||||
|
@ -434,3 +435,119 @@ class FederatedIdentityProviderTests(FederationTests):
|
|||
'protocol_id': proto}
|
||||
self.delete(url)
|
||||
self.get(url, expected_status=404)
|
||||
|
||||
|
||||
class MappingTests(FederationTests):
|
||||
|
||||
MAPPING_URL = '/OS-FEDERATION/mappings/'
|
||||
|
||||
def assertValidMappingListResponse(self, resp, *args, **kwargs):
|
||||
return self.assertValidListResponse(
|
||||
resp,
|
||||
'mappings',
|
||||
self.assertValidMapping,
|
||||
keys_to_check=[],
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
def assertValidMappingResponse(self, resp, *args, **kwargs):
|
||||
return self.assertValidResponse(
|
||||
resp,
|
||||
'mapping',
|
||||
self.assertValidMapping,
|
||||
keys_to_check=[],
|
||||
*args,
|
||||
**kwargs)
|
||||
|
||||
def assertValidMapping(self, entity, ref=None):
|
||||
self.assertIsNotNone(entity.get('id'))
|
||||
self.assertIsNotNone(entity.get('rules'))
|
||||
if ref:
|
||||
self.assertEqual(jsonutils.loads(entity['rules']), ref['rules'])
|
||||
return entity
|
||||
|
||||
def _create_default_mapping_entry(self):
|
||||
url = self.MAPPING_URL + uuid.uuid4().hex
|
||||
resp = self.put(url,
|
||||
body={'mapping': mapping_fixtures.MAPPING_LARGE},
|
||||
expected_status=201)
|
||||
return resp
|
||||
|
||||
def _get_id_from_response(self, resp):
|
||||
r = resp.result.get('mapping')
|
||||
return r.get('id')
|
||||
|
||||
def test_mapping_create(self):
|
||||
resp = self._create_default_mapping_entry()
|
||||
self.assertValidMappingResponse(resp, mapping_fixtures.MAPPING_LARGE)
|
||||
|
||||
def test_mapping_list(self):
|
||||
url = self.MAPPING_URL
|
||||
self._create_default_mapping_entry()
|
||||
resp = self.get(url)
|
||||
entities = resp.result.get('mappings')
|
||||
self.assertIsNotNone(entities)
|
||||
self.assertResponseStatus(resp, 200)
|
||||
self.assertValidListLinks(resp.result.get('links'))
|
||||
self.assertEqual(len(entities), 1)
|
||||
|
||||
def test_mapping_delete(self):
|
||||
url = self.MAPPING_URL + '%(mapping_id)s'
|
||||
resp = self._create_default_mapping_entry()
|
||||
mapping_id = self._get_id_from_response(resp)
|
||||
url = url % {'mapping_id': str(mapping_id)}
|
||||
resp = self.delete(url)
|
||||
self.assertResponseStatus(resp, 204)
|
||||
self.get(url, expected_status=404)
|
||||
|
||||
def test_mapping_get(self):
|
||||
url = self.MAPPING_URL + '%(mapping_id)s'
|
||||
resp = self._create_default_mapping_entry()
|
||||
mapping_id = self._get_id_from_response(resp)
|
||||
url = url % {'mapping_id': mapping_id}
|
||||
resp = self.get(url)
|
||||
self.assertValidMappingResponse(resp, mapping_fixtures.MAPPING_LARGE)
|
||||
|
||||
def test_mapping_update(self):
|
||||
url = self.MAPPING_URL + '%(mapping_id)s'
|
||||
resp = self._create_default_mapping_entry()
|
||||
mapping_id = self._get_id_from_response(resp)
|
||||
url = url % {'mapping_id': mapping_id}
|
||||
resp = self.patch(url,
|
||||
body={'mapping': mapping_fixtures.MAPPING_SMALL})
|
||||
self.assertValidMappingResponse(resp, mapping_fixtures.MAPPING_SMALL)
|
||||
resp = self.get(url)
|
||||
self.assertValidMappingResponse(resp, mapping_fixtures.MAPPING_SMALL)
|
||||
|
||||
def test_delete_mapping_dne(self):
|
||||
url = self.MAPPING_URL + uuid.uuid4().hex
|
||||
self.delete(url, expected_status=404)
|
||||
|
||||
def test_get_mapping_dne(self):
|
||||
url = self.MAPPING_URL + uuid.uuid4().hex
|
||||
self.get(url, expected_status=404)
|
||||
|
||||
def test_create_mapping_bad_requirements(self):
|
||||
url = self.MAPPING_URL + uuid.uuid4().hex
|
||||
self.put(url, expected_status=400,
|
||||
body={'mapping': mapping_fixtures.MAPPING_BAD_REQ})
|
||||
|
||||
def test_create_mapping_no_rules(self):
|
||||
url = self.MAPPING_URL + uuid.uuid4().hex
|
||||
self.put(url, expected_status=400,
|
||||
body={'mapping': mapping_fixtures.MAPPING_NO_RULES})
|
||||
|
||||
def test_create_mapping_no_remote_objects(self):
|
||||
url = self.MAPPING_URL + uuid.uuid4().hex
|
||||
self.put(url, expected_status=400,
|
||||
body={'mapping': mapping_fixtures.MAPPING_NO_REMOTE})
|
||||
|
||||
def test_create_mapping_bad_value(self):
|
||||
url = self.MAPPING_URL + uuid.uuid4().hex
|
||||
self.put(url, expected_status=400,
|
||||
body={'mapping': mapping_fixtures.MAPPING_BAD_VALUE})
|
||||
|
||||
def test_create_mapping_missing_local(self):
|
||||
url = self.MAPPING_URL + uuid.uuid4().hex
|
||||
self.put(url, expected_status=400,
|
||||
body={'mapping': mapping_fixtures.MAPPING_MISSING_LOCAL})
|
||||
|
|
|
@ -19,6 +19,7 @@ oslo.config>=1.2.0
|
|||
Babel>=1.3
|
||||
oauthlib
|
||||
dogpile.cache>=0.5.0
|
||||
jsonschema>=1.3.0,!=1.4.0
|
||||
|
||||
# KDS exclusive dependencies
|
||||
|
||||
|
|
Loading…
Reference in New Issue