Merge "Remove the sql token driver and uuid token provider"
This commit is contained in:
commit
69b8815d04
|
@ -20,16 +20,7 @@ You can register your own token provider by configuring the following property:
|
||||||
entry point for the token provider in the ``keystone.token.provider``
|
entry point for the token provider in the ``keystone.token.provider``
|
||||||
namespace.
|
namespace.
|
||||||
|
|
||||||
Each token format uses different technologies to achieve various performance,
|
Below is the detailed list of the token formats supported by keystone.:
|
||||||
scaling, and architectural requirements. The Identity service includes
|
|
||||||
``fernet``, ``pkiz``, ``pki``, and ``uuid`` token providers.
|
|
||||||
|
|
||||||
Below is the detailed list of the token formats:
|
|
||||||
|
|
||||||
UUID
|
|
||||||
``uuid`` tokens must be persisted (using the back end specified in the
|
|
||||||
``[token] driver`` option), but do not require any extra configuration
|
|
||||||
or setup.
|
|
||||||
|
|
||||||
Fernet
|
Fernet
|
||||||
``fernet`` tokens do not need to be persisted at all, but require that you run
|
``fernet`` tokens do not need to be persisted at all, but require that you run
|
||||||
|
@ -38,6 +29,5 @@ Fernet
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
|
|
||||||
UUID and Fernet tokens are both bearer tokens. They
|
Fernet tokens are bearer tokens. They must be protected from unnecessary
|
||||||
must be protected from unnecessary disclosure to prevent unauthorized
|
disclosure to prevent unauthorized access.
|
||||||
access.
|
|
||||||
|
|
|
@ -55,7 +55,6 @@
|
||||||
# features. This list only covers drivers that are in tree. Out of tree
|
# features. This list only covers drivers that are in tree. Out of tree
|
||||||
# drivers should maintain their own equivalent document, and merge it with this
|
# drivers should maintain their own equivalent document, and merge it with this
|
||||||
# when their code merges into core.
|
# when their code merges into core.
|
||||||
driver-impl-uuid=UUID tokens
|
|
||||||
driver-impl-fernet=Fernet tokens
|
driver-impl-fernet=Fernet tokens
|
||||||
|
|
||||||
[operation.create_unscoped_token]
|
[operation.create_unscoped_token]
|
||||||
|
@ -65,7 +64,6 @@ notes=All token providers must be capable of issuing tokens without an explicit
|
||||||
scope of authorization.
|
scope of authorization.
|
||||||
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
||||||
--os-password=<password> token issue
|
--os-password=<password> token issue
|
||||||
driver-impl-uuid=complete
|
|
||||||
driver-impl-fernet=complete
|
driver-impl-fernet=complete
|
||||||
|
|
||||||
[operation.create_system_token]
|
[operation.create_system_token]
|
||||||
|
@ -74,7 +72,6 @@ status=mandatory
|
||||||
notes=All token providers must be capable of issuing system-scoped tokens.
|
notes=All token providers must be capable of issuing system-scoped tokens.
|
||||||
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
||||||
--os-system token issue
|
--os-system token issue
|
||||||
driver-impl-uuid=complete
|
|
||||||
driver-impl-fernet=complete
|
driver-impl-fernet=complete
|
||||||
|
|
||||||
[operation.create_project_scoped_token]
|
[operation.create_project_scoped_token]
|
||||||
|
@ -84,7 +81,6 @@ notes=All token providers must be capable of issuing project-scoped tokens.
|
||||||
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
||||||
--os-password=<password> --os-project-name=<project>
|
--os-password=<password> --os-project-name=<project>
|
||||||
--os-project-domain-name=<domain> token issue
|
--os-project-domain-name=<domain> token issue
|
||||||
driver-impl-uuid=complete
|
|
||||||
driver-impl-fernet=complete
|
driver-impl-fernet=complete
|
||||||
|
|
||||||
[operation.create_domain_scoped_token]
|
[operation.create_domain_scoped_token]
|
||||||
|
@ -94,7 +90,6 @@ notes=Domain-scoped tokens are not required for all use cases, and for some use
|
||||||
cases, projects can be used instead.
|
cases, projects can be used instead.
|
||||||
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
||||||
--os-password=<password> --os-domain-name=<domain> token issue
|
--os-password=<password> --os-domain-name=<domain> token issue
|
||||||
driver-impl-uuid=complete
|
|
||||||
driver-impl-fernet=complete
|
driver-impl-fernet=complete
|
||||||
|
|
||||||
[operation.create_trust_scoped_token]
|
[operation.create_trust_scoped_token]
|
||||||
|
@ -104,7 +99,6 @@ notes=Tokens scoped to a trust convey only the user impersonation and
|
||||||
project-based authorization attributes included in the delegation.
|
project-based authorization attributes included in the delegation.
|
||||||
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
cli=openstack --os-username=<username> --os-user-domain-name=<domain>
|
||||||
--os-password=<password> --os-trust-id=<trust> token issue
|
--os-password=<password> --os-trust-id=<trust> token issue
|
||||||
driver-impl-uuid=complete
|
|
||||||
driver-impl-fernet=complete
|
driver-impl-fernet=complete
|
||||||
|
|
||||||
[operation.create_token_using_oauth]
|
[operation.create_token_using_oauth]
|
||||||
|
@ -112,7 +106,6 @@ title=Create a token given an OAuth access token
|
||||||
status=optional
|
status=optional
|
||||||
notes=OAuth access tokens can be exchanged for keystone tokens.
|
notes=OAuth access tokens can be exchanged for keystone tokens.
|
||||||
cli=
|
cli=
|
||||||
driver-impl-uuid=complete
|
|
||||||
driver-impl-fernet=complete
|
driver-impl-fernet=complete
|
||||||
|
|
||||||
[operation.create_token_with_bind]
|
[operation.create_token_with_bind]
|
||||||
|
@ -121,7 +114,6 @@ status=optional
|
||||||
notes=Tokens can express a binding to an additional authentication method, such
|
notes=Tokens can express a binding to an additional authentication method, such
|
||||||
as kerberos or x509.
|
as kerberos or x509.
|
||||||
cli=
|
cli=
|
||||||
driver-impl-uuid=complete
|
|
||||||
driver-impl-fernet=missing
|
driver-impl-fernet=missing
|
||||||
|
|
||||||
[operation.revoke_token]
|
[operation.revoke_token]
|
||||||
|
@ -132,7 +124,6 @@ notes=Tokens may be individually revoked, such as when a user logs out of
|
||||||
single token may be revoked as a result of this operation (such as when the
|
single token may be revoked as a result of this operation (such as when the
|
||||||
revoked token was previously used to create additional tokens).
|
revoked token was previously used to create additional tokens).
|
||||||
cli=openstack token revoke
|
cli=openstack token revoke
|
||||||
driver-impl-uuid=complete
|
|
||||||
driver-impl-fernet=complete
|
driver-impl-fernet=complete
|
||||||
|
|
||||||
[feature.online_validation]
|
[feature.online_validation]
|
||||||
|
@ -141,7 +132,6 @@ status=mandatory
|
||||||
notes=Keystone must be able to validate the tokens that it issues when
|
notes=Keystone must be able to validate the tokens that it issues when
|
||||||
presented with a token that it previously issued.
|
presented with a token that it previously issued.
|
||||||
cli=
|
cli=
|
||||||
driver-impl-uuid=complete
|
|
||||||
driver-impl-fernet=complete
|
driver-impl-fernet=complete
|
||||||
|
|
||||||
[feature.offline_validation]
|
[feature.offline_validation]
|
||||||
|
@ -151,7 +141,6 @@ notes=Services using Keystone for authentication may want to validate tokens
|
||||||
themselves, rather than calling back to keystone, in order to improve
|
themselves, rather than calling back to keystone, in order to improve
|
||||||
performance and scalability.
|
performance and scalability.
|
||||||
cli=
|
cli=
|
||||||
driver-impl-uuid=missing
|
|
||||||
driver-impl-fernet=missing
|
driver-impl-fernet=missing
|
||||||
|
|
||||||
[feature.non_persistent]
|
[feature.non_persistent]
|
||||||
|
@ -162,5 +151,4 @@ notes=If a token format does not require persistence (such as to a SQL
|
||||||
keystone can issue at once, and there is no need to perform clean up
|
keystone can issue at once, and there is no need to perform clean up
|
||||||
operations such as `keystone-manage token_flush`.
|
operations such as `keystone-manage token_flush`.
|
||||||
cli=
|
cli=
|
||||||
driver-impl-uuid=missing
|
|
||||||
driver-impl-fernet=complete
|
driver-impl-fernet=complete
|
||||||
|
|
|
@ -26,7 +26,6 @@ from keystone import policy
|
||||||
from keystone import resource
|
from keystone import resource
|
||||||
from keystone import revoke
|
from keystone import revoke
|
||||||
from keystone import token
|
from keystone import token
|
||||||
from keystone.token import persistence
|
|
||||||
from keystone import trust
|
from keystone import trust
|
||||||
|
|
||||||
|
|
||||||
|
@ -49,8 +48,7 @@ def load_backends():
|
||||||
identity.Manager, identity.ShadowUsersManager,
|
identity.Manager, identity.ShadowUsersManager,
|
||||||
limit.Manager, oauth1.Manager, policy.Manager,
|
limit.Manager, oauth1.Manager, policy.Manager,
|
||||||
resource.Manager, revoke.Manager, assignment.RoleManager,
|
resource.Manager, revoke.Manager, assignment.RoleManager,
|
||||||
trust.Manager, token.provider.Manager,
|
trust.Manager, token.provider.Manager]
|
||||||
persistence.PersistenceManager]
|
|
||||||
|
|
||||||
drivers = {d._provides_api: d() for d in managers}
|
drivers = {d._provides_api: d() for d in managers}
|
||||||
|
|
||||||
|
|
|
@ -12,11 +12,8 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
import datetime
|
|
||||||
import functools
|
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
import freezegun
|
|
||||||
import mock
|
import mock
|
||||||
from oslo_db import exception as db_exception
|
from oslo_db import exception as db_exception
|
||||||
from oslo_db import options
|
from oslo_db import options
|
||||||
|
@ -43,9 +40,7 @@ from keystone.tests.unit.ksfixtures import database
|
||||||
from keystone.tests.unit.limit import test_backends as limit_tests
|
from keystone.tests.unit.limit import test_backends as limit_tests
|
||||||
from keystone.tests.unit.policy import test_backends as policy_tests
|
from keystone.tests.unit.policy import test_backends as policy_tests
|
||||||
from keystone.tests.unit.resource import test_backends as resource_tests
|
from keystone.tests.unit.resource import test_backends as resource_tests
|
||||||
from keystone.tests.unit.token import test_backends as token_tests
|
|
||||||
from keystone.tests.unit.trust import test_backends as trust_tests
|
from keystone.tests.unit.trust import test_backends as trust_tests
|
||||||
from keystone.token.persistence.backends import sql as token_sql
|
|
||||||
from keystone.trust.backends import sql as trust_sql
|
from keystone.trust.backends import sql as trust_sql
|
||||||
|
|
||||||
|
|
||||||
|
@ -745,132 +740,6 @@ class SqlTrust(SqlTests, trust_tests.TrustTests):
|
||||||
self.assertEqual(trust_ref.expires_at, trust_ref.expires_at_int)
|
self.assertEqual(trust_ref.expires_at, trust_ref.expires_at_int)
|
||||||
|
|
||||||
|
|
||||||
class SqlToken(SqlTests, token_tests.TokenTests):
|
|
||||||
def test_token_revocation_list_uses_right_columns(self):
|
|
||||||
# This query used to be heavy with too many columns. We want
|
|
||||||
# to make sure it is only running with the minimum columns
|
|
||||||
# necessary.
|
|
||||||
|
|
||||||
expected_query_args = (token_sql.TokenModel.id,
|
|
||||||
token_sql.TokenModel.expires,
|
|
||||||
token_sql.TokenModel.extra,)
|
|
||||||
|
|
||||||
with mock.patch.object(token_sql, 'sql') as mock_sql:
|
|
||||||
tok = token_sql.Token()
|
|
||||||
tok.list_revoked_tokens()
|
|
||||||
|
|
||||||
mock_query = mock_sql.session_for_read().__enter__().query
|
|
||||||
mock_query.assert_called_with(*expected_query_args)
|
|
||||||
|
|
||||||
def test_flush_expired_tokens_batch(self):
|
|
||||||
# TODO(dstanek): This test should be rewritten to be less
|
|
||||||
# brittle. The code will likely need to be changed first. I
|
|
||||||
# just copied the spirit of the existing test when I rewrote
|
|
||||||
# mox -> mock. These tests are brittle because they have the
|
|
||||||
# call structure for SQLAlchemy encoded in them.
|
|
||||||
|
|
||||||
# test sqlite dialect
|
|
||||||
with mock.patch.object(token_sql, 'sql') as mock_sql:
|
|
||||||
mock_sql.get_session().bind.dialect.name = 'sqlite'
|
|
||||||
tok = token_sql.Token()
|
|
||||||
tok.flush_expired_tokens()
|
|
||||||
|
|
||||||
filter_mock = mock_sql.get_session().query().filter()
|
|
||||||
self.assertFalse(filter_mock.limit.called)
|
|
||||||
self.assertTrue(filter_mock.delete.called_once)
|
|
||||||
|
|
||||||
def test_flush_expired_tokens_batch_mysql(self):
|
|
||||||
# test mysql dialect, we don't need to test IBM DB SA separately, since
|
|
||||||
# other tests below test the differences between how they use the batch
|
|
||||||
# strategy
|
|
||||||
with mock.patch.object(token_sql, 'sql') as mock_sql:
|
|
||||||
mock_sql.session_for_write().__enter__(
|
|
||||||
).query().filter().delete.return_value = 0
|
|
||||||
|
|
||||||
mock_sql.session_for_write().__enter__(
|
|
||||||
).bind.dialect.name = 'mysql'
|
|
||||||
|
|
||||||
tok = token_sql.Token()
|
|
||||||
expiry_mock = mock.Mock()
|
|
||||||
ITERS = [1, 2, 3]
|
|
||||||
expiry_mock.return_value = iter(ITERS)
|
|
||||||
token_sql._expiry_range_batched = expiry_mock
|
|
||||||
tok.flush_expired_tokens()
|
|
||||||
|
|
||||||
# The expiry strategy is only invoked once, the other calls are via
|
|
||||||
# the yield return.
|
|
||||||
self.assertEqual(1, expiry_mock.call_count)
|
|
||||||
|
|
||||||
mock_delete = mock_sql.session_for_write().__enter__(
|
|
||||||
).query().filter().delete
|
|
||||||
|
|
||||||
self.assertThat(mock_delete.call_args_list,
|
|
||||||
matchers.HasLength(len(ITERS)))
|
|
||||||
|
|
||||||
def test_expiry_range_batched(self):
|
|
||||||
upper_bound_mock = mock.Mock(side_effect=[1, "final value"])
|
|
||||||
sess_mock = mock.Mock()
|
|
||||||
query_mock = sess_mock.query().filter().order_by().offset().limit()
|
|
||||||
query_mock.one.side_effect = [['test'], sql.NotFound()]
|
|
||||||
for i, x in enumerate(token_sql._expiry_range_batched(sess_mock,
|
|
||||||
upper_bound_mock,
|
|
||||||
batch_size=50)):
|
|
||||||
if i == 0:
|
|
||||||
# The first time the batch iterator returns, it should return
|
|
||||||
# the first result that comes back from the database.
|
|
||||||
self.assertEqual('test', x)
|
|
||||||
elif i == 1:
|
|
||||||
# The second time, the database range function should return
|
|
||||||
# nothing, so the batch iterator returns the result of the
|
|
||||||
# upper_bound function
|
|
||||||
self.assertEqual("final value", x)
|
|
||||||
else:
|
|
||||||
self.fail("range batch function returned more than twice")
|
|
||||||
|
|
||||||
def test_expiry_range_strategy_sqlite(self):
|
|
||||||
tok = token_sql.Token()
|
|
||||||
sqlite_strategy = tok._expiry_range_strategy('sqlite')
|
|
||||||
self.assertEqual(token_sql._expiry_range_all, sqlite_strategy)
|
|
||||||
|
|
||||||
def test_expiry_range_strategy_ibm_db_sa(self):
|
|
||||||
tok = token_sql.Token()
|
|
||||||
db2_strategy = tok._expiry_range_strategy('ibm_db_sa')
|
|
||||||
self.assertIsInstance(db2_strategy, functools.partial)
|
|
||||||
self.assertEqual(token_sql._expiry_range_batched, db2_strategy.func)
|
|
||||||
self.assertEqual({'batch_size': 100}, db2_strategy.keywords)
|
|
||||||
|
|
||||||
def test_expiry_range_strategy_mysql(self):
|
|
||||||
tok = token_sql.Token()
|
|
||||||
mysql_strategy = tok._expiry_range_strategy('mysql')
|
|
||||||
self.assertIsInstance(mysql_strategy, functools.partial)
|
|
||||||
self.assertEqual(token_sql._expiry_range_batched, mysql_strategy.func)
|
|
||||||
self.assertEqual({'batch_size': 1000}, mysql_strategy.keywords)
|
|
||||||
|
|
||||||
def test_expiry_range_with_allow_expired(self):
|
|
||||||
window_secs = 200
|
|
||||||
self.config_fixture.config(group='token',
|
|
||||||
allow_expired_window=window_secs)
|
|
||||||
|
|
||||||
tok = token_sql.Token()
|
|
||||||
time = datetime.datetime.utcnow()
|
|
||||||
|
|
||||||
with freezegun.freeze_time(time):
|
|
||||||
# unknown strategy just ensures we are getting the dumbest strategy
|
|
||||||
# that will remove everything in one go
|
|
||||||
strategy = tok._expiry_range_strategy('unkown')
|
|
||||||
upper_bound_func = token_sql._expiry_upper_bound_func
|
|
||||||
|
|
||||||
# session is ignored for dumb strategy
|
|
||||||
expiry_times = list(strategy(session=None,
|
|
||||||
upper_bound_func=upper_bound_func))
|
|
||||||
|
|
||||||
# basically just ensure that we are removing things in the past
|
|
||||||
delta = datetime.timedelta(seconds=window_secs)
|
|
||||||
previous_time = datetime.datetime.utcnow() - delta
|
|
||||||
|
|
||||||
self.assertEqual([previous_time], expiry_times)
|
|
||||||
|
|
||||||
|
|
||||||
class SqlCatalog(SqlTests, catalog_tests.CatalogTests):
|
class SqlCatalog(SqlTests, catalog_tests.CatalogTests):
|
||||||
|
|
||||||
_legacy_endpoint_id_in_endpoint = True
|
_legacy_endpoint_id_in_endpoint = True
|
||||||
|
@ -1047,24 +916,6 @@ class SqlImpliedRoles(SqlTests, assignment_tests.ImpliedRoleTests):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class SqlTokenCacheInvalidationWithUUID(SqlTests,
|
|
||||||
token_tests.TokenCacheInvalidation):
|
|
||||||
def setUp(self):
|
|
||||||
super(SqlTokenCacheInvalidationWithUUID, self).setUp()
|
|
||||||
self._create_test_data()
|
|
||||||
|
|
||||||
def config_overrides(self):
|
|
||||||
super(SqlTokenCacheInvalidationWithUUID, self).config_overrides()
|
|
||||||
# NOTE(lbragstad): The TokenCacheInvalidation tests are coded to work
|
|
||||||
# against a persistent token backend. Only run these with token
|
|
||||||
# providers that issue persistent tokens.
|
|
||||||
self.config_fixture.config(group='token', provider='uuid')
|
|
||||||
|
|
||||||
|
|
||||||
# NOTE(lbragstad): The Fernet token provider doesn't persist tokens in a
|
|
||||||
# backend, so running the TokenCacheInvalidation tests here doesn't make sense.
|
|
||||||
|
|
||||||
|
|
||||||
class SqlFilterTests(SqlTests, identity_tests.FilterTests):
|
class SqlFilterTests(SqlTests, identity_tests.FilterTests):
|
||||||
|
|
||||||
def clean_up_entities(self):
|
def clean_up_entities(self):
|
||||||
|
|
|
@ -53,23 +53,6 @@ CONF = keystone.conf.CONF
|
||||||
PROVIDERS = provider_api.ProviderAPIs
|
PROVIDERS = provider_api.ProviderAPIs
|
||||||
|
|
||||||
|
|
||||||
class CliTestCase(unit.SQLDriverOverrides, unit.TestCase):
|
|
||||||
def config_files(self):
|
|
||||||
config_files = super(CliTestCase, self).config_files()
|
|
||||||
config_files.append(unit.dirs.tests_conf('backend_sql.conf'))
|
|
||||||
return config_files
|
|
||||||
|
|
||||||
def test_token_flush(self):
|
|
||||||
self.useFixture(database.Database())
|
|
||||||
self.load_backends()
|
|
||||||
# NOTE(morgan): we are testing a direct instantiation of the
|
|
||||||
# persistence manager for flushing. We should clear this out so we
|
|
||||||
# don't error. CLI should never impact a running service
|
|
||||||
# and should never actually lock the registry for dependencies.
|
|
||||||
provider_api.ProviderAPIs._clear_registry_instances()
|
|
||||||
cli.TokenFlush.main()
|
|
||||||
|
|
||||||
|
|
||||||
class CliNoConfigTestCase(unit.BaseTestCase):
|
class CliNoConfigTestCase(unit.BaseTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -483,15 +483,6 @@ class RevokeTests(object):
|
||||||
self.assertEqual(2, len(revocation_backend.list_events()))
|
self.assertEqual(2, len(revocation_backend.list_events()))
|
||||||
|
|
||||||
|
|
||||||
class UUIDSqlRevokeTests(test_backend_sql.SqlTests, RevokeTests):
|
|
||||||
def config_overrides(self):
|
|
||||||
super(UUIDSqlRevokeTests, self).config_overrides()
|
|
||||||
self.config_fixture.config(
|
|
||||||
group='token',
|
|
||||||
provider='uuid',
|
|
||||||
revoke_by_id=False)
|
|
||||||
|
|
||||||
|
|
||||||
class FernetSqlRevokeTests(test_backend_sql.SqlTests, RevokeTests):
|
class FernetSqlRevokeTests(test_backend_sql.SqlTests, RevokeTests):
|
||||||
def config_overrides(self):
|
def config_overrides(self):
|
||||||
super(FernetSqlRevokeTests, self).config_overrides()
|
super(FernetSqlRevokeTests, self).config_overrides()
|
||||||
|
|
|
@ -2400,13 +2400,6 @@ class TokenAPITests(object):
|
||||||
frozen_datetime.tick(delta=datetime.timedelta(seconds=12))
|
frozen_datetime.tick(delta=datetime.timedelta(seconds=12))
|
||||||
self._validate_token(token, expected_status=http_client.NOT_FOUND)
|
self._validate_token(token, expected_status=http_client.NOT_FOUND)
|
||||||
|
|
||||||
# flush the tokens, this will only have an effect on sql
|
|
||||||
try:
|
|
||||||
provider_api = PROVIDERS.token_provider_api
|
|
||||||
provider_api._persistence.flush_expired_tokens()
|
|
||||||
except exception.NotImplemented:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# but if we pass allow_expired it validates
|
# but if we pass allow_expired it validates
|
||||||
self._validate_token(token, allow_expired=True)
|
self._validate_token(token, allow_expired=True)
|
||||||
|
|
||||||
|
@ -2543,25 +2536,6 @@ class AllowRescopeScopedTokenDisabledTests(test_v3.RestfulTestCase):
|
||||||
expected_status=http_client.FORBIDDEN)
|
expected_status=http_client.FORBIDDEN)
|
||||||
|
|
||||||
|
|
||||||
class TestUUIDTokenAPIs(test_v3.RestfulTestCase, TokenAPITests,
|
|
||||||
TokenDataTests):
|
|
||||||
def config_overrides(self):
|
|
||||||
super(TestUUIDTokenAPIs, self).config_overrides()
|
|
||||||
self.config_fixture.config(group='token', provider='uuid')
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(TestUUIDTokenAPIs, self).setUp()
|
|
||||||
self.doSetUp()
|
|
||||||
|
|
||||||
def test_v3_token_id(self):
|
|
||||||
auth_data = self.build_authentication_request(
|
|
||||||
user_id=self.user['id'],
|
|
||||||
password=self.user['password'])
|
|
||||||
resp = self.v3_create_token(auth_data)
|
|
||||||
token_data = resp.result
|
|
||||||
self.assertIn('expires_at', token_data['token'])
|
|
||||||
|
|
||||||
|
|
||||||
class TestFernetTokenAPIs(test_v3.RestfulTestCase, TokenAPITests,
|
class TestFernetTokenAPIs(test_v3.RestfulTestCase, TokenAPITests,
|
||||||
TokenDataTests):
|
TokenDataTests):
|
||||||
def config_overrides(self):
|
def config_overrides(self):
|
||||||
|
@ -3460,58 +3434,6 @@ class TestTokenRevokeById(test_v3.RestfulTestCase):
|
||||||
expected_status=http_client.OK)
|
expected_status=http_client.OK)
|
||||||
|
|
||||||
|
|
||||||
class TestTokenRevokeByAssignment(TestTokenRevokeById):
|
|
||||||
|
|
||||||
def config_overrides(self):
|
|
||||||
super(TestTokenRevokeById, self).config_overrides()
|
|
||||||
self.config_fixture.config(
|
|
||||||
group='token',
|
|
||||||
provider='uuid',
|
|
||||||
revoke_by_id=True)
|
|
||||||
|
|
||||||
def test_removing_role_assignment_keeps_other_project_token_groups(self):
|
|
||||||
"""Test assignment isolation.
|
|
||||||
|
|
||||||
Revoking a group role from one project should not invalidate all group
|
|
||||||
users' tokens
|
|
||||||
"""
|
|
||||||
PROVIDERS.assignment_api.create_grant(
|
|
||||||
self.role1['id'], group_id=self.group1['id'],
|
|
||||||
project_id=self.projectB['id']
|
|
||||||
)
|
|
||||||
|
|
||||||
project_token = self.get_requested_token(
|
|
||||||
self.build_authentication_request(
|
|
||||||
user_id=self.user1['id'],
|
|
||||||
password=self.user1['password'],
|
|
||||||
project_id=self.projectB['id']))
|
|
||||||
|
|
||||||
other_project_token = self.get_requested_token(
|
|
||||||
self.build_authentication_request(
|
|
||||||
user_id=self.user1['id'],
|
|
||||||
password=self.user1['password'],
|
|
||||||
project_id=self.projectA['id']))
|
|
||||||
|
|
||||||
PROVIDERS.assignment_api.delete_grant(
|
|
||||||
self.role1['id'], group_id=self.group1['id'],
|
|
||||||
project_id=self.projectB['id']
|
|
||||||
)
|
|
||||||
|
|
||||||
# authorization for the projectA should still succeed
|
|
||||||
self.head('/auth/tokens',
|
|
||||||
headers={'X-Subject-Token': other_project_token},
|
|
||||||
expected_status=http_client.OK)
|
|
||||||
# while token for the projectB should not
|
|
||||||
self.head('/auth/tokens',
|
|
||||||
headers={'X-Subject-Token': project_token},
|
|
||||||
expected_status=http_client.NOT_FOUND)
|
|
||||||
revoked_tokens = [
|
|
||||||
t['id'] for t in PROVIDERS.token_provider_api.list_revoked_tokens()
|
|
||||||
]
|
|
||||||
# token is in token revocation list
|
|
||||||
self.assertIn(project_token, revoked_tokens)
|
|
||||||
|
|
||||||
|
|
||||||
class TestTokenRevokeApi(TestTokenRevokeById):
|
class TestTokenRevokeApi(TestTokenRevokeById):
|
||||||
"""Test token revocation on the v3 Identity API."""
|
"""Test token revocation on the v3 Identity API."""
|
||||||
|
|
||||||
|
@ -3743,19 +3665,6 @@ class AuthExternalDomainBehavior(object):
|
||||||
self.assertEqual(self.user['name'], token['bind']['kerberos'])
|
self.assertEqual(self.user['name'], token['bind']['kerberos'])
|
||||||
|
|
||||||
|
|
||||||
class TestAuthExternalDomainBehaviorWithUUID(AuthExternalDomainBehavior,
|
|
||||||
test_v3.RestfulTestCase):
|
|
||||||
def config_overrides(self):
|
|
||||||
super(TestAuthExternalDomainBehaviorWithUUID, self).config_overrides()
|
|
||||||
self.kerberos = False
|
|
||||||
self.auth_plugin_config_override(external='Domain')
|
|
||||||
self.config_fixture.config(group='token', provider='uuid')
|
|
||||||
|
|
||||||
|
|
||||||
# NOTE(lbragstad): The Fernet token provider doesn't support bind
|
|
||||||
# authentication so we don't inhereit TestAuthExternalDomain here to test it.
|
|
||||||
|
|
||||||
|
|
||||||
class TestAuthExternalDefaultDomain(object):
|
class TestAuthExternalDefaultDomain(object):
|
||||||
content_type = 'json'
|
content_type = 'json'
|
||||||
|
|
||||||
|
@ -3814,29 +3723,6 @@ class TestAuthExternalDefaultDomain(object):
|
||||||
token['bind']['kerberos'])
|
token['bind']['kerberos'])
|
||||||
|
|
||||||
|
|
||||||
class UUIDAuthExternalDefaultDomain(TestAuthExternalDefaultDomain,
|
|
||||||
test_v3.RestfulTestCase):
|
|
||||||
|
|
||||||
def config_overrides(self):
|
|
||||||
super(UUIDAuthExternalDefaultDomain, self).config_overrides()
|
|
||||||
self.config_fixture.config(group='token', provider='uuid')
|
|
||||||
|
|
||||||
|
|
||||||
class UUIDAuthKerberos(AuthExternalDomainBehavior, test_v3.RestfulTestCase):
|
|
||||||
|
|
||||||
def config_overrides(self):
|
|
||||||
super(UUIDAuthKerberos, self).config_overrides()
|
|
||||||
self.kerberos = True
|
|
||||||
self.config_fixture.config(group='token', provider='uuid')
|
|
||||||
self.auth_plugin_config_override(
|
|
||||||
methods=['kerberos', 'password', 'token'])
|
|
||||||
|
|
||||||
|
|
||||||
# NOTE(lbragstad): The Fernet token provider doesn't support bind
|
|
||||||
# authentication so we don't inherit AuthExternalDomainBehavior here to test
|
|
||||||
# it.
|
|
||||||
|
|
||||||
|
|
||||||
class TestAuthJSONExternal(test_v3.RestfulTestCase):
|
class TestAuthJSONExternal(test_v3.RestfulTestCase):
|
||||||
content_type = 'json'
|
content_type = 'json'
|
||||||
|
|
||||||
|
@ -5380,18 +5266,6 @@ class TestFetchRevocationList(object):
|
||||||
self.assertEqual({'revoked': [exp_token_revoke_data]}, res.json)
|
self.assertEqual({'revoked': [exp_token_revoke_data]}, res.json)
|
||||||
|
|
||||||
|
|
||||||
class UUIDFetchRevocationList(TestFetchRevocationList,
|
|
||||||
test_v3.RestfulTestCase):
|
|
||||||
|
|
||||||
def config_overrides(self):
|
|
||||||
super(UUIDFetchRevocationList, self).config_overrides()
|
|
||||||
self.config_fixture.config(group='token', provider='uuid')
|
|
||||||
|
|
||||||
|
|
||||||
# NOTE(lbragstad): The Fernet token provider doesn't use Revocation lists so
|
|
||||||
# don't inherit TestFetchRevocationList here to test it.
|
|
||||||
|
|
||||||
|
|
||||||
class ApplicationCredentialAuth(test_v3.RestfulTestCase):
|
class ApplicationCredentialAuth(test_v3.RestfulTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -517,10 +517,6 @@ class IdentityTestCase(test_v3.RestfulTestCase):
|
||||||
self.assertRaises(exception.CredentialNotFound,
|
self.assertRaises(exception.CredentialNotFound,
|
||||||
PROVIDERS.credential_api.get_credential,
|
PROVIDERS.credential_api.get_credential,
|
||||||
self.credential['id'])
|
self.credential['id'])
|
||||||
# And the no tokens we remain valid
|
|
||||||
tokens = PROVIDERS.token_provider_api._persistence._list_tokens(
|
|
||||||
self.user['id'])
|
|
||||||
self.assertEqual(0, len(tokens))
|
|
||||||
# But the credential for user2 is unaffected
|
# But the credential for user2 is unaffected
|
||||||
r = PROVIDERS.credential_api.get_credential(credential2['id'])
|
r = PROVIDERS.credential_api.get_credential(credential2['id'])
|
||||||
self.assertDictEqual(credential2, r)
|
self.assertDictEqual(credential2, r)
|
||||||
|
|
|
@ -662,13 +662,6 @@ class FernetAuthTokenTests(AuthTokenTests, OAuthFlowTests):
|
||||||
self.skipTest('Fernet tokens are never persisted in the backend.')
|
self.skipTest('Fernet tokens are never persisted in the backend.')
|
||||||
|
|
||||||
|
|
||||||
class UUIDAuthTokenTests(AuthTokenTests, OAuthFlowTests):
|
|
||||||
|
|
||||||
def config_overrides(self):
|
|
||||||
super(UUIDAuthTokenTests, self).config_overrides()
|
|
||||||
self.config_fixture.config(group='token', provider='uuid')
|
|
||||||
|
|
||||||
|
|
||||||
class MaliciousOAuth1Tests(OAuth1Tests):
|
class MaliciousOAuth1Tests(OAuth1Tests):
|
||||||
|
|
||||||
def _switch_baseurl_scheme(self):
|
def _switch_baseurl_scheme(self):
|
||||||
|
|
|
@ -1,502 +0,0 @@
|
||||||
# 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 copy
|
|
||||||
import datetime
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
import freezegun
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
import six
|
|
||||||
from six.moves import range
|
|
||||||
|
|
||||||
from keystone.common import provider_api
|
|
||||||
from keystone import exception
|
|
||||||
from keystone.tests import unit
|
|
||||||
from keystone.token import provider
|
|
||||||
|
|
||||||
|
|
||||||
NULL_OBJECT = object()
|
|
||||||
PROVIDERS = provider_api.ProviderAPIs
|
|
||||||
|
|
||||||
|
|
||||||
class TokenTests(object):
|
|
||||||
def _create_token_id(self):
|
|
||||||
return uuid.uuid4().hex
|
|
||||||
|
|
||||||
def _assert_revoked_token_list_matches_token_persistence(
|
|
||||||
self, revoked_token_id_list):
|
|
||||||
# Assert that the list passed in matches the list returned by the
|
|
||||||
# token persistence service
|
|
||||||
persistence_list = [
|
|
||||||
x['id']
|
|
||||||
for x in PROVIDERS.token_provider_api.list_revoked_tokens()
|
|
||||||
]
|
|
||||||
self.assertEqual(persistence_list, revoked_token_id_list)
|
|
||||||
|
|
||||||
def test_token_crud(self):
|
|
||||||
token_id = self._create_token_id()
|
|
||||||
data = {'id': token_id, 'a': 'b',
|
|
||||||
'trust_id': None,
|
|
||||||
'user': {'id': 'testuserid'},
|
|
||||||
'token_data': {'access': {'token': {
|
|
||||||
'audit_ids': [uuid.uuid4().hex]}}}}
|
|
||||||
data_ref = PROVIDERS.token_provider_api._persistence.create_token(
|
|
||||||
token_id, data
|
|
||||||
)
|
|
||||||
expires = data_ref.pop('expires')
|
|
||||||
data_ref.pop('user_id')
|
|
||||||
self.assertIsInstance(expires, datetime.datetime)
|
|
||||||
data_ref.pop('id')
|
|
||||||
data.pop('id')
|
|
||||||
self.assertDictEqual(data, data_ref)
|
|
||||||
|
|
||||||
new_data_ref = PROVIDERS.token_provider_api._persistence.get_token(
|
|
||||||
token_id
|
|
||||||
)
|
|
||||||
expires = new_data_ref.pop('expires')
|
|
||||||
self.assertIsInstance(expires, datetime.datetime)
|
|
||||||
new_data_ref.pop('user_id')
|
|
||||||
new_data_ref.pop('id')
|
|
||||||
|
|
||||||
self.assertEqual(data, new_data_ref)
|
|
||||||
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token(token_id)
|
|
||||||
self.assertRaises(
|
|
||||||
exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api._persistence.get_token, token_id)
|
|
||||||
self.assertRaises(
|
|
||||||
exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token, token_id)
|
|
||||||
|
|
||||||
def create_token_sample_data(self, token_id=None, tenant_id=None,
|
|
||||||
trust_id=None, user_id=None, expires=None):
|
|
||||||
if token_id is None:
|
|
||||||
token_id = self._create_token_id()
|
|
||||||
if user_id is None:
|
|
||||||
user_id = 'testuserid'
|
|
||||||
# FIXME(morganfainberg): These tokens look nothing like "Real" tokens.
|
|
||||||
# This should be fixed when token issuance is cleaned up.
|
|
||||||
data = {'id': token_id, 'a': 'b',
|
|
||||||
'user': {'id': user_id},
|
|
||||||
'access': {'token': {'audit_ids': [uuid.uuid4().hex]}}}
|
|
||||||
if tenant_id is not None:
|
|
||||||
data['tenant'] = {'id': tenant_id, 'name': tenant_id}
|
|
||||||
if tenant_id is NULL_OBJECT:
|
|
||||||
data['tenant'] = None
|
|
||||||
if expires is not None:
|
|
||||||
data['expires'] = expires
|
|
||||||
if trust_id is not None:
|
|
||||||
data['trust_id'] = trust_id
|
|
||||||
data['access'].setdefault('trust', {})
|
|
||||||
# Testuserid2 is used here since a trustee will be different in
|
|
||||||
# the cases of impersonation and therefore should not match the
|
|
||||||
# token's user_id.
|
|
||||||
data['access']['trust']['trustee_user_id'] = 'testuserid2'
|
|
||||||
data['token_version'] = provider.V3
|
|
||||||
# Issue token stores a copy of all token data at token['token_data'].
|
|
||||||
# This emulates that assumption as part of the test.
|
|
||||||
data['token_data'] = copy.deepcopy(data)
|
|
||||||
new_token = PROVIDERS.token_provider_api._persistence.create_token(
|
|
||||||
token_id, data
|
|
||||||
)
|
|
||||||
return new_token['id'], data
|
|
||||||
|
|
||||||
def test_delete_tokens(self):
|
|
||||||
tokens = PROVIDERS.token_provider_api._persistence._list_tokens(
|
|
||||||
'testuserid')
|
|
||||||
self.assertEqual(0, len(tokens))
|
|
||||||
token_id1, data = self.create_token_sample_data(
|
|
||||||
tenant_id='testtenantid')
|
|
||||||
token_id2, data = self.create_token_sample_data(
|
|
||||||
tenant_id='testtenantid')
|
|
||||||
token_id3, data = self.create_token_sample_data(
|
|
||||||
tenant_id='testtenantid',
|
|
||||||
user_id='testuserid1')
|
|
||||||
tokens = PROVIDERS.token_provider_api._persistence._list_tokens(
|
|
||||||
'testuserid')
|
|
||||||
self.assertEqual(2, len(tokens))
|
|
||||||
self.assertIn(token_id2, tokens)
|
|
||||||
self.assertIn(token_id1, tokens)
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_tokens(
|
|
||||||
user_id='testuserid',
|
|
||||||
tenant_id='testtenantid')
|
|
||||||
tokens = PROVIDERS.token_provider_api._persistence._list_tokens(
|
|
||||||
'testuserid')
|
|
||||||
self.assertEqual(0, len(tokens))
|
|
||||||
self.assertRaises(exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api._persistence.get_token,
|
|
||||||
token_id1)
|
|
||||||
self.assertRaises(exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api._persistence.get_token,
|
|
||||||
token_id2)
|
|
||||||
|
|
||||||
PROVIDERS.token_provider_api._persistence.get_token(token_id3)
|
|
||||||
|
|
||||||
def test_delete_tokens_trust(self):
|
|
||||||
tokens = PROVIDERS.token_provider_api._persistence._list_tokens(
|
|
||||||
user_id='testuserid')
|
|
||||||
self.assertEqual(0, len(tokens))
|
|
||||||
token_id1, data = self.create_token_sample_data(
|
|
||||||
tenant_id='testtenantid',
|
|
||||||
trust_id='testtrustid')
|
|
||||||
token_id2, data = self.create_token_sample_data(
|
|
||||||
tenant_id='testtenantid',
|
|
||||||
user_id='testuserid1',
|
|
||||||
trust_id='testtrustid1')
|
|
||||||
tokens = PROVIDERS.token_provider_api._persistence._list_tokens(
|
|
||||||
'testuserid')
|
|
||||||
self.assertEqual(1, len(tokens))
|
|
||||||
self.assertIn(token_id1, tokens)
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_tokens(
|
|
||||||
user_id='testuserid',
|
|
||||||
tenant_id='testtenantid',
|
|
||||||
trust_id='testtrustid')
|
|
||||||
self.assertRaises(exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api._persistence.get_token,
|
|
||||||
token_id1)
|
|
||||||
PROVIDERS.token_provider_api._persistence.get_token(token_id2)
|
|
||||||
|
|
||||||
def _test_token_list(self, token_list_fn):
|
|
||||||
tokens = token_list_fn('testuserid')
|
|
||||||
self.assertEqual(0, len(tokens))
|
|
||||||
token_id1, data = self.create_token_sample_data()
|
|
||||||
tokens = token_list_fn('testuserid')
|
|
||||||
self.assertEqual(1, len(tokens))
|
|
||||||
self.assertIn(token_id1, tokens)
|
|
||||||
token_id2, data = self.create_token_sample_data()
|
|
||||||
tokens = token_list_fn('testuserid')
|
|
||||||
self.assertEqual(2, len(tokens))
|
|
||||||
self.assertIn(token_id2, tokens)
|
|
||||||
self.assertIn(token_id1, tokens)
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token(token_id1)
|
|
||||||
tokens = token_list_fn('testuserid')
|
|
||||||
self.assertIn(token_id2, tokens)
|
|
||||||
self.assertNotIn(token_id1, tokens)
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token(token_id2)
|
|
||||||
tokens = token_list_fn('testuserid')
|
|
||||||
self.assertNotIn(token_id2, tokens)
|
|
||||||
self.assertNotIn(token_id1, tokens)
|
|
||||||
|
|
||||||
# tenant-specific tokens
|
|
||||||
tenant1 = uuid.uuid4().hex
|
|
||||||
tenant2 = uuid.uuid4().hex
|
|
||||||
token_id3, data = self.create_token_sample_data(tenant_id=tenant1)
|
|
||||||
token_id4, data = self.create_token_sample_data(tenant_id=tenant2)
|
|
||||||
# test for existing but empty tenant (LP:1078497)
|
|
||||||
token_id5, data = self.create_token_sample_data(tenant_id=NULL_OBJECT)
|
|
||||||
tokens = token_list_fn('testuserid')
|
|
||||||
self.assertEqual(3, len(tokens))
|
|
||||||
self.assertNotIn(token_id1, tokens)
|
|
||||||
self.assertNotIn(token_id2, tokens)
|
|
||||||
self.assertIn(token_id3, tokens)
|
|
||||||
self.assertIn(token_id4, tokens)
|
|
||||||
self.assertIn(token_id5, tokens)
|
|
||||||
tokens = token_list_fn('testuserid', tenant2)
|
|
||||||
self.assertEqual(1, len(tokens))
|
|
||||||
self.assertNotIn(token_id1, tokens)
|
|
||||||
self.assertNotIn(token_id2, tokens)
|
|
||||||
self.assertNotIn(token_id3, tokens)
|
|
||||||
self.assertIn(token_id4, tokens)
|
|
||||||
|
|
||||||
def test_token_list(self):
|
|
||||||
self._test_token_list(
|
|
||||||
PROVIDERS.token_provider_api._persistence._list_tokens)
|
|
||||||
|
|
||||||
def test_token_list_trust(self):
|
|
||||||
trust_id = uuid.uuid4().hex
|
|
||||||
token_id5, data = self.create_token_sample_data(trust_id=trust_id)
|
|
||||||
tokens = PROVIDERS.token_provider_api._persistence._list_tokens(
|
|
||||||
'testuserid', trust_id=trust_id)
|
|
||||||
self.assertEqual(1, len(tokens))
|
|
||||||
self.assertIn(token_id5, tokens)
|
|
||||||
|
|
||||||
def test_get_token_returns_not_found(self):
|
|
||||||
self.assertRaises(exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api._persistence.get_token,
|
|
||||||
uuid.uuid4().hex)
|
|
||||||
|
|
||||||
def test_delete_token_returns_not_found(self):
|
|
||||||
self.assertRaises(
|
|
||||||
exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token,
|
|
||||||
uuid.uuid4().hex
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_null_expires_token(self):
|
|
||||||
token_id = uuid.uuid4().hex
|
|
||||||
data = {'id': token_id, 'id_hash': token_id, 'a': 'b', 'expires': None,
|
|
||||||
'user': {'id': 'testuserid'}}
|
|
||||||
data_ref = PROVIDERS.token_provider_api._persistence.create_token(
|
|
||||||
token_id, data
|
|
||||||
)
|
|
||||||
self.assertIsNotNone(data_ref['expires'])
|
|
||||||
new_data_ref = PROVIDERS.token_provider_api._persistence.get_token(
|
|
||||||
token_id
|
|
||||||
)
|
|
||||||
|
|
||||||
# MySQL doesn't store microseconds, so discard them before testing
|
|
||||||
data_ref['expires'] = data_ref['expires'].replace(microsecond=0)
|
|
||||||
new_data_ref['expires'] = new_data_ref['expires'].replace(
|
|
||||||
microsecond=0)
|
|
||||||
|
|
||||||
self.assertEqual(data_ref, new_data_ref)
|
|
||||||
|
|
||||||
def check_list_revoked_tokens(self, token_infos):
|
|
||||||
revocation_list = PROVIDERS.token_provider_api.list_revoked_tokens()
|
|
||||||
revoked_ids = [x['id'] for x in revocation_list]
|
|
||||||
revoked_audit_ids = [x['audit_id'] for x in revocation_list]
|
|
||||||
self._assert_revoked_token_list_matches_token_persistence(revoked_ids)
|
|
||||||
for token_id, audit_id in token_infos:
|
|
||||||
self.assertIn(token_id, revoked_ids)
|
|
||||||
self.assertIn(audit_id, revoked_audit_ids)
|
|
||||||
|
|
||||||
def delete_token(self):
|
|
||||||
token_id = uuid.uuid4().hex
|
|
||||||
audit_id = uuid.uuid4().hex
|
|
||||||
data = {'id_hash': token_id, 'id': token_id, 'a': 'b',
|
|
||||||
'user': {'id': 'testuserid'},
|
|
||||||
'token_data': {'token': {'audit_ids': [audit_id]}}}
|
|
||||||
data_ref = PROVIDERS.token_provider_api._persistence.create_token(
|
|
||||||
token_id, data
|
|
||||||
)
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token(token_id)
|
|
||||||
self.assertRaises(
|
|
||||||
exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api._persistence.get_token,
|
|
||||||
data_ref['id'])
|
|
||||||
self.assertRaises(
|
|
||||||
exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token,
|
|
||||||
data_ref['id'])
|
|
||||||
return (token_id, audit_id)
|
|
||||||
|
|
||||||
def test_list_revoked_tokens_returns_empty_list(self):
|
|
||||||
revoked_ids = [
|
|
||||||
x['id'] for x in PROVIDERS.token_provider_api.list_revoked_tokens()
|
|
||||||
]
|
|
||||||
self._assert_revoked_token_list_matches_token_persistence(revoked_ids)
|
|
||||||
self.assertEqual([], revoked_ids)
|
|
||||||
|
|
||||||
def test_list_revoked_tokens_for_single_token(self):
|
|
||||||
self.check_list_revoked_tokens([self.delete_token()])
|
|
||||||
|
|
||||||
def test_list_revoked_tokens_for_multiple_tokens(self):
|
|
||||||
self.check_list_revoked_tokens([self.delete_token()
|
|
||||||
for x in range(2)])
|
|
||||||
|
|
||||||
def test_flush_expired_token(self):
|
|
||||||
token_id = uuid.uuid4().hex
|
|
||||||
window = self.config_fixture.conf.token.allow_expired_window + 5
|
|
||||||
expire_time = timeutils.utcnow() - datetime.timedelta(minutes=window)
|
|
||||||
data = {'id_hash': token_id, 'id': token_id, 'a': 'b',
|
|
||||||
'expires': expire_time,
|
|
||||||
'trust_id': None,
|
|
||||||
'user': {'id': 'testuserid'}}
|
|
||||||
data_ref = PROVIDERS.token_provider_api._persistence.create_token(
|
|
||||||
token_id, data
|
|
||||||
)
|
|
||||||
data_ref.pop('user_id')
|
|
||||||
self.assertDictEqual(data, data_ref)
|
|
||||||
|
|
||||||
token_id = uuid.uuid4().hex
|
|
||||||
expire_time = timeutils.utcnow() + datetime.timedelta(minutes=window)
|
|
||||||
data = {'id_hash': token_id, 'id': token_id, 'a': 'b',
|
|
||||||
'expires': expire_time,
|
|
||||||
'trust_id': None,
|
|
||||||
'user': {'id': 'testuserid'}}
|
|
||||||
data_ref = PROVIDERS.token_provider_api._persistence.create_token(
|
|
||||||
token_id, data
|
|
||||||
)
|
|
||||||
data_ref.pop('user_id')
|
|
||||||
self.assertDictEqual(data, data_ref)
|
|
||||||
|
|
||||||
PROVIDERS.token_provider_api._persistence.flush_expired_tokens()
|
|
||||||
tokens = PROVIDERS.token_provider_api._persistence._list_tokens(
|
|
||||||
'testuserid')
|
|
||||||
self.assertEqual(1, len(tokens))
|
|
||||||
self.assertIn(token_id, tokens)
|
|
||||||
|
|
||||||
@unit.skip_if_cache_disabled('token')
|
|
||||||
def test_revocation_list_cache(self):
|
|
||||||
expire_time = timeutils.utcnow() + datetime.timedelta(minutes=10)
|
|
||||||
token_id = uuid.uuid4().hex
|
|
||||||
token_data = {'id_hash': token_id, 'id': token_id, 'a': 'b',
|
|
||||||
'expires': expire_time,
|
|
||||||
'trust_id': None,
|
|
||||||
'user': {'id': 'testuserid'},
|
|
||||||
'token_data': {'token': {
|
|
||||||
'audit_ids': [uuid.uuid4().hex]}}}
|
|
||||||
token2_id = uuid.uuid4().hex
|
|
||||||
token2_data = {'id_hash': token2_id, 'id': token2_id, 'a': 'b',
|
|
||||||
'expires': expire_time,
|
|
||||||
'trust_id': None,
|
|
||||||
'user': {'id': 'testuserid'},
|
|
||||||
'token_data': {'token': {
|
|
||||||
'audit_ids': [uuid.uuid4().hex]}}}
|
|
||||||
# Create 2 Tokens.
|
|
||||||
PROVIDERS.token_provider_api._persistence.create_token(
|
|
||||||
token_id, token_data
|
|
||||||
)
|
|
||||||
PROVIDERS.token_provider_api._persistence.create_token(
|
|
||||||
token2_id, token2_data
|
|
||||||
)
|
|
||||||
# Verify the revocation list is empty.
|
|
||||||
self.assertEqual(
|
|
||||||
[], PROVIDERS.token_provider_api._persistence.list_revoked_tokens()
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
[], PROVIDERS.token_provider_api.list_revoked_tokens()
|
|
||||||
)
|
|
||||||
# Delete a token directly, bypassing the manager.
|
|
||||||
PROVIDERS.token_provider_api._persistence.driver.delete_token(token_id)
|
|
||||||
# Verify the revocation list is still empty.
|
|
||||||
self.assertEqual(
|
|
||||||
[], PROVIDERS.token_provider_api._persistence.list_revoked_tokens()
|
|
||||||
)
|
|
||||||
self.assertEqual(
|
|
||||||
[], PROVIDERS.token_provider_api.list_revoked_tokens()
|
|
||||||
)
|
|
||||||
# Invalidate the revocation list.
|
|
||||||
PROVIDERS.token_provider_api._persistence.invalidate_revocation_list()
|
|
||||||
# Verify the deleted token is in the revocation list.
|
|
||||||
revoked_ids = [
|
|
||||||
x['id'] for x in PROVIDERS.token_provider_api.list_revoked_tokens()
|
|
||||||
]
|
|
||||||
self._assert_revoked_token_list_matches_token_persistence(revoked_ids)
|
|
||||||
self.assertIn(token_id, revoked_ids)
|
|
||||||
# Delete the second token, through the manager
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token(token2_id)
|
|
||||||
revoked_ids = [
|
|
||||||
x['id'] for x in PROVIDERS.token_provider_api.list_revoked_tokens()
|
|
||||||
]
|
|
||||||
self._assert_revoked_token_list_matches_token_persistence(revoked_ids)
|
|
||||||
# Verify both tokens are in the revocation list.
|
|
||||||
self.assertIn(token_id, revoked_ids)
|
|
||||||
self.assertIn(token2_id, revoked_ids)
|
|
||||||
|
|
||||||
def test_predictable_revoked_uuid_token_id(self):
|
|
||||||
token_id = uuid.uuid4().hex
|
|
||||||
token = {'user': {'id': uuid.uuid4().hex},
|
|
||||||
'token_data': {'token': {'audit_ids': [uuid.uuid4().hex]}}}
|
|
||||||
|
|
||||||
PROVIDERS.token_provider_api._persistence.create_token(token_id, token)
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token(token_id)
|
|
||||||
|
|
||||||
revoked_tokens = PROVIDERS.token_provider_api.list_revoked_tokens()
|
|
||||||
revoked_ids = [x['id'] for x in revoked_tokens]
|
|
||||||
self._assert_revoked_token_list_matches_token_persistence(revoked_ids)
|
|
||||||
self.assertIn(token_id, revoked_ids)
|
|
||||||
for t in revoked_tokens:
|
|
||||||
self.assertIn('expires', t)
|
|
||||||
|
|
||||||
def test_create_unicode_token_id(self):
|
|
||||||
token_id = six.text_type(self._create_token_id())
|
|
||||||
self.create_token_sample_data(token_id=token_id)
|
|
||||||
PROVIDERS.token_provider_api._persistence.get_token(token_id)
|
|
||||||
|
|
||||||
def test_create_unicode_user_id(self):
|
|
||||||
user_id = six.text_type(uuid.uuid4().hex)
|
|
||||||
token_id, data = self.create_token_sample_data(user_id=user_id)
|
|
||||||
PROVIDERS.token_provider_api._persistence.get_token(token_id)
|
|
||||||
|
|
||||||
|
|
||||||
class TokenCacheInvalidation(object):
|
|
||||||
def _create_test_data(self):
|
|
||||||
time = datetime.datetime.utcnow()
|
|
||||||
with freezegun.freeze_time(time) as frozen_datetime:
|
|
||||||
# Create an equivalent of a scoped token
|
|
||||||
token_id, data = PROVIDERS.token_provider_api.issue_token(
|
|
||||||
self.user_foo['id'],
|
|
||||||
['password'],
|
|
||||||
project_id=self.tenant_bar['id']
|
|
||||||
)
|
|
||||||
self.scoped_token_id = token_id
|
|
||||||
|
|
||||||
# ..and an un-scoped one
|
|
||||||
token_id, data = PROVIDERS.token_provider_api.issue_token(
|
|
||||||
self.user_foo['id'],
|
|
||||||
['password']
|
|
||||||
)
|
|
||||||
self.unscoped_token_id = token_id
|
|
||||||
frozen_datetime.tick(delta=datetime.timedelta(seconds=1))
|
|
||||||
|
|
||||||
# Validate them, in the various ways possible - this will load the
|
|
||||||
# responses into the token cache.
|
|
||||||
PROVIDERS.token_provider_api.validate_token(self.scoped_token_id)
|
|
||||||
PROVIDERS.token_provider_api.validate_token(self.unscoped_token_id)
|
|
||||||
|
|
||||||
def test_delete_unscoped_token(self):
|
|
||||||
time = datetime.datetime.utcnow()
|
|
||||||
with freezegun.freeze_time(time) as frozen_datetime:
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token(
|
|
||||||
self.unscoped_token_id)
|
|
||||||
frozen_datetime.tick(delta=datetime.timedelta(seconds=1))
|
|
||||||
|
|
||||||
# Ensure the unscoped token is invalid
|
|
||||||
self.assertRaises(
|
|
||||||
exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api.validate_token,
|
|
||||||
self.unscoped_token_id)
|
|
||||||
# Ensure the scoped token is still valid
|
|
||||||
PROVIDERS.token_provider_api.validate_token(self.scoped_token_id)
|
|
||||||
|
|
||||||
def test_delete_scoped_token_by_id(self):
|
|
||||||
time = datetime.datetime.utcnow()
|
|
||||||
with freezegun.freeze_time(time) as frozen_datetime:
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_token(
|
|
||||||
self.scoped_token_id
|
|
||||||
)
|
|
||||||
frozen_datetime.tick(delta=datetime.timedelta(seconds=1))
|
|
||||||
|
|
||||||
# Ensure the project token is invalid
|
|
||||||
self.assertRaises(
|
|
||||||
exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api.validate_token,
|
|
||||||
self.scoped_token_id)
|
|
||||||
# Ensure the unscoped token is still valid
|
|
||||||
PROVIDERS.token_provider_api.validate_token(self.unscoped_token_id)
|
|
||||||
|
|
||||||
def test_delete_scoped_token_by_user(self):
|
|
||||||
time = datetime.datetime.utcnow()
|
|
||||||
with freezegun.freeze_time(time) as frozen_datetime:
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_tokens(
|
|
||||||
self.user_foo['id']
|
|
||||||
)
|
|
||||||
frozen_datetime.tick(delta=datetime.timedelta(seconds=1))
|
|
||||||
|
|
||||||
# Since we are deleting all tokens for this user, they should all
|
|
||||||
# now be invalid.
|
|
||||||
self.assertRaises(
|
|
||||||
exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api.validate_token,
|
|
||||||
self.scoped_token_id)
|
|
||||||
self.assertRaises(
|
|
||||||
exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api.validate_token,
|
|
||||||
self.unscoped_token_id)
|
|
||||||
|
|
||||||
def test_delete_scoped_token_by_user_and_tenant(self):
|
|
||||||
time = datetime.datetime.utcnow()
|
|
||||||
with freezegun.freeze_time(time) as frozen_datetime:
|
|
||||||
PROVIDERS.token_provider_api._persistence.delete_tokens(
|
|
||||||
self.user_foo['id'],
|
|
||||||
tenant_id=self.tenant_bar['id'])
|
|
||||||
frozen_datetime.tick(delta=datetime.timedelta(seconds=1))
|
|
||||||
|
|
||||||
# Ensure the scoped token is invalid
|
|
||||||
self.assertRaises(
|
|
||||||
exception.TokenNotFound,
|
|
||||||
PROVIDERS.token_provider_api.validate_token,
|
|
||||||
self.scoped_token_id)
|
|
||||||
# Ensure the unscoped token is still valid
|
|
||||||
PROVIDERS.token_provider_api.validate_token(self.unscoped_token_id)
|
|
|
@ -1,26 +0,0 @@
|
||||||
# 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.tests import unit
|
|
||||||
from keystone.token.providers import uuid
|
|
||||||
|
|
||||||
|
|
||||||
class TestUuidTokenProvider(unit.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
super(TestUuidTokenProvider, self).setUp()
|
|
||||||
self.provider = uuid.Provider()
|
|
||||||
|
|
||||||
def test_supports_bind_authentication_returns_true(self):
|
|
||||||
self.assertTrue(self.provider._supports_bind_authentication)
|
|
||||||
|
|
||||||
def test_need_persistence_return_true(self):
|
|
||||||
self.assertIs(True, self.provider.needs_persistence())
|
|
|
@ -12,5 +12,4 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from keystone.token import persistence # noqa
|
|
||||||
from keystone.token import provider # noqa
|
from keystone.token import provider # noqa
|
||||||
|
|
|
@ -1,16 +0,0 @@
|
||||||
# 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.token.persistence.core import * # noqa
|
|
||||||
|
|
||||||
|
|
||||||
__all__ = ('PersistenceManager', 'TokenDriverBase')
|
|
|
@ -1,306 +0,0 @@
|
||||||
# Copyright 2012 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
|
|
||||||
# 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 copy
|
|
||||||
import datetime
|
|
||||||
import functools
|
|
||||||
|
|
||||||
from oslo_log import log
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
|
|
||||||
from keystone.common import sql
|
|
||||||
import keystone.conf
|
|
||||||
from keystone import exception
|
|
||||||
from keystone import token
|
|
||||||
from keystone.token.providers import common
|
|
||||||
|
|
||||||
|
|
||||||
CONF = keystone.conf.CONF
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class TokenModel(sql.ModelBase, sql.ModelDictMixinWithExtras):
|
|
||||||
__tablename__ = 'token'
|
|
||||||
attributes = ['id', 'expires', 'user_id', 'trust_id']
|
|
||||||
id = sql.Column(sql.String(64), primary_key=True)
|
|
||||||
expires = sql.Column(sql.DateTime(), default=None)
|
|
||||||
extra = sql.Column(sql.JsonBlob())
|
|
||||||
valid = sql.Column(sql.Boolean(), default=True, nullable=False)
|
|
||||||
user_id = sql.Column(sql.String(64))
|
|
||||||
trust_id = sql.Column(sql.String(64))
|
|
||||||
__table_args__ = (
|
|
||||||
sql.Index('ix_token_expires', 'expires'),
|
|
||||||
sql.Index('ix_token_expires_valid', 'expires', 'valid'),
|
|
||||||
sql.Index('ix_token_user_id', 'user_id'),
|
|
||||||
sql.Index('ix_token_trust_id', 'trust_id')
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def _expiry_upper_bound_func():
|
|
||||||
# don't flush anything within the grace window
|
|
||||||
sec = datetime.timedelta(seconds=CONF.token.allow_expired_window)
|
|
||||||
return timeutils.utcnow() - sec
|
|
||||||
|
|
||||||
|
|
||||||
def _expiry_range_batched(session, upper_bound_func, batch_size):
|
|
||||||
"""Return the stop point of the next batch for expiration.
|
|
||||||
|
|
||||||
Return the timestamp of the next token that is `batch_size` rows from
|
|
||||||
being the oldest expired token.
|
|
||||||
"""
|
|
||||||
# This expiry strategy splits the tokens into roughly equal sized batches
|
|
||||||
# to be deleted. It does this by finding the timestamp of a token
|
|
||||||
# `batch_size` rows from the oldest token and yielding that to the caller.
|
|
||||||
# It's expected that the caller will then delete all rows with a timestamp
|
|
||||||
# equal to or older than the one yielded. This may delete slightly more
|
|
||||||
# tokens than the batch_size, but that should be ok in almost all cases.
|
|
||||||
LOG.debug('Token expiration batch size: %d', batch_size)
|
|
||||||
query = session.query(TokenModel.expires)
|
|
||||||
query = query.filter(TokenModel.expires < upper_bound_func())
|
|
||||||
query = query.order_by(TokenModel.expires)
|
|
||||||
query = query.offset(batch_size - 1)
|
|
||||||
query = query.limit(1)
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
next_expiration = query.one()[0]
|
|
||||||
except sql.NotFound:
|
|
||||||
# There are less than `batch_size` rows remaining, so fall
|
|
||||||
# through to the normal delete
|
|
||||||
break
|
|
||||||
yield next_expiration
|
|
||||||
yield upper_bound_func()
|
|
||||||
|
|
||||||
|
|
||||||
def _expiry_range_all(session, upper_bound_func):
|
|
||||||
"""Expire all tokens in one pass."""
|
|
||||||
yield upper_bound_func()
|
|
||||||
|
|
||||||
|
|
||||||
class Token(token.persistence.TokenDriverBase):
|
|
||||||
# Public interface
|
|
||||||
def get_token(self, token_id):
|
|
||||||
if token_id is None:
|
|
||||||
raise exception.TokenNotFound(token_id=token_id)
|
|
||||||
with sql.session_for_read() as session:
|
|
||||||
token_ref = session.query(TokenModel).get(token_id)
|
|
||||||
if not token_ref or not token_ref.valid:
|
|
||||||
raise exception.TokenNotFound(token_id=token_id)
|
|
||||||
return token_ref.to_dict()
|
|
||||||
|
|
||||||
def create_token(self, token_id, data):
|
|
||||||
data_copy = copy.deepcopy(data)
|
|
||||||
if not data_copy.get('expires'):
|
|
||||||
data_copy['expires'] = common.default_expire_time()
|
|
||||||
if not data_copy.get('user_id'):
|
|
||||||
data_copy['user_id'] = data_copy['user']['id']
|
|
||||||
|
|
||||||
token_ref = TokenModel.from_dict(data_copy)
|
|
||||||
token_ref.valid = True
|
|
||||||
with sql.session_for_write() as session:
|
|
||||||
session.add(token_ref)
|
|
||||||
return token_ref.to_dict()
|
|
||||||
|
|
||||||
def delete_token(self, token_id):
|
|
||||||
with sql.session_for_write() as session:
|
|
||||||
token_ref = session.query(TokenModel).get(token_id)
|
|
||||||
if not token_ref or not token_ref.valid:
|
|
||||||
raise exception.TokenNotFound(token_id=token_id)
|
|
||||||
token_ref.valid = False
|
|
||||||
|
|
||||||
def delete_tokens(self, user_id, tenant_id=None, trust_id=None,
|
|
||||||
consumer_id=None):
|
|
||||||
"""Delete all tokens in one session.
|
|
||||||
|
|
||||||
The user_id will be ignored if the trust_id is specified. user_id
|
|
||||||
will always be specified.
|
|
||||||
If using a trust, the token's user_id is set to the trustee's user ID
|
|
||||||
or the trustor's user ID, so will use trust_id to query the tokens.
|
|
||||||
|
|
||||||
"""
|
|
||||||
token_list = []
|
|
||||||
with sql.session_for_write() as session:
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
query = session.query(TokenModel)
|
|
||||||
query = query.filter_by(valid=True)
|
|
||||||
query = query.filter(TokenModel.expires > now)
|
|
||||||
if trust_id:
|
|
||||||
query = query.filter(TokenModel.trust_id == trust_id)
|
|
||||||
else:
|
|
||||||
query = query.filter(TokenModel.user_id == user_id)
|
|
||||||
|
|
||||||
for token_ref in query.all():
|
|
||||||
if tenant_id:
|
|
||||||
token_ref_dict = token_ref.to_dict()
|
|
||||||
if not self._tenant_matches(tenant_id, token_ref_dict):
|
|
||||||
continue
|
|
||||||
if consumer_id:
|
|
||||||
token_ref_dict = token_ref.to_dict()
|
|
||||||
if not self._consumer_matches(consumer_id, token_ref_dict):
|
|
||||||
continue
|
|
||||||
|
|
||||||
token_ref.valid = False
|
|
||||||
token_list.append(token_ref.id)
|
|
||||||
|
|
||||||
return token_list
|
|
||||||
|
|
||||||
def _tenant_matches(self, tenant_id, token_ref_dict):
|
|
||||||
return ((tenant_id is None) or
|
|
||||||
(token_ref_dict.get('tenant') and
|
|
||||||
token_ref_dict['tenant'].get('id') == tenant_id))
|
|
||||||
|
|
||||||
def _consumer_matches(self, consumer_id, ref):
|
|
||||||
if consumer_id is None:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
oauth = ref['token_data']['token'].get('OS-OAUTH1', {})
|
|
||||||
return oauth and oauth['consumer_id'] == consumer_id
|
|
||||||
except KeyError:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def _list_tokens_for_trust(self, trust_id):
|
|
||||||
with sql.session_for_read() as session:
|
|
||||||
tokens = []
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
query = session.query(TokenModel)
|
|
||||||
query = query.filter(TokenModel.expires > now)
|
|
||||||
query = query.filter(TokenModel.trust_id == trust_id)
|
|
||||||
|
|
||||||
token_references = query.filter_by(valid=True)
|
|
||||||
for token_ref in token_references:
|
|
||||||
token_ref_dict = token_ref.to_dict()
|
|
||||||
tokens.append(token_ref_dict['id'])
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
def _list_tokens_for_user(self, user_id, tenant_id=None):
|
|
||||||
with sql.session_for_read() as session:
|
|
||||||
tokens = []
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
query = session.query(TokenModel)
|
|
||||||
query = query.filter(TokenModel.expires > now)
|
|
||||||
query = query.filter(TokenModel.user_id == user_id)
|
|
||||||
|
|
||||||
token_references = query.filter_by(valid=True)
|
|
||||||
for token_ref in token_references:
|
|
||||||
token_ref_dict = token_ref.to_dict()
|
|
||||||
if self._tenant_matches(tenant_id, token_ref_dict):
|
|
||||||
tokens.append(token_ref['id'])
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
def _list_tokens_for_consumer(self, user_id, consumer_id):
|
|
||||||
tokens = []
|
|
||||||
with sql.session_for_write() as session:
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
query = session.query(TokenModel)
|
|
||||||
query = query.filter(TokenModel.expires > now)
|
|
||||||
query = query.filter(TokenModel.user_id == user_id)
|
|
||||||
token_references = query.filter_by(valid=True)
|
|
||||||
|
|
||||||
for token_ref in token_references:
|
|
||||||
token_ref_dict = token_ref.to_dict()
|
|
||||||
if self._consumer_matches(consumer_id, token_ref_dict):
|
|
||||||
tokens.append(token_ref_dict['id'])
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
def _list_tokens(self, user_id, tenant_id=None, trust_id=None,
|
|
||||||
consumer_id=None):
|
|
||||||
if not CONF.token.revoke_by_id:
|
|
||||||
return []
|
|
||||||
if trust_id:
|
|
||||||
return self._list_tokens_for_trust(trust_id)
|
|
||||||
if consumer_id:
|
|
||||||
return self._list_tokens_for_consumer(user_id, consumer_id)
|
|
||||||
else:
|
|
||||||
return self._list_tokens_for_user(user_id, tenant_id)
|
|
||||||
|
|
||||||
def list_revoked_tokens(self):
|
|
||||||
with sql.session_for_read() as session:
|
|
||||||
tokens = []
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
query = session.query(TokenModel.id, TokenModel.expires,
|
|
||||||
TokenModel.extra)
|
|
||||||
query = query.filter(TokenModel.expires > now)
|
|
||||||
token_references = query.filter_by(valid=False)
|
|
||||||
for token_ref in token_references:
|
|
||||||
token_data = token_ref[2]['token_data']
|
|
||||||
if 'access' in token_data:
|
|
||||||
# It's a v2 token.
|
|
||||||
audit_ids = token_data['access']['token']['audit_ids']
|
|
||||||
else:
|
|
||||||
# It's a v3 token.
|
|
||||||
audit_ids = token_data['token']['audit_ids']
|
|
||||||
|
|
||||||
record = {
|
|
||||||
'id': token_ref[0],
|
|
||||||
'expires': token_ref[1],
|
|
||||||
'audit_id': audit_ids[0],
|
|
||||||
}
|
|
||||||
tokens.append(record)
|
|
||||||
return tokens
|
|
||||||
|
|
||||||
def _expiry_range_strategy(self, dialect):
|
|
||||||
"""Choose a token range expiration strategy.
|
|
||||||
|
|
||||||
Based on the DB dialect, select an expiry range callable that is
|
|
||||||
appropriate.
|
|
||||||
"""
|
|
||||||
# DB2 and MySQL can both benefit from a batched strategy. On DB2 the
|
|
||||||
# transaction log can fill up and on MySQL w/Galera, large
|
|
||||||
# transactions can exceed the maximum write set size.
|
|
||||||
if dialect == 'ibm_db_sa':
|
|
||||||
# Limit of 100 is known to not fill a transaction log
|
|
||||||
# of default maximum size while not significantly
|
|
||||||
# impacting the performance of large token purges on
|
|
||||||
# systems where the maximum transaction log size has
|
|
||||||
# been increased beyond the default.
|
|
||||||
return functools.partial(_expiry_range_batched,
|
|
||||||
batch_size=100)
|
|
||||||
elif dialect == 'mysql':
|
|
||||||
# We want somewhat more than 100, since Galera replication delay is
|
|
||||||
# at least RTT*2. This can be a significant amount of time if
|
|
||||||
# doing replication across a WAN.
|
|
||||||
return functools.partial(_expiry_range_batched,
|
|
||||||
batch_size=1000)
|
|
||||||
return _expiry_range_all
|
|
||||||
|
|
||||||
def flush_expired_tokens(self):
|
|
||||||
# The DBAPI itself is in a "never autocommit" mode,
|
|
||||||
# BEGIN is emitted automatically as soon as any work is done,
|
|
||||||
# COMMIT is emitted when SQLAlchemy invokes commit() on the
|
|
||||||
# underlying DBAPI connection. So SQLAlchemy is only simulating
|
|
||||||
# "begin" here in any case, it is in fact automatic by the DBAPI.
|
|
||||||
with sql.session_for_write() as session: # Calls session.begin()
|
|
||||||
dialect = session.bind.dialect.name
|
|
||||||
expiry_range_func = self._expiry_range_strategy(dialect)
|
|
||||||
query = session.query(TokenModel.expires)
|
|
||||||
total_removed = 0
|
|
||||||
upper_bound_func = _expiry_upper_bound_func
|
|
||||||
for expiry_time in expiry_range_func(session, upper_bound_func):
|
|
||||||
delete_query = query.filter(TokenModel.expires <=
|
|
||||||
expiry_time)
|
|
||||||
row_count = delete_query.delete(synchronize_session=False)
|
|
||||||
# Explicitly commit each batch so as to free up
|
|
||||||
# resources early. We do not actually need
|
|
||||||
# transactional semantics here.
|
|
||||||
session.commit() # Emits connection.commit() on DBAPI
|
|
||||||
# Tells SQLAlchemy to "begin", e.g. hold a new connection
|
|
||||||
# open in a transaction
|
|
||||||
session.begin()
|
|
||||||
total_removed += row_count
|
|
||||||
LOG.debug('Removed %d total expired tokens', total_removed)
|
|
||||||
|
|
||||||
# When the "with: " block ends, the final "session.commit()"
|
|
||||||
# is emitted by enginefacade
|
|
||||||
session.flush()
|
|
||||||
LOG.info('Total expired tokens removed: %d', total_removed)
|
|
|
@ -1,294 +0,0 @@
|
||||||
# Copyright 2012 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
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Main entry point into the Token Persistence service."""
|
|
||||||
|
|
||||||
import abc
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from oslo_log import log
|
|
||||||
import six
|
|
||||||
|
|
||||||
from keystone.common import cache
|
|
||||||
from keystone.common import manager
|
|
||||||
import keystone.conf
|
|
||||||
from keystone import exception
|
|
||||||
|
|
||||||
|
|
||||||
CONF = keystone.conf.CONF
|
|
||||||
LOG = log.getLogger(__name__)
|
|
||||||
MEMOIZE = cache.get_memoization_decorator(group='token')
|
|
||||||
REVOCATION_MEMOIZE = cache.get_memoization_decorator(group='token',
|
|
||||||
expiration_group='revoke')
|
|
||||||
|
|
||||||
|
|
||||||
class PersistenceManager(manager.Manager):
|
|
||||||
"""Default pivot point for the Token Persistence backend.
|
|
||||||
|
|
||||||
See :mod:`keystone.common.manager.Manager` for more details on how this
|
|
||||||
dynamically calls the backend.
|
|
||||||
|
|
||||||
"""
|
|
||||||
|
|
||||||
driver_namespace = 'keystone.token.persistence'
|
|
||||||
_provides_api = '_token_persistence_manager'
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
super(PersistenceManager, self).__init__(CONF.token.driver)
|
|
||||||
|
|
||||||
def get_token(self, token_id):
|
|
||||||
return self._get_token(token_id)
|
|
||||||
|
|
||||||
@MEMOIZE
|
|
||||||
def _get_token(self, token_id):
|
|
||||||
# Only ever use the "unique" id in the cache key.
|
|
||||||
return self.driver.get_token(token_id)
|
|
||||||
|
|
||||||
def create_token(self, token_id, data):
|
|
||||||
data_copy = copy.deepcopy(data)
|
|
||||||
data_copy['id'] = token_id
|
|
||||||
ret = self.driver.create_token(token_id, data_copy)
|
|
||||||
if MEMOIZE.should_cache(ret):
|
|
||||||
# NOTE(morganfainberg): when doing a cache set, you must pass the
|
|
||||||
# same arguments through, the same as invalidate (this includes
|
|
||||||
# "self"). First argument is always the value to be cached
|
|
||||||
self._get_token.set(ret, self, token_id)
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def delete_token(self, token_id):
|
|
||||||
if not CONF.token.revoke_by_id:
|
|
||||||
return
|
|
||||||
self.driver.delete_token(token_id)
|
|
||||||
self._invalidate_individual_token_cache(token_id)
|
|
||||||
self.invalidate_revocation_list()
|
|
||||||
|
|
||||||
def delete_tokens(self, user_id, tenant_id=None, trust_id=None,
|
|
||||||
consumer_id=None):
|
|
||||||
if not CONF.token.revoke_by_id:
|
|
||||||
return
|
|
||||||
token_list = self.driver.delete_tokens(user_id, tenant_id, trust_id,
|
|
||||||
consumer_id)
|
|
||||||
for token_id in token_list:
|
|
||||||
self._invalidate_individual_token_cache(token_id)
|
|
||||||
self.invalidate_revocation_list()
|
|
||||||
|
|
||||||
@REVOCATION_MEMOIZE
|
|
||||||
def list_revoked_tokens(self):
|
|
||||||
return self.driver.list_revoked_tokens()
|
|
||||||
|
|
||||||
def invalidate_revocation_list(self):
|
|
||||||
# NOTE(morganfainberg): Note that ``self`` needs to be passed to
|
|
||||||
# invalidate() because of the way the invalidation method works on
|
|
||||||
# determining cache-keys.
|
|
||||||
self.list_revoked_tokens.invalidate(self)
|
|
||||||
|
|
||||||
def delete_tokens_for_domain(self, domain_id):
|
|
||||||
"""Delete all tokens for a given domain.
|
|
||||||
|
|
||||||
It will delete all the project-scoped tokens for the projects
|
|
||||||
that are owned by the given domain, as well as any tokens issued
|
|
||||||
to users that are owned by this domain.
|
|
||||||
|
|
||||||
However, deletion of domain_scoped tokens will still need to be
|
|
||||||
implemented as stated in TODO below.
|
|
||||||
"""
|
|
||||||
if not CONF.token.revoke_by_id:
|
|
||||||
return
|
|
||||||
projects = self.resource_api.list_projects()
|
|
||||||
for project in projects:
|
|
||||||
if project['domain_id'] == domain_id:
|
|
||||||
for user_id in self.assignment_api.list_user_ids_for_project(
|
|
||||||
project['id']):
|
|
||||||
self.delete_tokens_for_user(user_id, project['id'])
|
|
||||||
# TODO(morganfainberg): implement deletion of domain_scoped tokens.
|
|
||||||
|
|
||||||
users = self.identity_api.list_users(domain_id)
|
|
||||||
user_ids = (user['id'] for user in users)
|
|
||||||
self.delete_tokens_for_users(user_ids)
|
|
||||||
|
|
||||||
def delete_tokens_for_user(self, user_id, project_id=None):
|
|
||||||
"""Delete all tokens for a given user or user-project combination.
|
|
||||||
|
|
||||||
This method adds in the extra logic for handling trust-scoped token
|
|
||||||
revocations in a single call instead of needing to explicitly handle
|
|
||||||
trusts in the caller's logic.
|
|
||||||
"""
|
|
||||||
if not CONF.token.revoke_by_id:
|
|
||||||
return
|
|
||||||
self.delete_tokens(user_id, tenant_id=project_id)
|
|
||||||
for trust in self.trust_api.list_trusts_for_trustee(user_id):
|
|
||||||
# Ensure we revoke tokens associated to the trust / project
|
|
||||||
# user_id combination.
|
|
||||||
self.delete_tokens(user_id, trust_id=trust['id'],
|
|
||||||
tenant_id=project_id)
|
|
||||||
for trust in self.trust_api.list_trusts_for_trustor(user_id):
|
|
||||||
# Ensure we revoke tokens associated to the trust / project /
|
|
||||||
# user_id combination where the user_id is the trustor.
|
|
||||||
|
|
||||||
# NOTE(morganfainberg): This revocation is a bit coarse, but it
|
|
||||||
# covers a number of cases such as disabling of the trustor user,
|
|
||||||
# deletion of the trustor user (for any number of reasons). It
|
|
||||||
# might make sense to refine this and be more surgical on the
|
|
||||||
# deletions (e.g. don't revoke tokens for the trusts when the
|
|
||||||
# trustor changes password). For now, to maintain previous
|
|
||||||
# functionality, this will continue to be a bit overzealous on
|
|
||||||
# revocations.
|
|
||||||
self.delete_tokens(trust['trustee_user_id'], trust_id=trust['id'],
|
|
||||||
tenant_id=project_id)
|
|
||||||
|
|
||||||
def delete_tokens_for_users(self, user_ids, project_id=None):
|
|
||||||
"""Delete all tokens for a list of user_ids.
|
|
||||||
|
|
||||||
:param user_ids: list of user identifiers
|
|
||||||
:param project_id: optional project identifier
|
|
||||||
"""
|
|
||||||
if not CONF.token.revoke_by_id:
|
|
||||||
return
|
|
||||||
for user_id in user_ids:
|
|
||||||
self.delete_tokens_for_user(user_id, project_id=project_id)
|
|
||||||
|
|
||||||
def _invalidate_individual_token_cache(self, token_id):
|
|
||||||
# NOTE(morganfainberg): invalidate takes the exact same arguments as
|
|
||||||
# the normal method, this means we need to pass "self" in (which gets
|
|
||||||
# stripped off).
|
|
||||||
self._get_token.invalidate(self, token_id)
|
|
||||||
|
|
||||||
|
|
||||||
@six.add_metaclass(abc.ABCMeta)
|
|
||||||
class TokenDriverBase(object):
|
|
||||||
"""Interface description for a Token driver."""
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def get_token(self, token_id):
|
|
||||||
"""Get a token by id.
|
|
||||||
|
|
||||||
:param token_id: identity of the token
|
|
||||||
:type token_id: string
|
|
||||||
:returns: token_ref
|
|
||||||
:raises keystone.exception.TokenNotFound: If the token doesn't exist.
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise exception.NotImplemented() # pragma: no cover
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def create_token(self, token_id, data):
|
|
||||||
"""Create a token by id and data.
|
|
||||||
|
|
||||||
:param token_id: identity of the token
|
|
||||||
:type token_id: string
|
|
||||||
:param data: dictionary with additional reference information
|
|
||||||
|
|
||||||
::
|
|
||||||
|
|
||||||
{
|
|
||||||
expires=''
|
|
||||||
id=token_id,
|
|
||||||
user=user_ref,
|
|
||||||
tenant=tenant_ref,
|
|
||||||
}
|
|
||||||
|
|
||||||
:type data: dict
|
|
||||||
:returns: token_ref or None.
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise exception.NotImplemented() # pragma: no cover
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_token(self, token_id):
|
|
||||||
"""Delete a token by id.
|
|
||||||
|
|
||||||
:param token_id: identity of the token
|
|
||||||
:type token_id: string
|
|
||||||
:returns: None.
|
|
||||||
:raises keystone.exception.TokenNotFound: If the token doesn't exist.
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise exception.NotImplemented() # pragma: no cover
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def delete_tokens(self, user_id, tenant_id=None, trust_id=None,
|
|
||||||
consumer_id=None):
|
|
||||||
"""Delete tokens by user.
|
|
||||||
|
|
||||||
If the tenant_id is not None, only delete the tokens by user id under
|
|
||||||
the specified tenant.
|
|
||||||
|
|
||||||
If the trust_id is not None, it will be used to query tokens and the
|
|
||||||
user_id will be ignored.
|
|
||||||
|
|
||||||
If the consumer_id is not None, only delete the tokens by consumer id
|
|
||||||
that match the specified consumer id.
|
|
||||||
|
|
||||||
:param user_id: identity of user
|
|
||||||
:type user_id: string
|
|
||||||
:param tenant_id: identity of the tenant
|
|
||||||
:type tenant_id: string
|
|
||||||
:param trust_id: identity of the trust
|
|
||||||
:type trust_id: string
|
|
||||||
:param consumer_id: identity of the consumer
|
|
||||||
:type consumer_id: string
|
|
||||||
:returns: The tokens that have been deleted.
|
|
||||||
:raises keystone.exception.TokenNotFound: If the token doesn't exist.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if not CONF.token.revoke_by_id:
|
|
||||||
return
|
|
||||||
token_list = self._list_tokens(user_id,
|
|
||||||
tenant_id=tenant_id,
|
|
||||||
trust_id=trust_id,
|
|
||||||
consumer_id=consumer_id)
|
|
||||||
|
|
||||||
for token in token_list:
|
|
||||||
try:
|
|
||||||
self.delete_token(token)
|
|
||||||
except exception.NotFound: # nosec
|
|
||||||
# The token is already gone, good.
|
|
||||||
pass
|
|
||||||
return token_list
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def _list_tokens(self, user_id, tenant_id=None, trust_id=None,
|
|
||||||
consumer_id=None):
|
|
||||||
"""Return a list of current token_id's for a user.
|
|
||||||
|
|
||||||
This is effectively a private method only used by the ``delete_tokens``
|
|
||||||
method and should not be called by anything outside of the
|
|
||||||
``token_api`` manager or the token driver itself.
|
|
||||||
|
|
||||||
:param user_id: identity of the user
|
|
||||||
:type user_id: string
|
|
||||||
:param tenant_id: identity of the tenant
|
|
||||||
:type tenant_id: string
|
|
||||||
:param trust_id: identity of the trust
|
|
||||||
:type trust_id: string
|
|
||||||
:param consumer_id: identity of the consumer
|
|
||||||
:type consumer_id: string
|
|
||||||
:returns: list of token_id's
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise exception.NotImplemented() # pragma: no cover
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def list_revoked_tokens(self):
|
|
||||||
"""Return a list of all revoked tokens.
|
|
||||||
|
|
||||||
:returns: list of token_id's
|
|
||||||
|
|
||||||
"""
|
|
||||||
raise exception.NotImplemented() # pragma: no cover
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def flush_expired_tokens(self):
|
|
||||||
"""Archive or delete tokens that have expired."""
|
|
||||||
raise exception.NotImplemented() # pragma: no cover
|
|
|
@ -267,67 +267,73 @@ class Manager(manager.Manager):
|
||||||
self.invalidate_individual_token_cache(token_id)
|
self.invalidate_individual_token_cache(token_id)
|
||||||
|
|
||||||
def list_revoked_tokens(self):
|
def list_revoked_tokens(self):
|
||||||
return self._persistence.list_revoked_tokens()
|
# FIXME(lbragstad): In the future, the token providers are going to be
|
||||||
|
# responsible for handling persistence if they require it (e.g. token
|
||||||
|
# providers not doing some sort of authenticated encryption strategy).
|
||||||
|
# When that happens, we could still expose this API by checking an
|
||||||
|
# interface on the provider can calling it if available. For now, this
|
||||||
|
# will return a valid response, but it will just be an empty list. See
|
||||||
|
# http://paste.openstack.org/raw/670196/ for and example using
|
||||||
|
# keystoneclient.common.cms to verify the response.
|
||||||
|
return []
|
||||||
|
|
||||||
|
# FIXME(lbragstad): This callback doesn't have anything to do with
|
||||||
|
# persistence anymore now that the sql token driver has been removed. We
|
||||||
|
# should rename this to be more accurate since it's only used to invalidate
|
||||||
|
# the token cache region.
|
||||||
def _trust_deleted_event_callback(self, service, resource_type, operation,
|
def _trust_deleted_event_callback(self, service, resource_type, operation,
|
||||||
payload):
|
payload):
|
||||||
if CONF.token.revoke_by_id:
|
|
||||||
trust_id = payload['resource_info']
|
|
||||||
trust = PROVIDERS.trust_api.get_trust(trust_id, deleted=True)
|
|
||||||
self._persistence.delete_tokens(user_id=trust['trustor_user_id'],
|
|
||||||
trust_id=trust_id)
|
|
||||||
if CONF.token.cache_on_issue:
|
if CONF.token.cache_on_issue:
|
||||||
# NOTE(amakarov): preserving behavior
|
# NOTE(amakarov): preserving behavior
|
||||||
TOKENS_REGION.invalidate()
|
TOKENS_REGION.invalidate()
|
||||||
|
|
||||||
|
# FIXME(lbragstad): This callback doesn't have anything to do with
|
||||||
|
# persistence anymore now that the sql token driver has been removed. We
|
||||||
|
# should rename this to be more accurate since it's only used to invalidate
|
||||||
|
# the token cache region.
|
||||||
def _delete_user_tokens_callback(self, service, resource_type, operation,
|
def _delete_user_tokens_callback(self, service, resource_type, operation,
|
||||||
payload):
|
payload):
|
||||||
if CONF.token.revoke_by_id:
|
|
||||||
user_id = payload['resource_info']
|
|
||||||
self._persistence.delete_tokens_for_user(user_id)
|
|
||||||
if CONF.token.cache_on_issue:
|
if CONF.token.cache_on_issue:
|
||||||
# NOTE(amakarov): preserving behavior
|
# NOTE(amakarov): preserving behavior
|
||||||
TOKENS_REGION.invalidate()
|
TOKENS_REGION.invalidate()
|
||||||
|
|
||||||
|
# FIXME(lbragstad): This callback doesn't have anything to do with
|
||||||
|
# persistence anymore now that the sql token driver has been removed. We
|
||||||
|
# should rename this to be more accurate since it's only used to invalidate
|
||||||
|
# the token cache region.
|
||||||
def _delete_domain_tokens_callback(self, service, resource_type,
|
def _delete_domain_tokens_callback(self, service, resource_type,
|
||||||
operation, payload):
|
operation, payload):
|
||||||
if CONF.token.revoke_by_id:
|
|
||||||
domain_id = payload['resource_info']
|
|
||||||
self._persistence.delete_tokens_for_domain(domain_id=domain_id)
|
|
||||||
if CONF.token.cache_on_issue:
|
if CONF.token.cache_on_issue:
|
||||||
# NOTE(amakarov): preserving behavior
|
# NOTE(amakarov): preserving behavior
|
||||||
TOKENS_REGION.invalidate()
|
TOKENS_REGION.invalidate()
|
||||||
|
|
||||||
|
# FIXME(lbragstad): This callback doesn't have anything to do with
|
||||||
|
# persistence anymore now that the sql token driver has been removed. We
|
||||||
|
# should rename this to be more accurate since it's only used to invalidate
|
||||||
|
# the token cache region.
|
||||||
def _delete_user_project_tokens_callback(self, service, resource_type,
|
def _delete_user_project_tokens_callback(self, service, resource_type,
|
||||||
operation, payload):
|
operation, payload):
|
||||||
if CONF.token.revoke_by_id:
|
|
||||||
user_id = payload['resource_info']['user_id']
|
|
||||||
project_id = payload['resource_info']['project_id']
|
|
||||||
self._persistence.delete_tokens_for_user(user_id=user_id,
|
|
||||||
project_id=project_id)
|
|
||||||
if CONF.token.cache_on_issue:
|
if CONF.token.cache_on_issue:
|
||||||
# NOTE(amakarov): preserving behavior
|
# NOTE(amakarov): preserving behavior
|
||||||
TOKENS_REGION.invalidate()
|
TOKENS_REGION.invalidate()
|
||||||
|
|
||||||
|
# FIXME(lbragstad): This callback doesn't have anything to do with
|
||||||
|
# persistence anymore now that the sql token driver has been removed. We
|
||||||
|
# should rename this to be more accurate since it's only used to invalidate
|
||||||
|
# the token cache region.
|
||||||
def _delete_project_tokens_callback(self, service, resource_type,
|
def _delete_project_tokens_callback(self, service, resource_type,
|
||||||
operation, payload):
|
operation, payload):
|
||||||
if CONF.token.revoke_by_id:
|
|
||||||
project_id = payload['resource_info']
|
|
||||||
self._persistence.delete_tokens_for_users(
|
|
||||||
PROVIDERS.assignment_api.list_user_ids_for_project(project_id),
|
|
||||||
project_id=project_id)
|
|
||||||
if CONF.token.cache_on_issue:
|
if CONF.token.cache_on_issue:
|
||||||
# NOTE(amakarov): preserving behavior
|
# NOTE(amakarov): preserving behavior
|
||||||
TOKENS_REGION.invalidate()
|
TOKENS_REGION.invalidate()
|
||||||
|
|
||||||
|
# FIXME(lbragstad): This callback doesn't have anything to do with
|
||||||
|
# persistence anymore now that the sql token driver has been removed. We
|
||||||
|
# should rename this to be more accurate since it's only used to invalidate
|
||||||
|
# the token cache region.
|
||||||
def _delete_user_oauth_consumer_tokens_callback(self, service,
|
def _delete_user_oauth_consumer_tokens_callback(self, service,
|
||||||
resource_type, operation,
|
resource_type, operation,
|
||||||
payload):
|
payload):
|
||||||
if CONF.token.revoke_by_id:
|
|
||||||
user_id = payload['resource_info']['user_id']
|
|
||||||
consumer_id = payload['resource_info']['consumer_id']
|
|
||||||
self._persistence.delete_tokens(user_id=user_id,
|
|
||||||
consumer_id=consumer_id)
|
|
||||||
if CONF.token.cache_on_issue:
|
if CONF.token.cache_on_issue:
|
||||||
# NOTE(amakarov): preserving behavior
|
# NOTE(amakarov): preserving behavior
|
||||||
TOKENS_REGION.invalidate()
|
TOKENS_REGION.invalidate()
|
||||||
|
|
|
@ -1,49 +0,0 @@
|
||||||
# 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
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""Keystone UUID Token Provider."""
|
|
||||||
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
from oslo_log import versionutils
|
|
||||||
|
|
||||||
import uuid
|
|
||||||
|
|
||||||
from keystone.token.providers import common
|
|
||||||
|
|
||||||
|
|
||||||
class Provider(common.BaseProvider):
|
|
||||||
|
|
||||||
@versionutils.deprecated(
|
|
||||||
as_of=versionutils.deprecated.PIKE,
|
|
||||||
what='UUID Token Provider "[token] provider=uuid"',
|
|
||||||
in_favor_of='Fernet token Provider "[token] provider=fernet"',
|
|
||||||
remove_in=+2)
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
super(Provider, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
def _get_token_id(self, token_data):
|
|
||||||
return uuid.uuid4().hex
|
|
||||||
|
|
||||||
@property
|
|
||||||
def _supports_bind_authentication(self):
|
|
||||||
"""Return if the token provider supports bind authentication methods.
|
|
||||||
|
|
||||||
:returns: True
|
|
||||||
"""
|
|
||||||
return True
|
|
||||||
|
|
||||||
def needs_persistence(self):
|
|
||||||
"""Should the token be written to a backend."""
|
|
||||||
return True
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
other:
|
||||||
|
- |
|
||||||
|
[`blueprint removed-as-of-rocky <https://blueprints.launchpad.net/keystone/+spec/removed-as-of-rocky>`_]
|
||||||
|
The ``sql`` token driver and ``uuid`` token providers have been removed
|
||||||
|
in favor of the ``fernet`` token provider.
|
|
@ -152,12 +152,8 @@ keystone.resource.domain_config =
|
||||||
keystone.role =
|
keystone.role =
|
||||||
sql = keystone.assignment.role_backends.sql:Role
|
sql = keystone.assignment.role_backends.sql:Role
|
||||||
|
|
||||||
keystone.token.persistence =
|
|
||||||
sql = keystone.token.persistence.backends.sql:Token
|
|
||||||
|
|
||||||
keystone.token.provider =
|
keystone.token.provider =
|
||||||
fernet = keystone.token.providers.fernet:Provider
|
fernet = keystone.token.providers.fernet:Provider
|
||||||
uuid = keystone.token.providers.uuid:Provider
|
|
||||||
|
|
||||||
keystone.trust =
|
keystone.trust =
|
||||||
sql = keystone.trust.backends.sql:Trust
|
sql = keystone.trust.backends.sql:Trust
|
||||||
|
|
Loading…
Reference in New Issue