Add relay_state_prefix to Service Provider
To correctly create an ECP assertion, a keystone acting as an identity provider needs to know the relay state's prefix used by the service provider. This is usually 'ss:mem', but is configurable with mod_shib. Co-Authored-By: Rodrigo Duarte Sousa <rodrigods@lsd.ufcg.edu.br> Change-Id: I55fe059df8b73e8c6d1f195a958e73ee0a321015 Partial-Bug: 1426128 bp: ecp-wrapped-saml-assertions
This commit is contained in:
parent
8c42166c12
commit
659529a4ad
|
@ -978,6 +978,11 @@ FILE_OPTIONS = {
|
||||||
help='Path to the Identity Provider Metadata file. '
|
help='Path to the Identity Provider Metadata file. '
|
||||||
'This file should be generated with the '
|
'This file should be generated with the '
|
||||||
'keystone-manage saml_idp_metadata command.'),
|
'keystone-manage saml_idp_metadata command.'),
|
||||||
|
cfg.StrOpt('relay_state_prefix',
|
||||||
|
default='ss:mem:',
|
||||||
|
help='The prefix to use for the RelayState SAML '
|
||||||
|
'attribute, used when generating ECP wrapped '
|
||||||
|
'assertions.'),
|
||||||
],
|
],
|
||||||
'eventlet_server': [
|
'eventlet_server': [
|
||||||
cfg.IntOpt('public_workers',
|
cfg.IntOpt('public_workers',
|
||||||
|
|
|
@ -126,15 +126,17 @@ class MappingModel(sql.ModelBase, sql.DictBase):
|
||||||
|
|
||||||
class ServiceProviderModel(sql.ModelBase, sql.DictBase):
|
class ServiceProviderModel(sql.ModelBase, sql.DictBase):
|
||||||
__tablename__ = 'service_provider'
|
__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',
|
mutable_attributes = frozenset(['auth_url', 'description', 'enabled',
|
||||||
'sp_url'])
|
'relay_state_prefix', 'sp_url'])
|
||||||
|
|
||||||
id = sql.Column(sql.String(64), primary_key=True)
|
id = sql.Column(sql.String(64), primary_key=True)
|
||||||
enabled = sql.Column(sql.Boolean, nullable=False)
|
enabled = sql.Column(sql.Boolean, nullable=False)
|
||||||
description = sql.Column(sql.Text(), nullable=True)
|
description = sql.Column(sql.Text(), nullable=True)
|
||||||
auth_url = sql.Column(sql.String(256), nullable=False)
|
auth_url = sql.Column(sql.String(256), nullable=False)
|
||||||
sp_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
|
@classmethod
|
||||||
def from_dict(cls, dictionary):
|
def from_dict(cls, dictionary):
|
||||||
|
|
|
@ -406,15 +406,17 @@ class ServiceProvider(_ControllerBase):
|
||||||
member_name = 'service_provider'
|
member_name = 'service_provider'
|
||||||
|
|
||||||
_mutable_parameters = frozenset(['auth_url', 'description', 'enabled',
|
_mutable_parameters = frozenset(['auth_url', 'description', 'enabled',
|
||||||
'sp_url'])
|
'relay_state_prefix', 'sp_url'])
|
||||||
_public_parameters = frozenset(['auth_url', 'id', 'enabled', 'description',
|
_public_parameters = frozenset(['auth_url', 'id', 'enabled', 'description',
|
||||||
'links', 'sp_url'])
|
'links', 'relay_state_prefix', 'sp_url'])
|
||||||
|
|
||||||
@controller.protected()
|
@controller.protected()
|
||||||
@validation.validated(schema.service_provider_create, 'service_provider')
|
@validation.validated(schema.service_provider_create, 'service_provider')
|
||||||
def create_service_provider(self, context, sp_id, service_provider):
|
def create_service_provider(self, context, sp_id, service_provider):
|
||||||
service_provider = self._normalize_dict(service_provider)
|
service_provider = self._normalize_dict(service_provider)
|
||||||
service_provider.setdefault('enabled', False)
|
service_provider.setdefault('enabled', False)
|
||||||
|
service_provider.setdefault('relay_state_prefix',
|
||||||
|
CONF.saml.relay_state_prefix)
|
||||||
ServiceProvider.check_immutable_params(service_provider)
|
ServiceProvider.check_immutable_params(service_provider)
|
||||||
sp_ref = self.federation_api.create_sp(sp_id, service_provider)
|
sp_ref = self.federation_api.create_sp(sp_id, service_provider)
|
||||||
response = ServiceProvider.wrap_member(context, sp_ref)
|
response = ServiceProvider.wrap_member(context, sp_ref)
|
||||||
|
|
|
@ -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)
|
|
@ -58,7 +58,8 @@ _service_provider_properties = {
|
||||||
'auth_url': parameter_types.url,
|
'auth_url': parameter_types.url,
|
||||||
'sp_url': parameter_types.url,
|
'sp_url': parameter_types.url,
|
||||||
'description': validation.nullable(parameter_types.description),
|
'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 = {
|
service_provider_create = {
|
||||||
|
|
|
@ -46,5 +46,6 @@ class SqlFederation(test_backend_sql.SqlModels):
|
||||||
('id', sql.String, 64),
|
('id', sql.String, 64),
|
||||||
('enabled', sql.Boolean, None),
|
('enabled', sql.Boolean, None),
|
||||||
('description', sql.Text, None),
|
('description', sql.Text, None),
|
||||||
|
('relay_state_prefix', sql.String, 256),
|
||||||
('sp_url', sql.String, 256))
|
('sp_url', sql.String, 256))
|
||||||
self.assertExpectedSchema('service_provider', cols)
|
self.assertExpectedSchema('service_provider', cols)
|
||||||
|
|
|
@ -310,6 +310,12 @@ class FederationExtension(test_sql_upgrade.SqlMigrateBase):
|
||||||
self.assertEqual('', sp.auth_url)
|
self.assertEqual('', sp.auth_url)
|
||||||
self.assertEqual('', sp.sp_url)
|
self.assertEqual('', sp.sp_url)
|
||||||
|
|
||||||
|
def test_add_relay_state_column(self):
|
||||||
|
self.upgrade(8, repository=self.repo_path)
|
||||||
|
self.assertTableColumns(self.service_provider,
|
||||||
|
['id', 'description', 'enabled', 'auth_url',
|
||||||
|
'relay_state_prefix', 'sp_url'])
|
||||||
|
|
||||||
|
|
||||||
class RevokeExtension(test_sql_upgrade.SqlMigrateBase):
|
class RevokeExtension(test_sql_upgrade.SqlMigrateBase):
|
||||||
|
|
||||||
|
|
|
@ -2947,6 +2947,7 @@ class SAMLGenerationTests(FederationTests):
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'description': uuid.uuid4().hex,
|
'description': uuid.uuid4().hex,
|
||||||
'sp_url': self.RECIPIENT,
|
'sp_url': self.RECIPIENT,
|
||||||
|
'relay_state_prefix': CONF.saml.relay_state_prefix,
|
||||||
|
|
||||||
}
|
}
|
||||||
return ref
|
return ref
|
||||||
|
@ -3388,7 +3389,8 @@ class ServiceProviderTests(FederationTests):
|
||||||
MEMBER_NAME = 'service_provider'
|
MEMBER_NAME = 'service_provider'
|
||||||
COLLECTION_NAME = 'service_providers'
|
COLLECTION_NAME = 'service_providers'
|
||||||
SERVICE_PROVIDER_ID = 'ACME'
|
SERVICE_PROVIDER_ID = 'ACME'
|
||||||
SP_KEYS = ['auth_url', 'id', 'enabled', 'description', 'sp_url']
|
SP_KEYS = ['auth_url', 'id', 'enabled', 'description',
|
||||||
|
'relay_state_prefix', 'sp_url']
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(FederationTests, self).setUp()
|
super(FederationTests, self).setUp()
|
||||||
|
@ -3405,6 +3407,7 @@ class ServiceProviderTests(FederationTests):
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'description': uuid.uuid4().hex,
|
'description': uuid.uuid4().hex,
|
||||||
'sp_url': 'https://' + uuid.uuid4().hex + '.com',
|
'sp_url': 'https://' + uuid.uuid4().hex + '.com',
|
||||||
|
'relay_state_prefix': CONF.saml.relay_state_prefix
|
||||||
}
|
}
|
||||||
return ref
|
return ref
|
||||||
|
|
||||||
|
@ -3431,6 +3434,29 @@ class ServiceProviderTests(FederationTests):
|
||||||
self.assertValidEntity(resp.result['service_provider'],
|
self.assertValidEntity(resp.result['service_provider'],
|
||||||
keys_to_check=self.SP_KEYS)
|
keys_to_check=self.SP_KEYS)
|
||||||
|
|
||||||
|
def test_create_sp_relay_state_default(self):
|
||||||
|
"""Create an SP without relay state, should default to `ss:mem`."""
|
||||||
|
url = self.base_url(suffix=uuid.uuid4().hex)
|
||||||
|
sp = self.sp_ref()
|
||||||
|
del sp['relay_state_prefix']
|
||||||
|
resp = self.put(url, body={'service_provider': sp},
|
||||||
|
expected_status=201)
|
||||||
|
sp_result = resp.result['service_provider']
|
||||||
|
self.assertEqual(CONF.saml.relay_state_prefix,
|
||||||
|
sp_result['relay_state_prefix'])
|
||||||
|
|
||||||
|
def test_create_sp_relay_state_non_default(self):
|
||||||
|
"""Create an SP with custom relay state."""
|
||||||
|
url = self.base_url(suffix=uuid.uuid4().hex)
|
||||||
|
sp = self.sp_ref()
|
||||||
|
non_default_prefix = uuid.uuid4().hex
|
||||||
|
sp['relay_state_prefix'] = non_default_prefix
|
||||||
|
resp = self.put(url, body={'service_provider': sp},
|
||||||
|
expected_status=201)
|
||||||
|
sp_result = resp.result['service_provider']
|
||||||
|
self.assertEqual(non_default_prefix,
|
||||||
|
sp_result['relay_state_prefix'])
|
||||||
|
|
||||||
def test_create_service_provider_fail(self):
|
def test_create_service_provider_fail(self):
|
||||||
"""Try adding SP object with unallowed attribute."""
|
"""Try adding SP object with unallowed attribute."""
|
||||||
url = self.base_url(suffix=uuid.uuid4().hex)
|
url = self.base_url(suffix=uuid.uuid4().hex)
|
||||||
|
@ -3520,6 +3546,18 @@ class ServiceProviderTests(FederationTests):
|
||||||
self.patch(url, body={'service_provider': new_sp_ref},
|
self.patch(url, body={'service_provider': new_sp_ref},
|
||||||
expected_status=404)
|
expected_status=404)
|
||||||
|
|
||||||
|
def test_update_sp_relay_state(self):
|
||||||
|
"""Update an SP with custome relay state."""
|
||||||
|
new_sp_ref = self.sp_ref()
|
||||||
|
non_default_prefix = uuid.uuid4().hex
|
||||||
|
new_sp_ref['relay_state_prefix'] = non_default_prefix
|
||||||
|
url = self.base_url(suffix=self.SERVICE_PROVIDER_ID)
|
||||||
|
resp = self.patch(url, body={'service_provider': new_sp_ref},
|
||||||
|
expected_status=200)
|
||||||
|
sp_result = resp.result['service_provider']
|
||||||
|
self.assertEqual(non_default_prefix,
|
||||||
|
sp_result['relay_state_prefix'])
|
||||||
|
|
||||||
def test_delete_service_provider(self):
|
def test_delete_service_provider(self):
|
||||||
url = self.base_url(suffix=self.SERVICE_PROVIDER_ID)
|
url = self.base_url(suffix=self.SERVICE_PROVIDER_ID)
|
||||||
self.delete(url, expected_status=204)
|
self.delete(url, expected_status=204)
|
||||||
|
@ -3641,6 +3679,7 @@ class K2KServiceCatalogTests(FederationTests):
|
||||||
def sp_response(self, id, ref):
|
def sp_response(self, id, ref):
|
||||||
ref.pop('enabled')
|
ref.pop('enabled')
|
||||||
ref.pop('description')
|
ref.pop('description')
|
||||||
|
ref.pop('relay_state_prefix')
|
||||||
ref['id'] = id
|
ref['id'] = id
|
||||||
return ref
|
return ref
|
||||||
|
|
||||||
|
@ -3650,6 +3689,7 @@ class K2KServiceCatalogTests(FederationTests):
|
||||||
'enabled': True,
|
'enabled': True,
|
||||||
'description': uuid.uuid4().hex,
|
'description': uuid.uuid4().hex,
|
||||||
'sp_url': uuid.uuid4().hex,
|
'sp_url': uuid.uuid4().hex,
|
||||||
|
'relay_state_prefix': CONF.saml.relay_state_prefix,
|
||||||
}
|
}
|
||||||
return ref
|
return ref
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue