summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMorgan Fainberg <morgan.fainberg@gmail.com>2018-10-26 10:32:28 -0700
committerMorgan Fainberg <morgan.fainberg@gmail.com>2018-10-30 19:36:51 +0000
commit7e1b53625990bb08425645cb92f36e16bd67db7f (patch)
tree4090e7b53dfa9b3c7307608ba8adbc93031c0466
parentfc51082ef43e316bbfa65c16dd6483af1f2092e7 (diff)
Stop supporting revocation list
With keystone's move to eliminating pki, pkiz, and uuid tokens the revocation list is no longer generated. Keystonemiddleware no longer needs to attempt to retrieve it and reference it. Change-Id: Ief3bf1941e62f9136dbed11877bca81c4102041b closes-bug: #1361743 partial-bug: #1649735 partial-bug: #1736985
Notes
Notes (review): Code-Review+2: Lance Bragstad <lbragstad@gmail.com> Code-Review+1: Matthew Edmonds <edmondsw@us.ibm.com> Code-Review+2: wangxiyuan <wangxiyuan@huawei.com> Workflow+1: wangxiyuan <wangxiyuan@huawei.com> Verified+2: Zuul Submitted-by: Zuul Submitted-at: Wed, 07 Nov 2018 10:09:35 +0000 Reviewed-on: https://review.openstack.org/613651 Project: openstack/keystonemiddleware Branch: refs/heads/master
-rw-r--r--examples/pki/gen_cmsz.py38
-rw-r--r--keystonemiddleware/auth_token/__init__.py38
-rw-r--r--keystonemiddleware/auth_token/_identity.py22
-rw-r--r--keystonemiddleware/auth_token/_opts.py18
-rw-r--r--keystonemiddleware/auth_token/_revocations.py128
-rw-r--r--keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py326
-rw-r--r--keystonemiddleware/tests/unit/auth_token/test_revocations.py104
-rw-r--r--keystonemiddleware/tests/unit/client_fixtures.py57
-rw-r--r--keystonemiddleware/tests/unit/test_opts.py4
-rw-r--r--releasenotes/notes/bug-1649735-3c68f3243e474775.yaml8
10 files changed, 9 insertions, 734 deletions
diff --git a/examples/pki/gen_cmsz.py b/examples/pki/gen_cmsz.py
index 6840c08..9a8834e 100644
--- a/examples/pki/gen_cmsz.py
+++ b/examples/pki/gen_cmsz.py
@@ -16,7 +16,6 @@ import json
16import os 16import os
17 17
18from keystoneclient.common import cms 18from keystoneclient.common import cms
19from keystoneclient import utils
20 19
21CURRENT_DIR = os.path.abspath(os.path.dirname(__file__)) 20CURRENT_DIR = os.path.abspath(os.path.dirname(__file__))
22 21
@@ -25,41 +24,6 @@ def make_filename(*args):
25 return os.path.join(CURRENT_DIR, *args) 24 return os.path.join(CURRENT_DIR, *args)
26 25
27 26
28def generate_revocation_list():
29 REVOKED_TOKENS = ['auth_token_revoked', 'auth_v3_token_revoked']
30 revoked_list = []
31 for token in REVOKED_TOKENS:
32 with open(make_filename('cms', '%s.pkiz' % name), 'r') as f:
33 token_data = f.read()
34 id = utils.hash_signed_token(token_data.encode('utf-8'))
35 revoked_list.append({
36 'id': id,
37 "expires": "2112-08-14T17:58:48Z"
38 })
39 with open(make_filename('cms', '%s.pem' % name), 'r') as f:
40 pem_data = f.read()
41 token_data = cms.cms_to_token(pem_data).encode('utf-8')
42 id = utils.hash_signed_token(token_data)
43 revoked_list.append({
44 'id': id,
45 "expires": "2112-08-14T17:58:48Z"
46 })
47 revoked_json = json.dumps({"revoked": revoked_list})
48 with open(make_filename('cms', 'revocation_list.json'), 'w') as f:
49 f.write(revoked_json)
50 encoded = cms.pkiz_sign(revoked_json,
51 SIGNING_CERT_FILE_NAME,
52 SIGNING_KEY_FILE_NAME)
53 with open(make_filename('cms', 'revocation_list.pkiz'), 'w') as f:
54 f.write(encoded)
55
56 encoded = cms.cms_sign_data(revoked_json,
57 SIGNING_CERT_FILE_NAME,
58 SIGNING_KEY_FILE_NAME)
59 with open(make_filename('cms', 'revocation_list.pem'), 'w') as f:
60 f.write(encoded)
61
62
63CA_CERT_FILE_NAME = make_filename('certs', 'cacert.pem') 27CA_CERT_FILE_NAME = make_filename('certs', 'cacert.pem')
64SIGNING_CERT_FILE_NAME = make_filename('certs', 'signing_cert.pem') 28SIGNING_CERT_FILE_NAME = make_filename('certs', 'signing_cert.pem')
65SIGNING_KEY_FILE_NAME = make_filename('private', 'signing_key.pem') 29SIGNING_KEY_FILE_NAME = make_filename('private', 'signing_key.pem')
@@ -113,5 +77,3 @@ for name in EXAMPLE_TOKENS:
113 77
114 with open(pkiz_file, 'w') as f: 78 with open(pkiz_file, 'w') as f:
115 f.write(encoded) 79 f.write(encoded)
116
117 generate_revocation_list()
diff --git a/keystonemiddleware/auth_token/__init__.py b/keystonemiddleware/auth_token/__init__.py
index 003f14e..e140ab8 100644
--- a/keystonemiddleware/auth_token/__init__.py
+++ b/keystonemiddleware/auth_token/__init__.py
@@ -219,7 +219,6 @@ object is stored.
219 219
220import binascii 220import binascii
221import copy 221import copy
222import datetime
223 222
224from keystoneauth1 import access 223from keystoneauth1 import access
225from keystoneauth1 import adapter 224from keystoneauth1 import adapter
@@ -243,7 +242,6 @@ from keystonemiddleware.auth_token import _exceptions as ksm_exceptions
243from keystonemiddleware.auth_token import _identity 242from keystonemiddleware.auth_token import _identity
244from keystonemiddleware.auth_token import _opts 243from keystonemiddleware.auth_token import _opts
245from keystonemiddleware.auth_token import _request 244from keystonemiddleware.auth_token import _request
246from keystonemiddleware.auth_token import _revocations
247from keystonemiddleware.auth_token import _signing_dir 245from keystonemiddleware.auth_token import _signing_dir
248from keystonemiddleware.auth_token import _user_plugin 246from keystonemiddleware.auth_token import _user_plugin
249from keystonemiddleware.i18n import _ 247from keystonemiddleware.i18n import _
@@ -597,17 +595,6 @@ class AuthProtocol(BaseAuthProtocol):
597 595
598 self._token_cache = self._token_cache_factory() 596 self._token_cache = self._token_cache_factory()
599 597
600 revocation_cache_timeout = datetime.timedelta(
601 seconds=self._conf.get('revocation_cache_time'))
602 self._revocations = _revocations.Revocations(revocation_cache_timeout,
603 self._signing_directory,
604 self._identity_server,
605 self._cms_verify,
606 self.log)
607
608 self._check_revocations_for_cached = self._conf.get(
609 'check_revocations_for_cached')
610
611 def process_request(self, request): 598 def process_request(self, request):
612 """Process request. 599 """Process request.
613 600
@@ -690,9 +677,6 @@ class AuthProtocol(BaseAuthProtocol):
690 def _token_hashes(self, token): 677 def _token_hashes(self, token):
691 """Generate a list of hashes that the current token may be cached as. 678 """Generate a list of hashes that the current token may be cached as.
692 679
693 With PKI tokens we have multiple hashing algorithms that we test with
694 revocations. This generates that whole list.
695
696 The first element of this list is the preferred algorithm and is what 680 The first element of this list is the preferred algorithm and is what
697 new cache values should be saved as. 681 new cache values should be saved as.
698 682
@@ -740,11 +724,6 @@ class AuthProtocol(BaseAuthProtocol):
740 self.log.debug('Cached token is marked unauthorized') 724 self.log.debug('Cached token is marked unauthorized')
741 raise ksm_exceptions.InvalidToken() 725 raise ksm_exceptions.InvalidToken()
742 726
743 if self._check_revocations_for_cached:
744 # A token might have been revoked, regardless of initial
745 # mechanism used to validate it, and needs to be checked.
746 self._revocations.check(token_hashes)
747
748 # NOTE(jamielennox): Cached values used to be stored as a tuple 727 # NOTE(jamielennox): Cached values used to be stored as a tuple
749 # of data and expiry time. They no longer are but we have to 728 # of data and expiry time. They no longer are but we have to
750 # allow some time to transition the old format so if it's a 729 # allow some time to transition the old format so if it's a
@@ -765,7 +744,6 @@ class AuthProtocol(BaseAuthProtocol):
765 except (ksa_exceptions.ConnectFailure, 744 except (ksa_exceptions.ConnectFailure,
766 ksa_exceptions.DiscoveryFailure, 745 ksa_exceptions.DiscoveryFailure,
767 ksa_exceptions.RequestTimeout, 746 ksa_exceptions.RequestTimeout,
768 ksm_exceptions.RevocationListError,
769 ksm_exceptions.ServiceError) as e: 747 ksm_exceptions.ServiceError) as e:
770 self.log.critical('Unable to validate token: %s', e) 748 self.log.critical('Unable to validate token: %s', e)
771 if self._delay_auth_decision: 749 if self._delay_auth_decision:
@@ -797,14 +775,10 @@ class AuthProtocol(BaseAuthProtocol):
797 return 775 return
798 776
799 try: 777 try:
800 self._revocations.check(token_hashes)
801 verified = self._cms_verify(token_data, inform) 778 verified = self._cms_verify(token_data, inform)
802 except ksc_exceptions.CertificateConfigError: 779 except ksc_exceptions.CertificateConfigError:
803 self.log.warning('Fetch certificate config failed, ' 780 self.log.warning('Fetch certificate config failed, '
804 'fallback to online validation.') 781 'fallback to online validation.')
805 except ksm_exceptions.RevocationListError:
806 self.log.warning('Fetch revocation list failed, '
807 'fallback to online validation.')
808 else: 782 else:
809 self.log.warning('auth_token middleware received a PKI/Z token. ' 783 self.log.warning('auth_token middleware received a PKI/Z token. '
810 'This form of token is deprecated and has been ' 784 'This form of token is deprecated and has been '
@@ -815,17 +789,6 @@ class AuthProtocol(BaseAuthProtocol):
815 789
816 data = jsonutils.loads(verified) 790 data = jsonutils.loads(verified)
817 791
818 audit_ids = None
819 if 'access' in data:
820 # It's a v2 token.
821 audit_ids = data['access']['token'].get('audit_ids')
822 else:
823 # It's a v3 token
824 audit_ids = data['token'].get('audit_ids')
825
826 if audit_ids:
827 self._revocations.check_by_audit_id(audit_ids)
828
829 return data 792 return data
830 793
831 def _validate_token(self, auth_ref, **kwargs): 794 def _validate_token(self, auth_ref, **kwargs):
@@ -1005,4 +968,3 @@ def app_factory(global_conf, **local_conf):
1005InvalidToken = ksm_exceptions.InvalidToken 968InvalidToken = ksm_exceptions.InvalidToken
1006ServiceError = ksm_exceptions.ServiceError 969ServiceError = ksm_exceptions.ServiceError
1007ConfigurationError = ksm_exceptions.ConfigurationError 970ConfigurationError = ksm_exceptions.ConfigurationError
1008RevocationListError = ksm_exceptions.RevocationListError
diff --git a/keystonemiddleware/auth_token/_identity.py b/keystonemiddleware/auth_token/_identity.py
index c07eb45..1e37070 100644
--- a/keystonemiddleware/auth_token/_identity.py
+++ b/keystonemiddleware/auth_token/_identity.py
@@ -61,9 +61,6 @@ class _RequestStrategy(object):
61 def _fetch_ca_cert(self): 61 def _fetch_ca_cert(self):
62 pass 62 pass
63 63
64 def fetch_revocation_list(self):
65 pass
66
67 64
68class _V2RequestStrategy(_RequestStrategy): 65class _V2RequestStrategy(_RequestStrategy):
69 66
@@ -89,9 +86,6 @@ class _V2RequestStrategy(_RequestStrategy):
89 def _fetch_ca_cert(self): 86 def _fetch_ca_cert(self):
90 return self._client.certificates.get_ca_certificate() 87 return self._client.certificates.get_ca_certificate()
91 88
92 def fetch_revocation_list(self):
93 return self._client.tokens.get_revoked()
94
95 89
96class _V3RequestStrategy(_RequestStrategy): 90class _V3RequestStrategy(_RequestStrategy):
97 91
@@ -119,9 +113,6 @@ class _V3RequestStrategy(_RequestStrategy):
119 def _fetch_ca_cert(self): 113 def _fetch_ca_cert(self):
120 return self._client.simple_cert.get_ca_certificates() 114 return self._client.simple_cert.get_ca_certificates()
121 115
122 def fetch_revocation_list(self):
123 return self._client.tokens.get_revoked()
124
125 116
126_REQUEST_STRATEGIES = [_V3RequestStrategy, _V2RequestStrategy] 117_REQUEST_STRATEGIES = [_V3RequestStrategy, _V2RequestStrategy]
127 118
@@ -130,7 +121,7 @@ class IdentityServer(object):
130 """Base class for operations on the Identity API server. 121 """Base class for operations on the Identity API server.
131 122
132 The auth_token middleware needs to communicate with the Identity API server 123 The auth_token middleware needs to communicate with the Identity API server
133 to validate UUID tokens, fetch the revocation list, signing certificates, 124 to validate UUID tokens, signing certificates,
134 etc. This class encapsulates the data and methods to perform these 125 etc. This class encapsulates the data and methods to perform these
135 operations. 126 operations.
136 127
@@ -243,17 +234,6 @@ class IdentityServer(object):
243 else: 234 else:
244 return auth_ref 235 return auth_ref
245 236
246 def fetch_revocation_list(self):
247 try:
248 data = self._request_strategy.fetch_revocation_list()
249 except ksa_exceptions.HttpError as e:
250 msg = _('Failed to fetch token revocation list: %d')
251 raise ksm_exceptions.RevocationListError(msg % e.http_status)
252 if 'signed' not in data:
253 msg = _('Revocation list improperly formatted.')
254 raise ksm_exceptions.RevocationListError(msg)
255 return data['signed']
256
257 def fetch_signing_cert(self): 237 def fetch_signing_cert(self):
258 return self._request_strategy.fetch_signing_cert() 238 return self._request_strategy.fetch_signing_cert()
259 239
diff --git a/keystonemiddleware/auth_token/_opts.py b/keystonemiddleware/auth_token/_opts.py
index 7e68795..941d0ad 100644
--- a/keystonemiddleware/auth_token/_opts.py
+++ b/keystonemiddleware/auth_token/_opts.py
@@ -113,17 +113,6 @@ _OPTS = [
113 ' tokens, the middleware caches previously-seen tokens for a' 113 ' tokens, the middleware caches previously-seen tokens for a'
114 ' configurable duration (in seconds). Set to -1 to disable' 114 ' configurable duration (in seconds). Set to -1 to disable'
115 ' caching completely.'), 115 ' caching completely.'),
116 cfg.IntOpt('revocation_cache_time',
117 default=10,
118 deprecated_for_removal=True,
119 deprecated_reason='PKI token format is no longer supported.',
120 deprecated_since='Ocata',
121 help='Determines the frequency at which the list of revoked'
122 ' tokens is retrieved from the Identity service (in seconds). A'
123 ' high number of revocation events combined with a low cache'
124 ' duration may significantly reduce performance. Only valid'
125 ' for PKI tokens. This option has been deprecated in the Ocata'
126 ' release and will be removed in the P release.'),
127 cfg.StrOpt('memcache_security_strategy', 116 cfg.StrOpt('memcache_security_strategy',
128 default='None', 117 default='None',
129 choices=('None', 'MAC', 'ENCRYPT'), 118 choices=('None', 'MAC', 'ENCRYPT'),
@@ -179,13 +168,6 @@ _OPTS = [
179 ' unknown the token will be rejected. "required" any form of' 168 ' unknown the token will be rejected. "required" any form of'
180 ' token binding is needed to be allowed. Finally the name of a' 169 ' token binding is needed to be allowed. Finally the name of a'
181 ' binding method that must be present in tokens.'), 170 ' binding method that must be present in tokens.'),
182 cfg.BoolOpt('check_revocations_for_cached', default=False,
183 deprecated_for_removal=True,
184 deprecated_reason='PKI token format is no longer supported.',
185 deprecated_since='Ocata',
186 help='If true, the revocation list will be checked for cached'
187 ' tokens. This requires that PKI tokens are configured on the'
188 ' identity server.'),
189 cfg.ListOpt('hash_algorithms', default=['md5'], 171 cfg.ListOpt('hash_algorithms', default=['md5'],
190 deprecated_for_removal=True, 172 deprecated_for_removal=True,
191 deprecated_reason='PKI token format is no longer supported.', 173 deprecated_reason='PKI token format is no longer supported.',
diff --git a/keystonemiddleware/auth_token/_revocations.py b/keystonemiddleware/auth_token/_revocations.py
deleted file mode 100644
index b6d0fa3..0000000
--- a/keystonemiddleware/auth_token/_revocations.py
+++ /dev/null
@@ -1,128 +0,0 @@
1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5# http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13import datetime
14import os
15
16from oslo_log import log as logging
17from oslo_serialization import jsonutils
18from oslo_utils import timeutils
19
20from keystonemiddleware.auth_token import _exceptions as exc
21from keystonemiddleware.i18n import _
22
23_LOG = logging.getLogger(__name__)
24
25
26class Revocations(object):
27 _FILE_NAME = 'revoked.pem'
28
29 def __init__(self, timeout, signing_directory, identity_server,
30 cms_verify, log=_LOG):
31 self._cache_timeout = timeout
32 self._signing_directory = signing_directory
33 self._identity_server = identity_server
34 self._cms_verify = cms_verify
35 self._log = log
36
37 self._fetched_time_prop = None
38 self._list_prop = None
39
40 @property
41 def _fetched_time(self):
42 if not self._fetched_time_prop:
43 # If the fetched list has been written to disk, use its
44 # modification time.
45 file_path = self._signing_directory.calc_path(self._FILE_NAME)
46 if os.path.exists(file_path):
47 mtime = os.path.getmtime(file_path)
48 fetched_time = datetime.datetime.utcfromtimestamp(mtime)
49 # Otherwise the list will need to be fetched.
50 else:
51 fetched_time = datetime.datetime.min
52 self._fetched_time_prop = fetched_time
53 return self._fetched_time_prop
54
55 @_fetched_time.setter
56 def _fetched_time(self, value):
57 self._fetched_time_prop = value
58
59 def _fetch(self):
60 revocation_list_data = self._identity_server.fetch_revocation_list()
61 return self._cms_verify(revocation_list_data)
62
63 @property
64 def _list(self):
65 timeout = self._fetched_time + self._cache_timeout
66 list_is_current = timeutils.utcnow() < timeout
67
68 if list_is_current:
69 # Load the list from disk if required
70 if not self._list_prop:
71 self._list_prop = jsonutils.loads(
72 self._signing_directory.read_file(self._FILE_NAME))
73 else:
74 self._list = self._fetch()
75 return self._list_prop
76
77 @_list.setter
78 def _list(self, value):
79 """Save a revocation list to memory and to disk.
80
81 :param value: A json-encoded revocation list
82
83 """
84 self._list_prop = jsonutils.loads(value)
85 self._fetched_time = timeutils.utcnow()
86 self._signing_directory.write_file(self._FILE_NAME, value)
87
88 def _is_revoked(self, token_id):
89 """Indicate whether the token_id appears in the revocation list."""
90 revoked_tokens = self._list.get('revoked', None)
91 if not revoked_tokens:
92 return False
93
94 revoked_ids = (x['id'] for x in revoked_tokens)
95 return token_id in revoked_ids
96
97 def _any_revoked(self, token_ids):
98 for token_id in token_ids:
99 if self._is_revoked(token_id):
100 return True
101 return False
102
103 def check(self, token_ids):
104 if self._any_revoked(token_ids):
105 self._log.debug('Token is marked as having been revoked')
106 raise exc.InvalidToken(_('Token has been revoked'))
107
108 def check_by_audit_id(self, audit_ids):
109 """Check whether the audit_id appears in the revocation list.
110
111 :raises keystonemiddleware.auth_token._exceptions.InvalidToken:
112 if the audit ID(s) appear in the revocation list.
113
114 """
115 revoked_tokens = self._list.get('revoked', None)
116 if not revoked_tokens:
117 # There's no revoked tokens, so nothing to do.
118 return
119
120 # The audit_id may not be present in the revocation events because
121 # earlier versions of the identity server didn't provide them.
122 revoked_ids = set(
123 x['audit_id'] for x in revoked_tokens if 'audit_id' in x)
124 for audit_id in audit_ids:
125 if audit_id in revoked_ids:
126 self._log.debug(
127 'Token is marked as having been revoked by audit id')
128 raise exc.InvalidToken(_('Token has been revoked'))
diff --git a/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py b/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py
index 9051d71..c62b3cc 100644
--- a/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py
+++ b/keystonemiddleware/tests/unit/auth_token/test_auth_token_middleware.py
@@ -35,7 +35,6 @@ from oslo_utils import timeutils
35import pbr.version 35import pbr.version
36import six 36import six
37import testresources 37import testresources
38import testtools
39from testtools import matchers 38from testtools import matchers
40import webob 39import webob
41import webob.dec 40import webob.dec
@@ -44,7 +43,6 @@ from keystonemiddleware import auth_token
44from keystonemiddleware.auth_token import _base 43from keystonemiddleware.auth_token import _base
45from keystonemiddleware.auth_token import _cache 44from keystonemiddleware.auth_token import _cache
46from keystonemiddleware.auth_token import _exceptions as ksm_exceptions 45from keystonemiddleware.auth_token import _exceptions as ksm_exceptions
47from keystonemiddleware.auth_token import _revocations
48from keystonemiddleware.tests.unit.auth_token import base 46from keystonemiddleware.tests.unit.auth_token import base
49from keystonemiddleware.tests.unit import client_fixtures 47from keystonemiddleware.tests.unit import client_fixtures
50 48
@@ -101,13 +99,6 @@ ERROR_TOKEN = '7ae290c2a06244c4b41692eb4e9225f2'
101TIMEOUT_TOKEN = '4ed1c5e53beee59458adcf8261a8cae2' 99TIMEOUT_TOKEN = '4ed1c5e53beee59458adcf8261a8cae2'
102 100
103 101
104def cleanup_revoked_file(filename):
105 try:
106 os.remove(filename)
107 except OSError:
108 pass
109
110
111def strtime(at=None): 102def strtime(at=None):
112 at = at or timeutils.utcnow() 103 at = at or timeutils.utcnow()
113 return at.strftime(timeutils.PERFECT_TIME_FORMAT) 104 return at.strftime(timeutils.PERFECT_TIME_FORMAT)
@@ -337,9 +328,6 @@ class BaseAuthTokenMiddlewareTest(base.BaseAuthTokenTestCase):
337 self.middleware = auth_token.AuthProtocol( 328 self.middleware = auth_token.AuthProtocol(
338 self.fake_app(self.expected_env), self.conf) 329 self.fake_app(self.expected_env), self.conf)
339 330
340 self.middleware._revocations._list = jsonutils.dumps(
341 {"revoked": [], "extra": "success"})
342
343 def update_expected_env(self, expected_env={}): 331 def update_expected_env(self, expected_env={}):
344 self.middleware._app.expected_env.update(expected_env) 332 self.middleware._app.expected_env.update(expected_env)
345 333
@@ -476,27 +464,14 @@ class GeneralAuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
476 self.assertThat(hashed_short_string_key, 464 self.assertThat(hashed_short_string_key,
477 matchers.HasLength(len(hashed_long_string_key))) 465 matchers.HasLength(len(hashed_long_string_key)))
478 466
479 def test_config_revocation_cache_timeout(self):
480 conf = {
481 'revocation_cache_time': '24',
482 'www_authenticate_uri': 'https://keystone.example.com:1234',
483 'admin_user': uuid.uuid4().hex
484 }
485 middleware = auth_token.AuthProtocol(self.fake_app, conf)
486 self.assertEqual(middleware._revocations._cache_timeout,
487 datetime.timedelta(seconds=24))
488
489 def test_conf_values_type_convert(self): 467 def test_conf_values_type_convert(self):
490 conf = { 468 conf = {
491 'revocation_cache_time': '24',
492 'identity_uri': 'https://keystone.example.com:1234', 469 'identity_uri': 'https://keystone.example.com:1234',
493 'include_service_catalog': '0', 470 'include_service_catalog': '0',
494 'nonexsit_option': '0', 471 'nonexsit_option': '0',
495 } 472 }
496 473
497 middleware = auth_token.AuthProtocol(self.fake_app, conf) 474 middleware = auth_token.AuthProtocol(self.fake_app, conf)
498 self.assertEqual(datetime.timedelta(seconds=24),
499 middleware._revocations._cache_timeout)
500 self.assertFalse(middleware._include_service_catalog) 475 self.assertFalse(middleware._include_service_catalog)
501 self.assertEqual('0', middleware._conf.get('nonexsit_option')) 476 self.assertEqual('0', middleware._conf.get('nonexsit_option'))
502 477
@@ -643,42 +618,6 @@ class CommonAuthTokenMiddlewareTest(object):
643 self.assert_valid_request_200(self.token_dict['uuid_token_default']) 618 self.assert_valid_request_200(self.token_dict['uuid_token_default'])
644 self.assert_valid_last_url(self.token_dict['uuid_token_default']) 619 self.assert_valid_last_url(self.token_dict['uuid_token_default'])
645 620
646 def _test_cache_revoked(self, token, revoked_form=None):
647 # When the token is cached and revoked, 401 is returned.
648 self.middleware._check_revocations_for_cached = True
649
650 # Token should be cached as ok after this.
651 self.call_middleware(headers={'X-Auth-Token': token})
652
653 # Put it in revocation list.
654 self.middleware._revocations._list = self.get_revocation_list_json(
655 token_ids=[revoked_form or token])
656
657 self.call_middleware(headers={'X-Auth-Token': token},
658 expected_status=401)
659
660 def test_cached_revoked_error(self):
661 # When the token is cached and revocation list retrieval fails,
662 # 503 is returned
663 token = self.token_dict['uuid_token_default']
664 self.middleware._check_revocations_for_cached = True
665
666 # Token should be cached as ok after this.
667 resp = self.call_middleware(headers={'X-Auth-Token': token})
668 self.assertEqual(200, resp.status_int)
669
670 # Cause the revocation list to be fetched again next time so we can
671 # test the case where that retrieval fails
672 self.middleware._revocations._fetched_time = datetime.datetime.min
673 with mock.patch.object(self.middleware._revocations, '_fetch',
674 side_effect=ksm_exceptions.RevocationListError):
675 self.call_middleware(headers={'X-Auth-Token': token},
676 expected_status=503)
677
678 def test_cached_revoked_uuid(self):
679 # When the UUID token is cached and revoked, 401 is returned.
680 self._test_cache_revoked(self.token_dict['uuid_token_default'])
681
682 def test_valid_signed_request(self): 621 def test_valid_signed_request(self):
683 for _ in range(2): # Do it twice because first result was cached. 622 for _ in range(2): # Do it twice because first result was cached.
684 self.assert_valid_request_200( 623 self.assert_valid_request_200(
@@ -692,176 +631,13 @@ class CommonAuthTokenMiddlewareTest(object):
692 # ensure that signed requests do not generate HTTP traffic 631 # ensure that signed requests do not generate HTTP traffic
693 self.assertLastPath(None) 632 self.assertLastPath(None)
694 633
695 def test_revoked_token_receives_401(self):
696 self.middleware._revocations._list = (
697 self.get_revocation_list_json())
698
699 token = self.token_dict['revoked_token']
700 self.call_middleware(headers={'X-Auth-Token': token},
701 expected_status=401)
702
703 def test_revoked_token_receives_401_sha256(self):
704 self.conf['hash_algorithms'] = ','.join(['sha256', 'md5'])
705 self.set_middleware()
706 self.middleware._revocations._list = (
707 self.get_revocation_list_json(mode='sha256'))
708
709 token = self.token_dict['revoked_token']
710 self.call_middleware(headers={'X-Auth-Token': token},
711 expected_status=401)
712
713 def test_cached_revoked_pki(self):
714 # When the PKI token is cached and revoked, 401 is returned.
715 token = self.token_dict['signed_token_scoped']
716 revoked_form = cms.cms_hash_token(token)
717 self._test_cache_revoked(token, revoked_form)
718
719 def test_cached_revoked_pkiz(self):
720 # When the PKIZ token is cached and revoked, 401 is returned.
721 token = self.token_dict['signed_token_scoped_pkiz']
722 revoked_form = cms.cms_hash_token(token)
723 self._test_cache_revoked(token, revoked_form)
724
725 def test_revoked_token_receives_401_md5_secondary(self):
726 # When hash_algorithms has 'md5' as the secondary hash and the
727 # revocation list contains the md5 hash for a token, that token is
728 # considered revoked so returns 401.
729 self.conf['hash_algorithms'] = ','.join(['sha256', 'md5'])
730 self.set_middleware()
731 self.middleware._revocations._list = (
732 self.get_revocation_list_json())
733
734 token = self.token_dict['revoked_token']
735 self.call_middleware(headers={'X-Auth-Token': token},
736 expected_status=401)
737
738 def _test_revoked_hashed_token(self, token_name):
739 # If hash_algorithms is set as ['sha256', 'md5'],
740 # and check_revocations_for_cached is True,
741 # and a token is in the cache because it was successfully validated
742 # using the md5 hash, then
743 # if the token is in the revocation list by md5 hash, it'll be
744 # rejected and auth_token returns 401.
745 self.conf['hash_algorithms'] = ','.join(['sha256', 'md5'])
746 self.conf['check_revocations_for_cached'] = 'true'
747 self.set_middleware()
748
749 token = self.token_dict[token_name]
750
751 # Put the token in the revocation list.
752 token_hashed = cms.cms_hash_token(token)
753 self.middleware._revocations._list = self.get_revocation_list_json(
754 token_ids=[token_hashed])
755
756 # First, request is using the hashed token, is valid so goes in
757 # cache using the given hash.
758 self.call_middleware(headers={'X-Auth-Token': token_hashed})
759
760 # This time use the PKI(Z) token
761 # Should find the token in the cache and revocation list.
762 self.call_middleware(headers={'X-Auth-Token': token},
763 expected_status=401)
764
765 def test_revoked_hashed_pki_token(self):
766 self._test_revoked_hashed_token('signed_token_scoped')
767
768 def test_revoked_hashed_pkiz_token(self):
769 self._test_revoked_hashed_token('signed_token_scoped_pkiz')
770
771 def test_revoked_pki_token_by_audit_id(self):
772 # When the audit ID is in the revocation list, the token is invalid.
773 self.set_middleware()
774 token = self.token_dict['signed_token_scoped']
775
776 # Put the token audit ID in the revocation list,
777 # the entry will have a false token ID so the token ID doesn't match.
778 fake_token_id = uuid.uuid4().hex
779 # The audit_id value is in examples/pki/cms/auth_*_token_scoped.json.
780 audit_id = 'SLIXlXQUQZWUi9VJrqdXqA'
781 revocation_list_data = {
782 'revoked': [
783 {
784 'id': fake_token_id,
785 'audit_id': audit_id
786 },
787 ]
788 }
789 self.middleware._revocations._list = jsonutils.dumps(
790 revocation_list_data)
791
792 self.call_middleware(headers={'X-Auth-Token': token},
793 expected_status=401)
794
795 def get_revocation_list_json(self, token_ids=None, mode=None):
796 if token_ids is None:
797 key = 'revoked_token_hash' + (('_' + mode) if mode else '')
798 token_ids = [self.token_dict[key]]
799 revocation_list = {'revoked': [{'id': x, 'expires': timeutils.utcnow()}
800 for x in token_ids]}
801 return jsonutils.dumps(revocation_list)
802
803 def test_is_signed_token_revoked_returns_false(self):
804 # explicitly setting an empty revocation list here to document intent
805 self.middleware._revocations._list = jsonutils.dumps(
806 {"revoked": [], "extra": "success"})
807 result = self.middleware._revocations._any_revoked(
808 [self.token_dict['revoked_token_hash']])
809 self.assertFalse(result)
810
811 def test_is_signed_token_revoked_returns_true(self):
812 self.middleware._revocations._list = (
813 self.get_revocation_list_json())
814 result = self.middleware._revocations._any_revoked(
815 [self.token_dict['revoked_token_hash']])
816 self.assertTrue(result)
817
818 def test_is_signed_token_revoked_returns_true_sha256(self):
819 self.conf['hash_algorithms'] = ','.join(['sha256', 'md5'])
820 self.set_middleware()
821 self.middleware._revocations._list = (
822 self.get_revocation_list_json(mode='sha256'))
823 result = self.middleware._revocations._any_revoked(
824 [self.token_dict['revoked_token_hash_sha256']])
825 self.assertTrue(result)
826
827 def test_validate_offline_raises_exception_for_revoked_token(self):
828 self.middleware._revocations._list = (
829 self.get_revocation_list_json())
830 self.assertRaises(ksm_exceptions.InvalidToken,
831 self.middleware._validate_offline,
832 self.token_dict['revoked_token'],
833 [self.token_dict['revoked_token_hash']])
834
835 def test_validate_offline_raises_exception_for_revoked_token_s256(self):
836 self.conf['hash_algorithms'] = ','.join(['sha256', 'md5'])
837 self.set_middleware()
838 self.middleware._revocations._list = (
839 self.get_revocation_list_json(mode='sha256'))
840 self.assertRaises(ksm_exceptions.InvalidToken,
841 self.middleware._validate_offline,
842 self.token_dict['revoked_token'],
843 [self.token_dict['revoked_token_hash_sha256'],
844 self.token_dict['revoked_token_hash']])
845
846 def test_validate_offline_raises_exception_for_revoked_pkiz_token(self):
847 self.middleware._revocations._list = (
848 self.examples.REVOKED_TOKEN_PKIZ_LIST_JSON)
849 self.assertRaises(ksm_exceptions.InvalidToken,
850 self.middleware._validate_offline,
851 self.token_dict['revoked_token_pkiz'],
852 [self.token_dict['revoked_token_pkiz_hash']])
853
854 def test_validate_offline_succeeds_for_unrevoked_token(self): 634 def test_validate_offline_succeeds_for_unrevoked_token(self):
855 self.middleware._revocations._list = (
856 self.get_revocation_list_json())
857 token = self.middleware._validate_offline( 635 token = self.middleware._validate_offline(
858 self.token_dict['signed_token_scoped'], 636 self.token_dict['signed_token_scoped'],
859 [self.token_dict['signed_token_scoped_hash']]) 637 [self.token_dict['signed_token_scoped_hash']])
860 self.assertIsInstance(token, dict) 638 self.assertIsInstance(token, dict)
861 639
862 def test_verify_signed_compressed_token_succeeds_for_unrevoked_token(self): 640 def test_verify_signed_compressed_token_succeeds_for_unrevoked_token(self):
863 self.middleware._revocations._list = (
864 self.get_revocation_list_json())
865 token = self.middleware._validate_offline( 641 token = self.middleware._validate_offline(
866 self.token_dict['signed_token_scoped_pkiz'], 642 self.token_dict['signed_token_scoped_pkiz'],
867 [self.token_dict['signed_token_scoped_hash']]) 643 [self.token_dict['signed_token_scoped_hash']])
@@ -870,84 +646,12 @@ class CommonAuthTokenMiddlewareTest(object):
870 def test_validate_offline_token_succeeds_for_unrevoked_token_sha256(self): 646 def test_validate_offline_token_succeeds_for_unrevoked_token_sha256(self):
871 self.conf['hash_algorithms'] = ','.join(['sha256', 'md5']) 647 self.conf['hash_algorithms'] = ','.join(['sha256', 'md5'])
872 self.set_middleware() 648 self.set_middleware()
873 self.middleware._revocations._list = (
874 self.get_revocation_list_json(mode='sha256'))
875 token = self.middleware._validate_offline( 649 token = self.middleware._validate_offline(
876 self.token_dict['signed_token_scoped'], 650 self.token_dict['signed_token_scoped'],
877 [self.token_dict['signed_token_scoped_hash_sha256'], 651 [self.token_dict['signed_token_scoped_hash_sha256'],
878 self.token_dict['signed_token_scoped_hash']]) 652 self.token_dict['signed_token_scoped_hash']])
879 self.assertIsInstance(token, dict) 653 self.assertIsInstance(token, dict)
880 654
881 def test_get_token_revocation_list_fetched_time_returns_min(self):
882 self.middleware._revocations._fetched_time = None
883
884 # Get rid of the revoked file
885 revoked_path = self.middleware._signing_directory.calc_path(
886 _revocations.Revocations._FILE_NAME)
887 os.remove(revoked_path)
888
889 self.assertEqual(self.middleware._revocations._fetched_time,
890 datetime.datetime.min)
891
892 # FIXME(blk-u): move the unit tests into unit/test_auth_token.py
893 def test_get_token_revocation_list_fetched_time_returns_mtime(self):
894 self.middleware._revocations._fetched_time = None
895 revoked_path = self.middleware._signing_directory.calc_path(
896 _revocations.Revocations._FILE_NAME)
897 mtime = os.path.getmtime(revoked_path)
898 fetched_time = datetime.datetime.utcfromtimestamp(mtime)
899 self.assertEqual(fetched_time,
900 self.middleware._revocations._fetched_time)
901
902 @testtools.skipUnless(TimezoneFixture.supported(),
903 'TimezoneFixture not supported')
904 def test_get_token_revocation_list_fetched_time_returns_utc(self):
905 with TimezoneFixture('UTC-1'):
906 self.middleware._revocations._list = jsonutils.dumps(
907 self.examples.REVOCATION_LIST)
908 self.middleware._revocations._fetched_time = None
909 fetched_time = self.middleware._revocations._fetched_time
910 self.assertTrue(timeutils.is_soon(fetched_time, 1))
911
912 def test_get_token_revocation_list_fetched_time_returns_value(self):
913 expected = self.middleware._revocations._fetched_time
914 self.assertEqual(self.middleware._revocations._fetched_time,
915 expected)
916
917 def test_get_revocation_list_returns_fetched_list(self):
918 # auth_token uses v2 to fetch this, so don't allow the v3
919 # tests to override the fake http connection
920 self.middleware._revocations._fetched_time = None
921
922 # Get rid of the revoked file
923 revoked_path = self.middleware._signing_directory.calc_path(
924 _revocations.Revocations._FILE_NAME)
925 os.remove(revoked_path)
926
927 self.assertEqual(self.middleware._revocations._list,
928 self.examples.REVOCATION_LIST)
929
930 def test_get_revocation_list_returns_current_list_from_memory(self):
931 self.assertEqual(self.middleware._revocations._list,
932 self.middleware._revocations._list_prop)
933
934 def test_get_revocation_list_returns_current_list_from_disk(self):
935 in_memory_list = self.middleware._revocations._list
936 self.middleware._revocations._list_prop = None
937 self.assertEqual(self.middleware._revocations._list,
938 in_memory_list)
939
940 def test_invalid_revocation_list_raises_error(self):
941 self.requests_mock.get(self.revocation_url, json={})
942 self.assertRaises(ksm_exceptions.RevocationListError,
943 self.middleware._revocations._fetch)
944
945 def test_fetch_revocation_list(self):
946 # auth_token uses v2 to fetch this, so don't allow the v3
947 # tests to override the fake http connection
948 fetched = jsonutils.loads(self.middleware._revocations._fetch())
949 self.assertEqual(fetched, self.examples.REVOCATION_LIST)
950
951 def test_request_invalid_uuid_token(self): 655 def test_request_invalid_uuid_token(self):
952 # remember because we are testing the middleware we stub the connection 656 # remember because we are testing the middleware we stub the connection
953 # to the keystone server, but this is not what gets returned 657 # to the keystone server, but this is not what gets returned
@@ -1604,13 +1308,6 @@ class v2AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
1604 self.examples.SIGNED_TOKEN_SCOPED_HASH_SHA256, 1308 self.examples.SIGNED_TOKEN_SCOPED_HASH_SHA256,
1605 'signed_token_scoped_expired': 1309 'signed_token_scoped_expired':
1606 self.examples.SIGNED_TOKEN_SCOPED_EXPIRED, 1310 self.examples.SIGNED_TOKEN_SCOPED_EXPIRED,
1607 'revoked_token': self.examples.REVOKED_TOKEN,
1608 'revoked_token_pkiz': self.examples.REVOKED_TOKEN_PKIZ,
1609 'revoked_token_pkiz_hash':
1610 self.examples.REVOKED_TOKEN_PKIZ_HASH,
1611 'revoked_token_hash': self.examples.REVOKED_TOKEN_HASH,
1612 'revoked_token_hash_sha256':
1613 self.examples.REVOKED_TOKEN_HASH_SHA256,
1614 'uuid_service_token_default': 1311 'uuid_service_token_default':
1615 self.examples.UUID_SERVICE_TOKEN_DEFAULT, 1312 self.examples.UUID_SERVICE_TOKEN_DEFAULT,
1616 } 1313 }
@@ -1622,10 +1319,6 @@ class v2AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
1622 self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, 1319 self.requests_mock.post('%s/v2.0/tokens' % BASE_URI,
1623 text=FAKE_ADMIN_TOKEN) 1320 text=FAKE_ADMIN_TOKEN)
1624 1321
1625 self.revocation_url = '%s/v2.0/tokens/revoked' % BASE_URI
1626 self.requests_mock.get(self.revocation_url,
1627 text=self.examples.SIGNED_REVOCATION_LIST)
1628
1629 for token in (self.examples.UUID_TOKEN_DEFAULT, 1322 for token in (self.examples.UUID_TOKEN_DEFAULT,
1630 self.examples.UUID_TOKEN_UNSCOPED, 1323 self.examples.UUID_TOKEN_UNSCOPED,
1631 self.examples.UUID_TOKEN_BIND, 1324 self.examples.UUID_TOKEN_BIND,
@@ -1812,13 +1505,6 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
1812 self.examples.SIGNED_v3_TOKEN_SCOPED_HASH_SHA256, 1505 self.examples.SIGNED_v3_TOKEN_SCOPED_HASH_SHA256,
1813 'signed_token_scoped_expired': 1506 'signed_token_scoped_expired':
1814 self.examples.SIGNED_TOKEN_SCOPED_EXPIRED, 1507 self.examples.SIGNED_TOKEN_SCOPED_EXPIRED,
1815 'revoked_token': self.examples.REVOKED_v3_TOKEN,
1816 'revoked_token_pkiz': self.examples.REVOKED_v3_TOKEN_PKIZ,
1817 'revoked_token_hash': self.examples.REVOKED_v3_TOKEN_HASH,
1818 'revoked_token_hash_sha256':
1819 self.examples.REVOKED_v3_TOKEN_HASH_SHA256,
1820 'revoked_token_pkiz_hash':
1821 self.examples.REVOKED_v3_PKIZ_TOKEN_HASH,
1822 'uuid_service_token_default': 1508 'uuid_service_token_default':
1823 self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT, 1509 self.examples.v3_UUID_SERVICE_TOKEN_DEFAULT,
1824 } 1510 }
@@ -1832,10 +1518,6 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
1832 self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, 1518 self.requests_mock.post('%s/v2.0/tokens' % BASE_URI,
1833 text=FAKE_ADMIN_TOKEN) 1519 text=FAKE_ADMIN_TOKEN)
1834 1520
1835 self.revocation_url = '%s/v3/auth/tokens/OS-PKI/revoked' % BASE_URI
1836 self.requests_mock.get(self.revocation_url,
1837 text=self.examples.SIGNED_REVOCATION_LIST)
1838
1839 self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, 1521 self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI,
1840 text=self.token_response, 1522 text=self.token_response,
1841 headers={'X-Subject-Token': uuid.uuid4().hex}) 1523 headers={'X-Subject-Token': uuid.uuid4().hex})
@@ -1944,7 +1626,6 @@ class v3AuthTokenMiddlewareTest(BaseAuthTokenMiddlewareTest,
1944 self.token_dict['signed_token_scoped_pkiz']) 1626 self.token_dict['signed_token_scoped_pkiz'])
1945 1627
1946 def test_fallback_to_online_validation_with_revocation_list_error(self): 1628 def test_fallback_to_online_validation_with_revocation_list_error(self):
1947 self.requests_mock.get(self.revocation_url, status_code=404)
1948 self.assert_valid_request_200(self.token_dict['signed_token_scoped']) 1629 self.assert_valid_request_200(self.token_dict['signed_token_scoped'])
1949 self.assert_valid_request_200( 1630 self.assert_valid_request_200(
1950 self.token_dict['signed_token_scoped_pkiz']) 1631 self.token_dict['signed_token_scoped_pkiz'])
@@ -2398,10 +2079,6 @@ class v2CompositeAuthTests(BaseAuthTokenMiddlewareTest,
2398 self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, 2079 self.requests_mock.post('%s/v2.0/tokens' % BASE_URI,
2399 text=FAKE_ADMIN_TOKEN) 2080 text=FAKE_ADMIN_TOKEN)
2400 2081
2401 self.requests_mock.get('%s/v2.0/tokens/revoked' % BASE_URI,
2402 text=self.examples.SIGNED_REVOCATION_LIST,
2403 status_code=200)
2404
2405 for token in (self.examples.UUID_TOKEN_DEFAULT, 2082 for token in (self.examples.UUID_TOKEN_DEFAULT,
2406 self.examples.UUID_SERVICE_TOKEN_DEFAULT, 2083 self.examples.UUID_SERVICE_TOKEN_DEFAULT,
2407 self.examples.UUID_TOKEN_BIND, 2084 self.examples.UUID_TOKEN_BIND,
@@ -2455,9 +2132,6 @@ class v3CompositeAuthTests(BaseAuthTokenMiddlewareTest,
2455 self.requests_mock.post('%s/v2.0/tokens' % BASE_URI, 2132 self.requests_mock.post('%s/v2.0/tokens' % BASE_URI,
2456 text=FAKE_ADMIN_TOKEN) 2133 text=FAKE_ADMIN_TOKEN)
2457 2134
2458 self.requests_mock.get('%s/v3/auth/tokens/OS-PKI/revoked' % BASE_URI,
2459 text=self.examples.SIGNED_REVOCATION_LIST)
2460
2461 self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI, 2135 self.requests_mock.get('%s/v3/auth/tokens' % BASE_URI,
2462 text=self.token_response, 2136 text=self.token_response,
2463 headers={'X-Subject-Token': uuid.uuid4().hex}) 2137 headers={'X-Subject-Token': uuid.uuid4().hex})
diff --git a/keystonemiddleware/tests/unit/auth_token/test_revocations.py b/keystonemiddleware/tests/unit/auth_token/test_revocations.py
deleted file mode 100644
index 258e195..0000000
--- a/keystonemiddleware/tests/unit/auth_token/test_revocations.py
+++ /dev/null
@@ -1,104 +0,0 @@
1# Copyright 2014 IBM Corp.
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may
4# not use this file except in compliance with the License. You may obtain
5# a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations
13# under the License.
14
15import datetime
16import json
17import shutil
18import uuid
19
20import mock
21
22from keystonemiddleware.auth_token import _exceptions as exc
23from keystonemiddleware.auth_token import _revocations
24from keystonemiddleware.auth_token import _signing_dir
25from keystonemiddleware.tests.unit import utils
26
27
28class RevocationsTests(utils.BaseTestCase):
29
30 def _setup_revocations(self, revoked_list):
31 directory_name = '/tmp/%s' % uuid.uuid4().hex
32 signing_directory = _signing_dir.SigningDirectory(directory_name)
33 self.addCleanup(shutil.rmtree, directory_name)
34
35 identity_server = mock.Mock()
36
37 verify_result_obj = {'revoked': revoked_list}
38 cms_verify = mock.Mock(return_value=json.dumps(verify_result_obj))
39
40 revocations = _revocations.Revocations(
41 timeout=datetime.timedelta(1), signing_directory=signing_directory,
42 identity_server=identity_server, cms_verify=cms_verify)
43 return revocations
44
45 def _check_with_list(self, revoked_list, token_ids):
46 revoked_list = list({'id': r} for r in revoked_list)
47 revocations = self._setup_revocations(revoked_list)
48 revocations.check(token_ids)
49
50 def test_check_empty_list(self):
51 # When the identity server returns an empty list, a token isn't
52 # revoked.
53
54 revoked_tokens = []
55 token_ids = [uuid.uuid4().hex]
56 # No assert because this would raise
57 self._check_with_list(revoked_tokens, token_ids)
58
59 def test_check_revoked(self):
60 # When the identity server returns a list with a token in it, that
61 # token is revoked.
62
63 token_id = uuid.uuid4().hex
64 revoked_tokens = [token_id]
65 token_ids = [token_id]
66 self.assertRaises(exc.InvalidToken,
67 self._check_with_list, revoked_tokens, token_ids)
68
69 def test_check_by_audit_id_revoked(self):
70 # When the audit ID is in the revocation list, InvalidToken is raised.
71 audit_id = uuid.uuid4().hex
72 revoked_list = [{'id': uuid.uuid4().hex, 'audit_id': audit_id}]
73 revocations = self._setup_revocations(revoked_list)
74 self.assertRaises(exc.InvalidToken,
75 revocations.check_by_audit_id, [audit_id])
76
77 def test_check_by_audit_id_chain_revoked(self):
78 # When the token's audit chain ID is in the revocation list,
79 # InvalidToken is raised.
80 revoked_audit_id = uuid.uuid4().hex
81 revoked_list = [{'id': uuid.uuid4().hex, 'audit_id': revoked_audit_id}]
82 revocations = self._setup_revocations(revoked_list)
83
84 token_audit_ids = [uuid.uuid4().hex, revoked_audit_id]
85 self.assertRaises(exc.InvalidToken,
86 revocations.check_by_audit_id, token_audit_ids)
87
88 def test_check_by_audit_id_not_revoked(self):
89 # When the audit ID is not in the revocation list no exception.
90 revoked_list = [{'id': uuid.uuid4().hex, 'audit_id': uuid.uuid4().hex}]
91 revocations = self._setup_revocations(revoked_list)
92
93 audit_id = uuid.uuid4().hex
94 revocations.check_by_audit_id([audit_id])
95
96 def test_check_by_audit_id_no_audit_ids(self):
97 # Older identity servers don't send audit_ids in the revocation list.
98 # When this happens, check_by_audit_id still works, just doesn't
99 # verify anything.
100 revoked_list = [{'id': uuid.uuid4().hex}]
101 revocations = self._setup_revocations(revoked_list)
102
103 audit_id = uuid.uuid4().hex
104 revocations.check_by_audit_id([audit_id])
diff --git a/keystonemiddleware/tests/unit/client_fixtures.py b/keystonemiddleware/tests/unit/client_fixtures.py
index 9f5a917..9f56804 100644
--- a/keystonemiddleware/tests/unit/client_fixtures.py
+++ b/keystonemiddleware/tests/unit/client_fixtures.py
@@ -20,7 +20,6 @@ from keystoneauth1 import fixture
20from keystoneclient.common import cms 20from keystoneclient.common import cms
21from keystoneclient import utils 21from keystoneclient import utils
22from oslo_serialization import jsonutils 22from oslo_serialization import jsonutils
23from oslo_utils import timeutils
24import six 23import six
25import testresources 24import testresources
26 25
@@ -77,29 +76,17 @@ class Examples(fixtures.Fixture):
77 self.SIGNED_v3_TOKEN_SCOPED) 76 self.SIGNED_v3_TOKEN_SCOPED)
78 self.SIGNED_v3_TOKEN_SCOPED_HASH_SHA256 = _hash_signed_token_safe( 77 self.SIGNED_v3_TOKEN_SCOPED_HASH_SHA256 = _hash_signed_token_safe(
79 self.SIGNED_v3_TOKEN_SCOPED, mode='sha256') 78 self.SIGNED_v3_TOKEN_SCOPED, mode='sha256')
80 with open(os.path.join(CMSDIR, 'auth_token_revoked.pem')) as f:
81 self.REVOKED_TOKEN = cms.cms_to_token(f.read())
82 with open(os.path.join(CMSDIR, 'auth_token_scoped_expired.pem')) as f: 79 with open(os.path.join(CMSDIR, 'auth_token_scoped_expired.pem')) as f:
83 self.SIGNED_TOKEN_SCOPED_EXPIRED = cms.cms_to_token(f.read()) 80 self.SIGNED_TOKEN_SCOPED_EXPIRED = cms.cms_to_token(f.read())
84 with open(os.path.join(CMSDIR, 'auth_v3_token_revoked.pem')) as f:
85 self.REVOKED_v3_TOKEN = cms.cms_to_token(f.read())
86 with open(os.path.join(CMSDIR, 'auth_token_scoped.pkiz')) as f: 81 with open(os.path.join(CMSDIR, 'auth_token_scoped.pkiz')) as f:
87 self.SIGNED_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read()) 82 self.SIGNED_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read())
88 with open(os.path.join(CMSDIR, 'auth_token_unscoped.pkiz')) as f: 83 with open(os.path.join(CMSDIR, 'auth_token_unscoped.pkiz')) as f:
89 self.SIGNED_TOKEN_UNSCOPED_PKIZ = cms.cms_to_token(f.read()) 84 self.SIGNED_TOKEN_UNSCOPED_PKIZ = cms.cms_to_token(f.read())
90 with open(os.path.join(CMSDIR, 'auth_v3_token_scoped.pkiz')) as f: 85 with open(os.path.join(CMSDIR, 'auth_v3_token_scoped.pkiz')) as f:
91 self.SIGNED_v3_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read()) 86 self.SIGNED_v3_TOKEN_SCOPED_PKIZ = cms.cms_to_token(f.read())
92 with open(os.path.join(CMSDIR, 'auth_token_revoked.pkiz')) as f:
93 self.REVOKED_TOKEN_PKIZ = cms.cms_to_token(f.read())
94 with open(os.path.join(CMSDIR, 87 with open(os.path.join(CMSDIR,
95 'auth_token_scoped_expired.pkiz')) as f: 88 'auth_token_scoped_expired.pkiz')) as f:
96 self.SIGNED_TOKEN_SCOPED_EXPIRED_PKIZ = cms.cms_to_token(f.read()) 89 self.SIGNED_TOKEN_SCOPED_EXPIRED_PKIZ = cms.cms_to_token(f.read())
97 with open(os.path.join(CMSDIR, 'auth_v3_token_revoked.pkiz')) as f:
98 self.REVOKED_v3_TOKEN_PKIZ = cms.cms_to_token(f.read())
99 with open(os.path.join(CMSDIR, 'revocation_list.json')) as f:
100 self.REVOCATION_LIST = jsonutils.loads(f.read())
101 with open(os.path.join(CMSDIR, 'revocation_list.pem')) as f:
102 self.SIGNED_REVOCATION_LIST = jsonutils.dumps({'signed': f.read()})
103 90
104 self.SIGNING_CERT_FILE = os.path.join(CERTDIR, 'signing_cert.pem') 91 self.SIGNING_CERT_FILE = os.path.join(CERTDIR, 'signing_cert.pem')
105 with open(self.SIGNING_CERT_FILE) as f: 92 with open(self.SIGNING_CERT_FILE) as f:
@@ -134,50 +121,6 @@ class Examples(fixtures.Fixture):
134 self.v3_UUID_SERVICE_TOKEN_DEFAULT = 'g431071bbc2f492748596c1b53cb229' 121 self.v3_UUID_SERVICE_TOKEN_DEFAULT = 'g431071bbc2f492748596c1b53cb229'
135 self.v3_UUID_SERVICE_TOKEN_BIND = 'be705e4426d0449a89e35ae21c380a05' 122 self.v3_UUID_SERVICE_TOKEN_BIND = 'be705e4426d0449a89e35ae21c380a05'
136 self.v3_NOT_IS_ADMIN_PROJECT = uuid.uuid4().hex 123 self.v3_NOT_IS_ADMIN_PROJECT = uuid.uuid4().hex
137
138 revoked_token = self.REVOKED_TOKEN
139 if isinstance(revoked_token, six.text_type):
140 revoked_token = revoked_token.encode('utf-8')
141 self.REVOKED_TOKEN_HASH = utils.hash_signed_token(revoked_token)
142 self.REVOKED_TOKEN_HASH_SHA256 = utils.hash_signed_token(revoked_token,
143 mode='sha256')
144 self.REVOKED_TOKEN_LIST = (
145 {'revoked': [{'id': self.REVOKED_TOKEN_HASH,
146 'expires': timeutils.utcnow()}]})
147 self.REVOKED_TOKEN_LIST_JSON = jsonutils.dumps(self.REVOKED_TOKEN_LIST)
148
149 revoked_v3_token = self.REVOKED_v3_TOKEN
150 if isinstance(revoked_v3_token, six.text_type):
151 revoked_v3_token = revoked_v3_token.encode('utf-8')
152 self.REVOKED_v3_TOKEN_HASH = utils.hash_signed_token(revoked_v3_token)
153 hash = utils.hash_signed_token(revoked_v3_token, mode='sha256')
154 self.REVOKED_v3_TOKEN_HASH_SHA256 = hash
155 self.REVOKED_v3_TOKEN_LIST = (
156 {'revoked': [{'id': self.REVOKED_v3_TOKEN_HASH,
157 'expires': timeutils.utcnow()}]})
158 self.REVOKED_v3_TOKEN_LIST_JSON = jsonutils.dumps(
159 self.REVOKED_v3_TOKEN_LIST)
160
161 revoked_token_pkiz = self.REVOKED_TOKEN_PKIZ
162 if isinstance(revoked_token_pkiz, six.text_type):
163 revoked_token_pkiz = revoked_token_pkiz.encode('utf-8')
164 self.REVOKED_TOKEN_PKIZ_HASH = utils.hash_signed_token(
165 revoked_token_pkiz)
166 revoked_v3_token_pkiz = self.REVOKED_v3_TOKEN_PKIZ
167 if isinstance(revoked_v3_token_pkiz, six.text_type):
168 revoked_v3_token_pkiz = revoked_v3_token_pkiz.encode('utf-8')
169 self.REVOKED_v3_PKIZ_TOKEN_HASH = utils.hash_signed_token(
170 revoked_v3_token_pkiz)
171
172 self.REVOKED_TOKEN_PKIZ_LIST = (
173 {'revoked': [{'id': self.REVOKED_TOKEN_PKIZ_HASH,
174 'expires': timeutils.utcnow()},
175 {'id': self.REVOKED_v3_PKIZ_TOKEN_HASH,
176 'expires': timeutils.utcnow()},
177 ]})
178 self.REVOKED_TOKEN_PKIZ_LIST_JSON = jsonutils.dumps(
179 self.REVOKED_TOKEN_PKIZ_LIST)
180
181 self.SIGNED_TOKEN_SCOPED_KEY = cms.cms_hash_token( 124 self.SIGNED_TOKEN_SCOPED_KEY = cms.cms_hash_token(
182 self.SIGNED_TOKEN_SCOPED) 125 self.SIGNED_TOKEN_SCOPED)
183 self.SIGNED_TOKEN_UNSCOPED_KEY = cms.cms_hash_token( 126 self.SIGNED_TOKEN_UNSCOPED_KEY = cms.cms_hash_token(
diff --git a/keystonemiddleware/tests/unit/test_opts.py b/keystonemiddleware/tests/unit/test_opts.py
index 5768011..3b4e510 100644
--- a/keystonemiddleware/tests/unit/test_opts.py
+++ b/keystonemiddleware/tests/unit/test_opts.py
@@ -55,7 +55,6 @@ class OptsTestCase(utils.TestCase):
55 'signing_dir', 55 'signing_dir',
56 'memcached_servers', 56 'memcached_servers',
57 'token_cache_time', 57 'token_cache_time',
58 'revocation_cache_time',
59 'memcache_security_strategy', 58 'memcache_security_strategy',
60 'memcache_secret_key', 59 'memcache_secret_key',
61 'memcache_use_advanced_pool', 60 'memcache_use_advanced_pool',
@@ -66,7 +65,6 @@ class OptsTestCase(utils.TestCase):
66 'memcache_pool_socket_timeout', 65 'memcache_pool_socket_timeout',
67 'include_service_catalog', 66 'include_service_catalog',
68 'enforce_token_bind', 67 'enforce_token_bind',
69 'check_revocations_for_cached',
70 'hash_algorithms', 68 'hash_algorithms',
71 'auth_type', 69 'auth_type',
72 'auth_section', 70 'auth_section',
@@ -102,7 +100,6 @@ class OptsTestCase(utils.TestCase):
102 'signing_dir', 100 'signing_dir',
103 'memcached_servers', 101 'memcached_servers',
104 'token_cache_time', 102 'token_cache_time',
105 'revocation_cache_time',
106 'memcache_security_strategy', 103 'memcache_security_strategy',
107 'memcache_secret_key', 104 'memcache_secret_key',
108 'memcache_use_advanced_pool', 105 'memcache_use_advanced_pool',
@@ -113,7 +110,6 @@ class OptsTestCase(utils.TestCase):
113 'memcache_pool_socket_timeout', 110 'memcache_pool_socket_timeout',
114 'include_service_catalog', 111 'include_service_catalog',
115 'enforce_token_bind', 112 'enforce_token_bind',
116 'check_revocations_for_cached',
117 'hash_algorithms', 113 'hash_algorithms',
118 'auth_type', 114 'auth_type',
119 'auth_section', 115 'auth_section',
diff --git a/releasenotes/notes/bug-1649735-3c68f3243e474775.yaml b/releasenotes/notes/bug-1649735-3c68f3243e474775.yaml
new file mode 100644
index 0000000..06741d3
--- /dev/null
+++ b/releasenotes/notes/bug-1649735-3c68f3243e474775.yaml
@@ -0,0 +1,8 @@
1---
2fixes:
3 - >
4 [`bug 1649735 <https://bugs.launchpad.net/keystone/+bug/1649735>`_]
5 The auth_token middleware no longer attempts to retrieve the revocation
6 list from the Keystone server. The deprecated options
7 `check_revocations_for_cached` and `check_revocations_for_cached` have been
8 removed.