|
| 1 | +""" |
| 2 | +Copyright (c) 2020, Oracle Corporation and/or its affiliates. |
| 3 | +Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl. |
| 4 | +""" |
| 5 | +import com.bea.common.security.utils.encoders.BASE64Encoder as BASE64Encoder |
| 6 | +import com.bea.security.xacml.cache.resource.ResourcePolicyIdUtil as ResourcePolicyIdUtil |
| 7 | +from java.io import File |
| 8 | +from java.lang import String |
| 9 | +from oracle.weblogic.deploy.aliases import TypeUtils |
| 10 | + |
| 11 | +from wlsdeploy.aliases.model_constants import CROSS_DOMAIN |
| 12 | +from wlsdeploy.aliases.model_constants import METHOD |
| 13 | +from wlsdeploy.aliases.model_constants import PATH |
| 14 | +from wlsdeploy.aliases.model_constants import PROTOCOL |
| 15 | +from wlsdeploy.aliases.model_constants import REMOTE_DOMAIN |
| 16 | +from wlsdeploy.aliases.model_constants import REMOTE_HOST |
| 17 | +from wlsdeploy.aliases.model_constants import REMOTE_PASSWORD |
| 18 | +from wlsdeploy.aliases.model_constants import REMOTE_PORT |
| 19 | +from wlsdeploy.aliases.model_constants import REMOTE_USER |
| 20 | +from wlsdeploy.aliases.model_constants import USER |
| 21 | +from wlsdeploy.exception import exception_helper |
| 22 | +from wlsdeploy.logging.platform_logger import PlatformLogger |
| 23 | +from wlsdeploy.tool.util.targets import file_template_helper |
| 24 | +from wlsdeploy.util import dictionary_utils |
| 25 | +from wlsdeploy.util.weblogic_helper import WebLogicHelper |
| 26 | + |
| 27 | +DEFAULT_MAPPER_INIT_FILE = 'DefaultCredentialMapperInit.ldift' |
| 28 | +SECURITY_SUBDIR = 'security' |
| 29 | +TEMPLATE_PATH = 'oracle/weblogic/deploy/security' |
| 30 | + |
| 31 | +CREDENTIAL_MAPPINGS = 'credentialMappings' |
| 32 | +RESOURCE_MAPPINGS = 'resourceMappings' |
| 33 | + |
| 34 | +CROSS_DOMAIN_PROTOCOL = 'cross-domain-protocol' |
| 35 | +CROSS_DOMAIN_USER = 'cross-domain' |
| 36 | +NULL = 'null' |
| 37 | + |
| 38 | +# template hash constants |
| 39 | +HASH_CREDENTIAL_CN = 'credentialCn' |
| 40 | +HASH_LOCAL_USER = 'localUser' |
| 41 | +HASH_PASSWORD_ENCODED = 'passwordEncoded' |
| 42 | +HASH_REMOTE_USER = 'remoteUser' |
| 43 | +HASH_RESOURCE_CN = 'resourceCn' |
| 44 | +HASH_RESOURCE_NAME = 'resourceName' |
| 45 | + |
| 46 | +# keys in resource name |
| 47 | +ID_METHOD = 'method' |
| 48 | +ID_PATH = 'path' |
| 49 | +ID_PROTOCOL = 'protocol' |
| 50 | +ID_REMOTE_HOST = 'remoteHost' |
| 51 | +ID_REMOTE_PORT = 'remotePort' |
| 52 | + |
| 53 | +# ordered to match resource name output |
| 54 | +RESOURCE_FIELDS = [ |
| 55 | + ID_PROTOCOL, |
| 56 | + ID_REMOTE_HOST, |
| 57 | + ID_REMOTE_PORT, |
| 58 | + ID_PATH, |
| 59 | + ID_METHOD |
| 60 | +] |
| 61 | + |
| 62 | + |
| 63 | +class CredentialMapHelper(object): |
| 64 | + """ |
| 65 | + Creates .ldift initialization file for user/password credential mappings |
| 66 | + """ |
| 67 | + _class_name = 'CredentialMapHelper' |
| 68 | + |
| 69 | + def __init__(self, model_context, exception_type): |
| 70 | + """ |
| 71 | + Initialize an instance of CredentialMapHelper. |
| 72 | + :param model_context: used to find domain home |
| 73 | + :param exception_type: the type of exception to be thrown |
| 74 | + """ |
| 75 | + self._model_context = model_context |
| 76 | + self._exception_type = exception_type |
| 77 | + self._logger = PlatformLogger('wlsdeploy.tool.util') |
| 78 | + self._weblogic_helper = WebLogicHelper(self._logger) |
| 79 | + self._resource_escaper = ResourcePolicyIdUtil.getEscaper() |
| 80 | + self._b64_encoder = BASE64Encoder() |
| 81 | + |
| 82 | + def create_default_init_file(self, default_mapping_nodes): |
| 83 | + """ |
| 84 | + Create a .ldift file to initialize default credential mappers. |
| 85 | + Build a hash map for use with a template file resource. |
| 86 | + Write the file to a known location in the domain. |
| 87 | + :param default_mapping_nodes: the credential mapping elements from the model |
| 88 | + """ |
| 89 | + _method_name = 'create_default_init_file' |
| 90 | + |
| 91 | + template_hash = self._build_default_template_hash(default_mapping_nodes) |
| 92 | + template_path = TEMPLATE_PATH + '/' + DEFAULT_MAPPER_INIT_FILE |
| 93 | + |
| 94 | + output_dir = File(self._model_context.get_domain_home(), SECURITY_SUBDIR) |
| 95 | + output_file = File(output_dir, DEFAULT_MAPPER_INIT_FILE) |
| 96 | + |
| 97 | + self._logger.info('WLSDPLY-01790', output_file, class_name=self._class_name, method_name=_method_name) |
| 98 | + |
| 99 | + file_template_helper.create_file(template_path, template_hash, output_file, self._exception_type) |
| 100 | + |
| 101 | + def _build_default_template_hash(self, mapping_section_nodes): |
| 102 | + """ |
| 103 | + Create a dictionary of substitution values to apply to the default credential mappers template. |
| 104 | + :param mapping_section_nodes: the credential mapping elements from the model |
| 105 | + :return: the template hash dictionary |
| 106 | + """ |
| 107 | + template_hash = dict() |
| 108 | + |
| 109 | + credential_mappings = [] |
| 110 | + resource_mappings = [] |
| 111 | + |
| 112 | + for mapping_type in mapping_section_nodes.keys(): |
| 113 | + mapping_name_nodes = mapping_section_nodes[mapping_type] |
| 114 | + for mapping_name in mapping_name_nodes.keys(): |
| 115 | + mapping = mapping_name_nodes[mapping_name] |
| 116 | + mapping_hash = self._build_mapping_hash(mapping_type, mapping_name, mapping) |
| 117 | + |
| 118 | + # add a hash with remote target details to create a single passwordCredentialMap element |
| 119 | + credential_mappings.append(mapping_hash) |
| 120 | + |
| 121 | + # add a modified hash for each local user to create resourceMap elements |
| 122 | + resource_name = mapping_hash[HASH_RESOURCE_NAME] |
| 123 | + local_users = self._get_local_users(mapping_type, mapping_name, mapping) |
| 124 | + for local_user in local_users: |
| 125 | + resource_hash = dict(mapping_hash) |
| 126 | + resource_hash[HASH_LOCAL_USER] = local_user |
| 127 | + resource_hash[HASH_RESOURCE_CN] = self._create_cn(resource_name, local_user) |
| 128 | + resource_mappings.append(resource_hash) |
| 129 | + |
| 130 | + template_hash[CREDENTIAL_MAPPINGS] = credential_mappings |
| 131 | + template_hash[RESOURCE_MAPPINGS] = resource_mappings |
| 132 | + return template_hash |
| 133 | + |
| 134 | + def _build_mapping_hash(self, mapping_type, mapping_name, mapping): |
| 135 | + """ |
| 136 | + Build a template hash for the specified mapping element from the model. |
| 137 | + :param mapping_type: the type of the mapping, such as 'CrossDomain' |
| 138 | + :param mapping_name: the mapping name from the model, such as 'map1' |
| 139 | + :param mapping: the mapping element from the model |
| 140 | + :return: the template hash |
| 141 | + """ |
| 142 | + resource_name = self._build_resource_name(mapping_type, mapping_name, mapping) |
| 143 | + |
| 144 | + remote_user = self._get_required_attribute(mapping, REMOTE_USER, mapping_type, mapping_name) |
| 145 | + credential_cn = self._create_cn(resource_name, remote_user) |
| 146 | + |
| 147 | + # the password needs to be encrypted, then base64 encoded |
| 148 | + password = self._get_required_attribute(mapping, REMOTE_PASSWORD, mapping_type, mapping_name) |
| 149 | + encrypted = self._weblogic_helper.encrypt(password, self._model_context.get_domain_home()) |
| 150 | + password_encoded = self._b64_encoder.encodeBuffer(String(encrypted).getBytes("UTF-8")) |
| 151 | + |
| 152 | + # the local user and resource CN will be updated later for each user |
| 153 | + return { |
| 154 | + HASH_CREDENTIAL_CN: credential_cn, |
| 155 | + HASH_LOCAL_USER: NULL, |
| 156 | + HASH_PASSWORD_ENCODED: password_encoded, |
| 157 | + HASH_REMOTE_USER: remote_user, |
| 158 | + HASH_RESOURCE_CN: NULL, |
| 159 | + HASH_RESOURCE_NAME: resource_name |
| 160 | + } |
| 161 | + |
| 162 | + def _build_resource_name(self, mapping_type, mapping_name, mapping): |
| 163 | + """ |
| 164 | + Build the resource name based on elements in the mapping element from the model. |
| 165 | + Example: type=<remote>, protocol=http, remoteHost=my.host, remotePort=7020, path=/myapp, method=POST |
| 166 | + :param mapping_type: the type of the mapping, such as 'CrossDomain' |
| 167 | + :param mapping_name: the mapping name from the model, such as 'map1' |
| 168 | + :param mapping: the mapping element from the model |
| 169 | + :return: the resource name |
| 170 | + """ |
| 171 | + |
| 172 | + # for cross-domain mapping, use domain for remote host, and set cross-domain protocol |
| 173 | + if mapping_type == CROSS_DOMAIN: |
| 174 | + remote_host = self._get_required_attribute(mapping, REMOTE_DOMAIN, mapping_type, mapping_name) |
| 175 | + protocol = CROSS_DOMAIN_PROTOCOL |
| 176 | + else: |
| 177 | + remote_host = self._get_required_attribute(mapping, REMOTE_HOST, mapping_type, mapping_name) |
| 178 | + protocol = dictionary_utils.get_element(mapping, PROTOCOL) |
| 179 | + |
| 180 | + # build a map of available values, some may be None |
| 181 | + resource_name_values = { |
| 182 | + ID_METHOD: dictionary_utils.get_element(mapping, METHOD), |
| 183 | + ID_PATH: dictionary_utils.get_element(mapping, PATH), |
| 184 | + ID_PROTOCOL: protocol, |
| 185 | + ID_REMOTE_HOST: remote_host, |
| 186 | + ID_REMOTE_PORT: dictionary_utils.get_element(mapping, REMOTE_PORT) |
| 187 | + } |
| 188 | + |
| 189 | + # build the resource name string |
| 190 | + resource_name = 'type=<remote>' |
| 191 | + for field in RESOURCE_FIELDS: |
| 192 | + value = dictionary_utils.get_element(resource_name_values, field) |
| 193 | + if value is not None: |
| 194 | + resource_name += ', %s=%s' % (field, value) |
| 195 | + |
| 196 | + return resource_name |
| 197 | + |
| 198 | + def _get_local_users(self, mapping_type, mapping_name, mapping): |
| 199 | + """ |
| 200 | + Get the local users list, based on the mapping element from the model. |
| 201 | + :param mapping_type: the type of the mapping, such as 'CrossDomain' |
| 202 | + :param mapping_name: the mapping name from the model, such as 'map1' |
| 203 | + :param mapping: the mapping element from the model |
| 204 | + :return: a list of local users |
| 205 | + """ |
| 206 | + if mapping_type == CROSS_DOMAIN: |
| 207 | + return [CROSS_DOMAIN_USER] |
| 208 | + |
| 209 | + local_user_value = self._get_required_attribute(mapping, USER, mapping_type, mapping_name) |
| 210 | + return TypeUtils.convertToType(list, local_user_value) |
| 211 | + |
| 212 | + def _get_required_attribute(self, dictionary, name, mapping_type, mapping_name): |
| 213 | + """ |
| 214 | + Return the value of the specified attribute from the specified dictionary. |
| 215 | + Log and throw an exception if the attribute is not found. |
| 216 | + :param dictionary: the dictionary to be checked |
| 217 | + :param name: the name of the attribute to find |
| 218 | + :param mapping_type: the type of the mapping, such as 'CrossDomain' |
| 219 | + :param mapping_name: the mapping name from the model, such as 'map1' |
| 220 | + :return: the value of the attribute |
| 221 | + :raises: Tool type exception: if an the attribute is not found |
| 222 | + """ |
| 223 | + _method_name = '_get_required_attribute' |
| 224 | + |
| 225 | + result = dictionary_utils.get_element(dictionary, name) |
| 226 | + if result is None: |
| 227 | + pwe = exception_helper.create_exception(self._exception_type, 'WLSDPLY-01791', name, mapping_type, |
| 228 | + mapping_name) |
| 229 | + self._logger.throwing(class_name=self._class_name, method_name=_method_name, error=pwe) |
| 230 | + raise pwe |
| 231 | + return result |
| 232 | + |
| 233 | + def _create_cn(self, resource_name, user): |
| 234 | + """ |
| 235 | + Create a CN string from the specified resource name and user name. |
| 236 | + The result should be escaped for use as a CN. |
| 237 | + :param resource_name: the name of the resource |
| 238 | + :param user: the user name |
| 239 | + :return: the CN string |
| 240 | + """ |
| 241 | + name = resource_name + "." + user |
| 242 | + return self._resource_escaper.escapeString(name) |
0 commit comments