Skip to content

Commit bd4bde2

Browse files
Wdt compare model security providers (#901)
* WDT-561 compare block of security providers * Custom security providers attributes comparison * correct comments for new methods and files
1 parent 977c2fe commit bd4bde2

File tree

9 files changed

+326
-47
lines changed

9 files changed

+326
-47
lines changed

core/src/main/python/wlsdeploy/tool/compare/model_comparer.py

Lines changed: 136 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
Copyright (c) 2021, Oracle and/or its affiliates.
33
Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
44
"""
5+
from java.util import Properties
56

67
from oracle.weblogic.deploy.aliases import AliasException
78
from oracle.weblogic.deploy.util import PyOrderedDict
@@ -65,15 +66,76 @@ def _compare_folders(self, current_folder, past_folder, location, attributes_loc
6566

6667
# determine if the specified location has named folders, such as topology/Server
6768
has_named_folders = False
69+
has_security = False
6870
if (location is not None) and not self._aliases.is_artificial_type_folder(location):
69-
has_named_folders = self._aliases.supports_multiple_mbean_instances(location) or \
70-
self._aliases.requires_artificial_type_subfolder_handling(location)
71+
if self._aliases.supports_multiple_mbean_instances(location):
72+
has_named_folders = True
73+
elif self._aliases.requires_artificial_type_subfolder_handling(location):
74+
has_security = True
7175

7276
if has_named_folders:
7377
return self._compare_named_folders(current_folder, past_folder, location, attributes_location)
78+
elif has_security:
79+
return self._compare_security_folders(current_folder, past_folder, location, attributes_location)
7480
else:
7581
return self._compare_folder_contents(current_folder, past_folder, location, attributes_location)
7682

83+
def _compare_security_folders(self, current_folder, past_folder, location, attributes_location):
84+
"""
85+
Compare current and past security configuration provider section. If a provider section has an entry
86+
that is different from the original, the entire provider section will be returned as differences between
87+
the two folders.
88+
:param current_folder: a folder in the current model
89+
:param past_folder: corresponding folder in the past model
90+
:param location: the location for the specified folders
91+
:param attributes_location: the attribute location for the specified folders
92+
:return: a dictionary of differences between these folders
93+
"""
94+
providers = self._aliases.get_model_subfolder_names(location)
95+
matches = True
96+
custom = True
97+
if len(current_folder) == len(past_folder):
98+
curr_keys = current_folder.keys()
99+
past_keys = past_folder.keys()
100+
idx = 0
101+
while idx < len(curr_keys):
102+
if curr_keys[idx] == past_keys[idx]:
103+
custom = curr_keys[idx] not in providers
104+
next_curr_folder = current_folder[curr_keys[idx]]
105+
next_curr_keys = next_curr_folder.keys()
106+
next_past_folder = past_folder[past_keys[idx]]
107+
next_past_keys = next_past_folder.keys()
108+
next_idx = 0
109+
while next_idx < len(next_curr_keys):
110+
if next_curr_keys[next_idx] == next_past_keys[next_idx]:
111+
if custom:
112+
changes = self._compare_folder_sc_contents(next_curr_folder, next_past_folder,
113+
location, attributes_location)
114+
else:
115+
changes = self._compare_folder_contents(next_curr_folder, next_past_folder,
116+
location, attributes_location)
117+
if changes:
118+
matches = False
119+
break
120+
else:
121+
matches = False
122+
break
123+
next_idx +=1
124+
else:
125+
matches = False
126+
break
127+
idx +=1
128+
else:
129+
matches = False
130+
if matches is False:
131+
self._messages.add(('WLSDPLY-05716', location.get_folder_path()))
132+
comment = "Replace entire Security Provider section "
133+
new_folder = PyOrderedDict()
134+
_add_comment(comment, new_folder)
135+
new_folder.update(current_folder)
136+
return new_folder
137+
return PyOrderedDict()
138+
77139
def _compare_named_folders(self, current_folder, past_folder, location, attributes_location):
78140
"""
79141
Compare current and past named folders using the specified locations.
@@ -113,6 +175,46 @@ def _compare_named_folders(self, current_folder, past_folder, location, attribut
113175

114176
return change_folder
115177

178+
def _compare_folder_sc_contents(self, current_folder, past_folder, location, attributes_location):
179+
"""
180+
Compare the contents of current and past folders, looking at attribute changes for a security provider.
181+
Return any changes so that calling routine will know changes occurred and the entire section will
182+
of the current folder will be marked as changed.
183+
:param current_folder: a folder in the current model
184+
:param past_folder: corresponding folder in the past model
185+
:param location: the location for the specified folders
186+
:param attributes_location: the attribute location for the specified folders
187+
:return: a dictionary of differences between these folders
188+
"""
189+
change_folder = PyOrderedDict()
190+
191+
# check if keys in the current folder are present in the past folder
192+
for key in current_folder:
193+
if not self._check_key(key, location):
194+
continue
195+
196+
if key in past_folder:
197+
current_value = current_folder[key]
198+
past_value = past_folder[key]
199+
200+
self._compare_attribute_sc(current_value, past_value, attributes_location, key, change_folder)
201+
202+
else:
203+
# key is present the current folder, not in the past folder.
204+
# just add to the change folder, no further recursion needed.
205+
change_folder[key] = current_folder[key]
206+
207+
# check if keys in the past folder are not in the current folder
208+
for key in past_folder:
209+
if not self._check_key(key, location):
210+
continue
211+
212+
if key not in current_folder:
213+
change_folder[key] = past_folder[key]
214+
215+
self._finalize_folder(current_folder, past_folder, change_folder, location)
216+
return change_folder
217+
116218
def _compare_folder_contents(self, current_folder, past_folder, location, attributes_location):
117219
"""
118220
Compare the contents of current and past folders using the specified locations.
@@ -197,6 +299,38 @@ def _get_next_location(self, location, key):
197299

198300
return next_location, next_attributes_location
199301

302+
def _compare_attribute_sc(self, current_value, past_value, location, key, change_folder):
303+
"""
304+
Compare values of an attribute from the current and past folders.
305+
The changed value will signal the calling method that the entire new
306+
:param current_value: the value from the current model
307+
:param past_value: the value from the past model
308+
:param key: the key of the attribute
309+
:param change_folder: the folder in the change model to be updated
310+
:param location: the location for attributes in the specified folders
311+
"""
312+
if current_value != past_value:
313+
if type(current_value) == list:
314+
current_list = list(current_value)
315+
previous_list = list(past_value)
316+
317+
change_list = list(previous_list)
318+
for item in current_list:
319+
if item in previous_list:
320+
change_list.remove(item)
321+
else:
322+
change_list.append(item)
323+
for item in previous_list:
324+
if item not in current_list:
325+
change_list.remove(item)
326+
change_list.append(model_helper.get_delete_name(item))
327+
328+
elif isinstance(current_value, Properties):
329+
self._compare_properties(current_value, past_value, key, change_folder)
330+
331+
else:
332+
change_folder[key] = current_value
333+
200334
def _compare_attribute(self, current_value, past_value, location, key, change_folder):
201335
"""
202336
Compare values of an attribute from the current and past folders.
@@ -278,16 +412,6 @@ def _check_key(self, key, location):
278412
if (location is None) and (key == KUBERNETES):
279413
self._logger.info('WLSDPLY-05713', KUBERNETES, class_name=self._class_name, method_name=_method_name)
280414
return False
281-
try:
282-
if (location is not None) and (not self._aliases.is_artificial_type_folder(location)) and \
283-
(self._aliases.requires_artificial_type_subfolder_handling(location)):
284-
providers = self._aliases.get_model_subfolder_names(location)
285-
if key not in providers:
286-
self._logger.warning('WLSDPLY-05716', key,
287-
class_name=self._class_name, method_name=_method_name)
288-
return False
289-
except AliasException:
290-
return True
291415
return True
292416

293417
def _finalize_folder(self, current_folder, past_folder, change_folder, location):

core/src/main/resources/oracle/weblogic/deploy/messages/wlsdeploy_rb.properties

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,9 @@ WLSDPLY-05712=Unrecognized token {0} in path {1}
498498
WLSDPLY-05713=Model section {0} will not be compared
499499
WLSDPLY-05714={0} is unchanged, but required if other changes are present
500500
WLSDPLY-05715=There are {0} attributes that only exist in the previous model, see {1}
501-
WLSDPLY-05716=Model Security Configuration section contains a custom security provider {0}. The compare model cannot compare \
502-
a custom security provider and the section will not be included in the diffed model.
501+
WLSDPLY-05716=The Security Configuration Provider at location {0} has a difference and the current \
502+
provider(s) will replace the previous provider(s)
503+
503504

504505
# prepare_model.py
505506
WLSDPLY-05801=Error in prepare model {0}

core/src/test/python/compare_model_test.py

Lines changed: 53 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -122,11 +122,11 @@ def testCompareModelFull(self):
122122

123123
self.assertEqual(return_code, 0)
124124

125-
def testCompareModelSecurityConfiguration(self):
126-
_method_name = 'testCompareModelSecurityConfiguration'
125+
def testCompareModelSecurityConfigurationAttribute(self):
126+
_method_name = 'testCompareModelSecurityConfigurationAttribute'
127127

128-
_new_model_file = self._resources_dir + '/compare/model-a-old.yaml'
129-
_old_model_file = self._resources_dir + '/compare/model-a-new.yaml'
128+
_new_model_file = self._resources_dir + '/compare/model-sc1-new.yaml'
129+
_old_model_file = self._resources_dir + '/compare/model-sc1-old.yaml'
130130
_temp_dir = os.path.join(tempfile.gettempdir(), _method_name)
131131

132132
if os.path.exists(_temp_dir):
@@ -158,6 +158,7 @@ def testCompareModelSecurityConfiguration(self):
158158
self.assertEqual(model_dictionary['topology']['SecurityConfiguration']['Realm'].has_key('myrealm'), True)
159159
self.assertEqual(model_dictionary['topology']['SecurityConfiguration']['Realm']['myrealm'].has_key('Auditor'), False)
160160
self.assertEqual(model_dictionary['topology']['SecurityConfiguration']['Realm']['myrealm'].has_key('AuthenticationProvider'), True)
161+
self.assertEqual(model_dictionary['topology']['SecurityConfiguration']['Realm']['myrealm'].has_key('PasswordValidator'), False)
161162

162163
except (CompareException, PyWLSTException), te:
163164
return_code = 2
@@ -170,6 +171,54 @@ def testCompareModelSecurityConfiguration(self):
170171

171172
self.assertEqual(return_code, 0)
172173

174+
def testCompareModelSecurityConfigurationCustomList(self):
175+
_method_name = 'testCompareModelSecurityConfigurationCustomList'
176+
177+
_new_model_file = self._resources_dir + '/compare/model-sc2-new.yaml'
178+
_old_model_file = self._resources_dir + '/compare/model-sc2-old.yaml'
179+
_temp_dir = os.path.join(tempfile.gettempdir(), _method_name)
180+
181+
if os.path.exists(_temp_dir):
182+
shutil.rmtree(_temp_dir)
183+
184+
os.mkdir(_temp_dir)
185+
186+
mw_home = os.environ['MW_HOME']
187+
args_map = {
188+
'-oracle_home': mw_home,
189+
'-output_dir' : _temp_dir,
190+
'-domain_type' : 'WLS',
191+
'-trailing_arguments': [ _new_model_file, _old_model_file ]
192+
}
193+
194+
try:
195+
model_context = ModelContext('CompareModelTestCase', args_map)
196+
obj = ModelFileDiffer(_new_model_file, _old_model_file, model_context, _temp_dir)
197+
return_code = obj.compare()
198+
self.assertEqual(return_code, 0)
199+
200+
yaml_result = _temp_dir + os.sep + 'diffed_model.yaml'
201+
json_result = _temp_dir + os.sep + 'diffed_model.json'
202+
stdout_result = obj.get_compare_msgs()
203+
model_dictionary = FileToPython(yaml_result).parse()
204+
self.assertEqual(model_dictionary.has_key('topology'), True)
205+
self.assertEqual(model_dictionary['topology'].has_key('SecurityConfiguration'), True)
206+
self.assertEqual(model_dictionary['topology']['SecurityConfiguration'].has_key('Realm'), True)
207+
self.assertEqual(model_dictionary['topology']['SecurityConfiguration']['Realm'].has_key('myrealm'), True)
208+
self.assertEqual(model_dictionary['topology']['SecurityConfiguration']['Realm']['myrealm'].has_key('Auditor'), False)
209+
self.assertEqual(model_dictionary['topology']['SecurityConfiguration']['Realm']['myrealm'].has_key('AuthenticationProvider'), True)
210+
self.assertEqual(model_dictionary['topology']['SecurityConfiguration']['Realm']['myrealm'].has_key('PasswordValidator'), True)
211+
except (CompareException, PyWLSTException), te:
212+
return_code = 2
213+
self._logger.severe('WLSDPLY-05709',
214+
te.getLocalizedMessage(), error=te,
215+
class_name=self._program_name, method_name=_method_name)
216+
217+
if os.path.exists(_temp_dir):
218+
shutil.rmtree(_temp_dir)
219+
220+
self.assertEqual(return_code, 0)
221+
173222
def testCompareModelInvalidModel(self):
174223
_method_name = 'testCompareModelInvalidModel'
175224

core/src/test/resources/compare/model-a-new.yaml

Lines changed: 0 additions & 11 deletions
This file was deleted.

core/src/test/resources/compare/model-a-old.yaml

Lines changed: 0 additions & 18 deletions
This file was deleted.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright (c) 2021, Oracle and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
# Changing the value of ControlFlag should cause this entire
4+
# AuthenticationProvider section to be included in the compare result.
5+
# PasswordValidator providers are identical, this section should not be in the result.
6+
topology:
7+
SecurityConfiguration:
8+
Realm:
9+
myrealm:
10+
# Version 1.9 should be used fixing NPE
11+
AuthenticationProvider:
12+
DefaultAuthenticator:
13+
DefaultAuthenticator:
14+
ControlFlag: REQUIRED
15+
DefaultIdentityAsserter:
16+
DefaultIdentityAsserter:
17+
DefaultUserNameMapperAttributeType: CN
18+
ActiveType: [ 'AuthenticatedUser', 'X.509' ]
19+
DefaultUserNameMapperAttributeDelimiter: ','
20+
UseDefaultUserNameMapper: true
21+
PasswordValidator:
22+
SystemPasswordValidator:
23+
SystemPasswordValidator:
24+
MinAlphabeticCharacters: 1
25+
MinLowercaseCharacters: 1
26+
MinNumericCharacters: 1
27+
MaxConsecutiveCharacters: 2
28+
MinNonAlphanumericCharacters: 1
29+
MinUppercaseCharacters: 1
30+
RejectEqualOrContainUsername: true
31+
MinPasswordLength: 10
32+
RejectEqualOrContainReverseUsername: true
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Copyright (c) 2021, Oracle and/or its affiliates.
2+
# Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl.
3+
# Test to check default provider attribute changed in the new yaml causing the entire AuthenticationProvider
4+
# to be included in the diff
5+
# PasswordValidator providers are identical, this section should not be in the result.
6+
topology:
7+
SecurityConfiguration:
8+
Realm:
9+
myrealm:
10+
# Version 1.9 should be used fixing NPE
11+
AuthenticationProvider:
12+
DefaultAuthenticator:
13+
DefaultAuthenticator:
14+
ControlFlag: OPTIONAL
15+
DefaultIdentityAsserter:
16+
DefaultIdentityAsserter:
17+
DefaultUserNameMapperAttributeType: CN
18+
ActiveType: [ 'AuthenticatedUser', 'X.509' ]
19+
DefaultUserNameMapperAttributeDelimiter: ','
20+
UseDefaultUserNameMapper: true
21+
PasswordValidator:
22+
SystemPasswordValidator:
23+
SystemPasswordValidator:
24+
MinAlphabeticCharacters: 1
25+
MinLowercaseCharacters: 1
26+
MinNumericCharacters: 1
27+
MaxConsecutiveCharacters: 2
28+
MinNonAlphanumericCharacters: 1
29+
MinUppercaseCharacters: 1
30+
RejectEqualOrContainUsername: true
31+
MinPasswordLength: 10
32+
RejectEqualOrContainReverseUsername: true

0 commit comments

Comments
 (0)