Browse Source

Add Trusts unique constraint to remove duplicates

For now, effectively there could be multiple trusts with the same
project, trustor, trustee, expiry date, impersonation. The same
combination can have multiple trusts assigned with different roles
or not.

Patch fixes this issue by adding unique constraint to the trusts
database model. If two requests create trusts with the same
trustor, trustee, project, expiry, impersonation, then the second
request would bring up an exception saying there's a conflict.

This can help to improve specific trusts identification and
improve user experience.

Change-Id: I1a681b13cfbef40bf6c21271fb80966517fb1ec5
Closes-Bug: #1475091
tags/9.0.0.0b2
Kent Wang 3 years ago
parent
commit
59b09b50ff

+ 26
- 0
keystone/common/sql/migrate_repo/versions/086_add_duplicate_constraint_trusts.py View File

@@ -0,0 +1,26 @@
1
+# Copyright 2015 Intel Corporation
2
+# All Rights Reserved
3
+#
4
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
5
+#    not use this file except in compliance with the License. You may obtain
6
+#    a copy of the License at
7
+#
8
+#         http://www.apache.org/licenses/LICENSE-2.0
9
+#
10
+#    Unless required by applicable law or agreed to in writing, software
11
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+#    License for the specific language governing permissions and limitations
14
+#    under the License.
15
+
16
+from migrate import UniqueConstraint
17
+from sqlalchemy import MetaData, Table
18
+
19
+
20
+def upgrade(migrate_engine):
21
+    meta = MetaData(bind=migrate_engine)
22
+    trusts = Table('trust', meta, autoload=True)
23
+
24
+    UniqueConstraint('trustor_user_id', 'trustee_user_id', 'project_id',
25
+                     'impersonation', 'expires_at', table=trusts,
26
+                     name='duplicate_trust_constraint').create()

+ 22
- 2
keystone/tests/unit/test_backend.py View File

@@ -4786,13 +4786,13 @@ class TrustTests(object):
4786 4786
     def create_sample_trust(self, new_id, remaining_uses=None):
4787 4787
         self.trustor = self.user_foo
4788 4788
         self.trustee = self.user_two
4789
+        expires_at = datetime.datetime.utcnow().replace(year=2031)
4789 4790
         trust_data = (self.trust_api.create_trust
4790 4791
                       (new_id,
4791 4792
                        {'trustor_user_id': self.trustor['id'],
4792 4793
                         'trustee_user_id': self.user_two['id'],
4793 4794
                         'project_id': self.tenant_bar['id'],
4794
-                        'expires_at': timeutils.
4795
-                        parse_isotime('2031-02-18T18:10:00Z'),
4795
+                        'expires_at': expires_at,
4796 4796
                         'impersonation': True,
4797 4797
                         'remaining_uses': remaining_uses},
4798 4798
                        roles=[{"id": "member"},
@@ -4914,6 +4914,26 @@ class TrustTests(object):
4914 4914
                           self.trust_api.get_trust,
4915 4915
                           trust_data['id'])
4916 4916
 
4917
+    def test_duplicate_trusts_not_allowed(self):
4918
+        self.trustor = self.user_foo
4919
+        self.trustee = self.user_two
4920
+        trust_data = {'trustor_user_id': self.trustor['id'],
4921
+                      'trustee_user_id': self.user_two['id'],
4922
+                      'project_id': self.tenant_bar['id'],
4923
+                      'expires_at': timeutils.parse_isotime(
4924
+                          '2031-02-18T18:10:00Z'),
4925
+                      'impersonation': True,
4926
+                      'remaining_uses': None}
4927
+        roles = [{"id": "member"},
4928
+                 {"id": "other"},
4929
+                 {"id": "browser"}]
4930
+        self.trust_api.create_trust(uuid.uuid4().hex, trust_data, roles)
4931
+        self.assertRaises(exception.Conflict,
4932
+                          self.trust_api.create_trust,
4933
+                          uuid.uuid4().hex,
4934
+                          trust_data,
4935
+                          roles)
4936
+
4917 4937
 
4918 4938
 class CatalogTests(object):
4919 4939
 

+ 7
- 0
keystone/tests/unit/test_sql_upgrade.py View File

@@ -808,6 +808,13 @@ class SqlUpgradeTests(SqlMigrateBase):
808 808
         self.assertTableDoesNotExist('endpoint_group')
809 809
         self.assertTableDoesNotExist('project_endpoint_group')
810 810
 
811
+    def test_add_trust_unique_constraint_upgrade(self):
812
+        self.upgrade(86)
813
+        inspector = reflection.Inspector.from_engine(self.engine)
814
+        constraints = inspector.get_unique_constraints('trust')
815
+        constraint_names = [constraint['name'] for constraint in constraints]
816
+        self.assertIn('duplicate_trust_constraint', constraint_names)
817
+
811 818
     def populate_user_table(self, with_pass_enab=False,
812 819
                             with_pass_enab_domain=False):
813 820
         # Populate the appropriate fields in the user

+ 17
- 0
keystone/tests/unit/test_v3_auth.py View File

@@ -2847,6 +2847,8 @@ class TestTrustRedelegation(test_v3.RestfulTestCase):
2847 2847
 
2848 2848
         # Create first trust with extended set of roles
2849 2849
         ref = self.redelegated_trust_ref
2850
+        ref['expires_at'] = datetime.datetime.utcnow().replace(
2851
+            year=2031).strftime(unit.TIME_FORMAT)
2850 2852
         ref['roles'].append({'id': role['id']})
2851 2853
         r = self.post('/OS-TRUST/trusts',
2852 2854
                       body={'trust': ref})
@@ -2859,6 +2861,9 @@ class TestTrustRedelegation(test_v3.RestfulTestCase):
2859 2861
         trust_token = self._get_trust_token(trust)
2860 2862
 
2861 2863
         # Chain second trust with roles subset
2864
+        self.chained_trust_ref['expires_at'] = (
2865
+            datetime.datetime.utcnow().replace(year=2030).strftime(
2866
+                unit.TIME_FORMAT))
2862 2867
         r = self.post('/OS-TRUST/trusts',
2863 2868
                       body={'trust': self.chained_trust_ref},
2864 2869
                       token=trust_token)
@@ -2879,6 +2884,8 @@ class TestTrustRedelegation(test_v3.RestfulTestCase):
2879 2884
             expires=dict(minutes=1),
2880 2885
             role_names=[self.role['name']],
2881 2886
             allow_redelegation=True)
2887
+        ref['expires_at'] = datetime.datetime.utcnow().replace(
2888
+            year=2031).strftime(unit.TIME_FORMAT)
2882 2889
         r = self.post('/OS-TRUST/trusts',
2883 2890
                       body={'trust': ref})
2884 2891
         trust = self.assertValidTrustResponse(r)
@@ -2892,6 +2899,8 @@ class TestTrustRedelegation(test_v3.RestfulTestCase):
2892 2899
             impersonation=True,
2893 2900
             role_names=[self.role['name']],
2894 2901
             allow_redelegation=True)
2902
+        ref['expires_at'] = datetime.datetime.utcnow().replace(
2903
+            year=2030).strftime(unit.TIME_FORMAT)
2895 2904
         r = self.post('/OS-TRUST/trusts',
2896 2905
                       body={'trust': ref},
2897 2906
                       token=trust_token)
@@ -2924,12 +2933,18 @@ class TestTrustRedelegation(test_v3.RestfulTestCase):
2924 2933
                       expected_status=http_client.FORBIDDEN)
2925 2934
 
2926 2935
     def test_redelegation_terminator(self):
2936
+        self.redelegated_trust_ref['expires_at'] = (
2937
+            datetime.datetime.utcnow().replace(year=2031).strftime(
2938
+                unit.TIME_FORMAT))
2927 2939
         r = self.post('/OS-TRUST/trusts',
2928 2940
                       body={'trust': self.redelegated_trust_ref})
2929 2941
         trust = self.assertValidTrustResponse(r)
2930 2942
         trust_token = self._get_trust_token(trust)
2931 2943
 
2932 2944
         # Build second trust - the terminator
2945
+        self.chained_trust_ref['expires_at'] = (
2946
+            datetime.datetime.utcnow().replace(year=2030).strftime(
2947
+                unit.TIME_FORMAT))
2933 2948
         ref = dict(self.chained_trust_ref,
2934 2949
                    redelegation_count=1,
2935 2950
                    allow_redelegation=False)
@@ -3834,6 +3849,8 @@ class TestTrustAuth(test_v3.RestfulTestCase):
3834 3849
             role_ids=[self.role_id])
3835 3850
 
3836 3851
         for i in range(3):
3852
+            ref['expires_at'] = datetime.datetime.utcnow().replace(
3853
+                year=2031).strftime(unit.TIME_FORMAT)
3837 3854
             r = self.post('/OS-TRUST/trusts', body={'trust': ref})
3838 3855
             self.assertValidTrustResponse(r, ref)
3839 3856
 

+ 4
- 0
keystone/trust/backends/sql.py View File

@@ -45,6 +45,10 @@ class TrustModel(sql.ModelBase, sql.DictBase):
45 45
     expires_at = sql.Column(sql.DateTime)
46 46
     remaining_uses = sql.Column(sql.Integer, nullable=True)
47 47
     extra = sql.Column(sql.JsonBlob())
48
+    __table_args__ = (sql.UniqueConstraint(
49
+                      'trustor_user_id', 'trustee_user_id', 'project_id',
50
+                      'impersonation', 'expires_at',
51
+                      name='duplicate_trust_constraint'),)
48 52
 
49 53
 
50 54
 class TrustRole(sql.ModelBase):

Loading…
Cancel
Save