001 /** 002 * Copyright 2005-2013 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.kim.impl.jaxb; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.joda.time.DateTime; 020 import org.kuali.rice.core.api.criteria.QueryByCriteria; 021 import org.kuali.rice.core.api.membership.MemberType; 022 import org.kuali.rice.core.util.jaxb.NameAndNamespacePair; 023 import org.kuali.rice.kim.api.group.GroupContract; 024 import org.kuali.rice.kim.api.identity.principal.PrincipalContract; 025 import org.kuali.rice.kim.api.permission.PermissionContract; 026 import org.kuali.rice.kim.api.role.Role; 027 import org.kuali.rice.kim.api.role.RoleContract; 028 import org.kuali.rice.kim.api.role.RoleMember; 029 import org.kuali.rice.kim.api.role.RoleMemberContract; 030 import org.kuali.rice.kim.api.role.RoleService; 031 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 032 033 import javax.xml.bind.UnmarshalException; 034 import java.util.HashMap; 035 import java.util.List; 036 import java.util.Set; 037 038 import static org.kuali.rice.core.api.criteria.PredicateFactory.equal; 039 040 /** 041 * Helper class containing static methods for aiding in parsing role XML. 042 * 043 * <p>All non-private methods are package-private so that only the KIM-parsing-related code can make use of them. (TODO: Is that necessary?) 044 * 045 * <p>TODO: Should this be converted into a service instead? 046 * 047 * @author Kuali Rice Team (rice.collab@kuali.org) 048 */ 049 public final class RoleXmlUtil { 050 // Do not allow outside code to instantiate this class. 051 private RoleXmlUtil() {} 052 053 /** 054 * Performs the necessary validation on the new role, then saves it. 055 * 056 * @param newRole The role to persist. 057 * @return The ID of the persisted role. 058 * @throws IllegalArgumentException if newRole is null. 059 * @throws UnmarshalException if newRole contains invalid data. 060 */ 061 static String validateAndPersistNewRole(RoleXmlDTO newRole) throws UnmarshalException { 062 if (newRole == null) { 063 throw new IllegalArgumentException("Cannot persist a null role"); 064 } 065 066 // Validate the role and (if applicable) retrieve the ID from an existing matching role. 067 validateAndPrepareRole(newRole); 068 069 Role.Builder builder = Role.Builder.create(); 070 builder.setActive(newRole.getActive()); 071 builder.setDescription(newRole.getRoleDescription()); 072 builder.setId(newRole.getRoleId()); 073 builder.setKimTypeId(newRole.getKimTypeId()); 074 builder.setName(newRole.getRoleName()); 075 builder.setNamespaceCode(newRole.getNamespaceCode()); 076 077 //save the role 078 Role role = KimApiServiceLocator.getRoleService().createRole(builder.build()); 079 080 // Set a flag on the role to indicate that it has now been persisted so that the unmarshalling process will not save this role more than once. 081 newRole.setAlreadyPersisted(true); 082 083 return role.getId(); 084 } 085 086 /** 087 * Performs the necessary validation on the new role member, then saves it. 088 * 089 * @param newRoleMember The role member to save. 090 * @return The ID of the persisted role member. 091 * @throws IllegalArgumentException if newRoleMember is null. 092 * @throws UnmarshalException if newRoleMember contains invalid data. 093 */ 094 static String validateAndPersistNewRoleMember(RoleMemberXmlDTO newRoleMember) throws UnmarshalException { 095 096 if (newRoleMember == null) { 097 throw new IllegalArgumentException("Cannot persist a null role member"); 098 } 099 100 // Validate role ID and role name/namespace. 101 validateRoleIdAndRoleNameForMember(newRoleMember); 102 103 // Validate member type, member ID, and member name/namespace. 104 validateMemberIdentity(newRoleMember); 105 106 // Validate the from/to dates, if defined. 107 if (newRoleMember.getActiveFromDate() != null && newRoleMember.getActiveToDate() != null && 108 newRoleMember.getActiveFromDate().compareTo(newRoleMember.getActiveToDate()) > 0) { 109 throw new UnmarshalException("Cannot create a role member whose activeFromDate occurs after its activeToDate"); 110 } 111 112 // Define defaults as needed. 113 if (newRoleMember.getQualifications() == null) { 114 newRoleMember.setQualifications(new HashMap<String, String>()); 115 } 116 117 RoleMember.Builder builder = RoleMember.Builder.create(newRoleMember.getRoleId(), newRoleMember.getRoleIdAsMember(), 118 newRoleMember.getMemberId(), newRoleMember.getMemberType(), 119 newRoleMember.getActiveFromDate() == null ? null : new DateTime(newRoleMember.getActiveFromDate().getMillis()), 120 newRoleMember.getActiveToDate() == null ? null : new DateTime(newRoleMember.getActiveToDate().getMillis()), 121 newRoleMember.getQualifications(),newRoleMember.getMemberName(),newRoleMember.getMemberNamespaceCode()); 122 123 // Save the role member. 124 RoleMemberContract newMember = KimApiServiceLocator.getRoleService().createRoleMember(builder.build()); 125 126 return newMember.getId(); 127 } 128 129 /** 130 * Performs the necessary validation on the role permission, then saves it. 131 * 132 * @param newRolePermission The role permission to save. 133 * @throws IllegalArgumentException if newRolePermission is null 134 * @throws UnmarshalException if newRolePermission contains invalid data. 135 */ 136 static void validateAndPersistNewRolePermission(RolePermissionXmlDTO newRolePermission) throws UnmarshalException { 137 if (newRolePermission == null) { 138 throw new IllegalArgumentException("newRolePermission cannot be null"); 139 } 140 141 // Validate the role permission, and prepare its role ID if necessary. 142 validateAndPrepareRolePermission(newRolePermission); 143 144 // Save the role permission. 145 KimApiServiceLocator.getRoleService().assignPermissionToRole(newRolePermission.getPermissionId(), newRolePermission.getRoleId()); 146 } 147 148 /** 149 * Removes any role members for a given role whose IDs are not listed in a given role member ID set. 150 * 151 * @param roleId The ID of the role containing the role members. 152 * @param existingRoleMemberIds The IDs of the role members that should not be removed. 153 * @throws IllegalArgumentException if roleId is blank or refers to a non-existent role, or if existingRoleMemberIds is null. 154 */ 155 static void removeRoleMembers(String roleId, Set<String> existingRoleMemberIds) { 156 if (StringUtils.isBlank(roleId)) { 157 throw new IllegalArgumentException("roleId cannot be blank"); 158 } else if (existingRoleMemberIds == null) { 159 throw new IllegalArgumentException("existingRoleMemberIds cannot be null"); 160 } 161 RoleService roleUpdateService = KimApiServiceLocator.getRoleService(); 162 RoleContract role = KimApiServiceLocator.getRoleService().getRole(roleId); 163 if (role == null) { 164 throw new IllegalArgumentException("Cannot remove role members for role with ID \"" + roleId + "\" because that role does not exist"); 165 } 166 167 // Remove any role members whose IDs are not in the set. 168 List<RoleMember> roleMembers = KimApiServiceLocator.getRoleService().findRoleMembers( 169 QueryByCriteria.Builder.fromPredicates(equal("roleId", roleId))).getResults(); 170 if (roleMembers != null && !roleMembers.isEmpty()) { 171 for (RoleMemberContract roleMember : roleMembers) { 172 if (!existingRoleMemberIds.contains(roleMember.getId())) { 173 // If the role member needs to be removed, use the member type code to determine which removal method to call. 174 MemberType memberType = roleMember.getType(); 175 if (MemberType.PRINCIPAL.equals(memberType)) { 176 roleUpdateService.removePrincipalFromRole(roleMember.getMemberId(), role.getNamespaceCode(), role.getName(), 177 (roleMember.getAttributes() != null) ? roleMember.getAttributes() : new HashMap<String, String>()); 178 } else if (MemberType.GROUP.equals(memberType)) { 179 roleUpdateService.removeGroupFromRole(roleMember.getMemberId(), role.getNamespaceCode(), role.getName(), 180 (roleMember.getAttributes() != null) ? roleMember.getAttributes() :new HashMap<String, String>()); 181 } else if (MemberType.ROLE.equals(memberType)) { 182 roleUpdateService.removeRoleFromRole(roleMember.getMemberId(), role.getNamespaceCode(), role.getName(), 183 (roleMember.getAttributes() != null) ? roleMember.getAttributes() : new HashMap<String, String>()); 184 } 185 } 186 } 187 } 188 } 189 190 /** 191 * Validates a new role's name, namespace, KIM type, and description, and sets the role's ID if the name and namespace match an existing role. 192 */ 193 private static void validateAndPrepareRole(RoleXmlDTO newRole) throws UnmarshalException { 194 // Ensure that the role name, role namespace, KIM type, and description have all been specified. 195 if (StringUtils.isBlank(newRole.getRoleName()) || StringUtils.isBlank(newRole.getNamespaceCode())) { 196 throw new UnmarshalException("Cannot create or override a role with a blank name or a blank namespace"); 197 } else if (StringUtils.isBlank(newRole.getKimTypeId())) { 198 throw new UnmarshalException("Cannot create or override a role without specikfying a KIM type"); 199 } else if (StringUtils.isBlank(newRole.getRoleDescription())) { 200 throw new UnmarshalException("Cannot create or override a role with a blank description"); 201 } 202 203 // Attempt to find an existing matching role, and assign its ID to the validated role if it exists. 204 String matchingId = KimApiServiceLocator.getRoleService().getRoleIdByNamespaceCodeAndName( 205 newRole.getNamespaceCode(), newRole.getRoleName()); 206 if (StringUtils.isNotBlank(matchingId)) { 207 newRole.setRoleId(matchingId); 208 } 209 } 210 211 /** 212 * Validates a new role member's role ID, role name, and role namespace. 213 */ 214 private static void validateRoleIdAndRoleNameForMember(RoleMemberXmlDTO newRoleMember) throws UnmarshalException { 215 // If the "roleMember" tag was not a descendant of a "role" tag, derive and validate its role information accordingly. 216 if (newRoleMember instanceof RoleMemberXmlDTO.OutsideOfRole) { 217 RoleMemberXmlDTO.OutsideOfRole standaloneMember = (RoleMemberXmlDTO.OutsideOfRole) newRoleMember; 218 if (standaloneMember.getRoleNameAndNamespace() != null) { 219 // If a name + namespace combo is given, verify that the combo maps to an existing role. 220 String existingId = KimApiServiceLocator.getRoleService().getRoleIdByNamespaceCodeAndName( 221 standaloneMember.getRoleNamespaceCode(), standaloneMember.getRoleName()); 222 if (StringUtils.isBlank(existingId)) { 223 throw new UnmarshalException("Cannot create role member for role with name \"" + standaloneMember.getRoleName() + "\" and namespace \"" + 224 standaloneMember.getRoleNamespaceCode() + "\" because such a role does not exist"); 225 } 226 227 // If the role member defines its own role ID, verify that it's the same as the one from the existing role; otherwise, assign the member's role ID. 228 if (StringUtils.isBlank(standaloneMember.getRoleId())) { 229 standaloneMember.setRoleId(existingId); 230 } else if (!standaloneMember.getRoleId().equals(existingId)) { 231 throw new UnmarshalException("Cannot create role member for role with ID \"" + standaloneMember.getRoleId() + "\", name \"" + 232 standaloneMember.getRoleName() + "\", and namespace \"" + standaloneMember.getRoleNamespaceCode() + 233 "\" because the existing role with the same name and namespace has an ID of \"" + existingId + "\" instead"); 234 } 235 } else if (StringUtils.isBlank(standaloneMember.getRoleId())) { 236 throw new UnmarshalException("Cannot create role member without providing the role ID or role name + namespace that the member belongs to"); 237 } else if (KimApiServiceLocator.getRoleService().getRole(standaloneMember.getRoleId()) == null) { 238 throw new UnmarshalException("Cannot create role member for the role with ID \"" + standaloneMember.getRoleId() + "\" because that role does not exist"); 239 } 240 } 241 242 // Ensure that a role ID was explicitly defined or was derived from a name + namespace combo. 243 if (StringUtils.isBlank(newRoleMember.getRoleId())) { 244 throw new UnmarshalException("Cannot create role member without providing the role ID or role name + namespace that the member belongs to"); 245 } 246 } 247 248 /** 249 * Validates a new role member's member type, member ID, member name, and (if applicable) member namespace code. 250 */ 251 private static void validateMemberIdentity(RoleMemberXmlDTO newRoleMember) throws UnmarshalException { 252 // Ensure that sufficient and non-conflicting membership info has been set. (The getMemberTypeCode() method performs such validation.) 253 MemberType memberType = newRoleMember.getMemberType(); 254 if (memberType == null) { 255 throw new UnmarshalException("Cannot create a role member with no member principal/group/role identification information specified"); 256 } 257 258 // Ensure that a valid member ID was specified, if present. 259 if (StringUtils.isNotBlank(newRoleMember.getMemberId())) { 260 if (MemberType.PRINCIPAL.equals(memberType)) { 261 // If the member is a principal, ensure that the principal exists. 262 if (KimApiServiceLocator.getIdentityService().getPrincipal(newRoleMember.getPrincipalId()) == null) { 263 throw new UnmarshalException("Cannot create principal role member with principal ID \"" + 264 newRoleMember.getPrincipalId() + "\" because such a person does not exist"); 265 } 266 } else if (MemberType.GROUP.equals(memberType)) { 267 // If the member is a group, ensure that the group exists. 268 if (KimApiServiceLocator.getGroupService().getGroup(newRoleMember.getGroupId()) == null) { 269 throw new UnmarshalException("Cannot create group role member with group ID \"" + 270 newRoleMember.getGroupId() + "\" because such a group does not exist"); 271 } 272 } else if (MemberType.ROLE.equals(memberType)) { 273 // If the member is another role, ensure that the role exists and that the role is not trying to become a member of itself. 274 if (newRoleMember.getRoleId().equals(newRoleMember.getRoleIdAsMember())) { 275 throw new UnmarshalException("The role with ID \"" + newRoleMember.getRoleIdAsMember() + "\" cannot be made a member of itself"); 276 } else if (KimApiServiceLocator.getRoleService().getRole(newRoleMember.getRoleIdAsMember()) == null) { 277 throw new UnmarshalException("Cannot use role with ID \"" + newRoleMember.getRoleIdAsMember() + 278 "\" as a role member because such a role does not exist"); 279 } 280 } 281 } 282 283 // Ensure that a valid member name (and namespace, if applicable) was specified, if present. 284 if (StringUtils.isNotBlank(newRoleMember.getMemberName())) { 285 if (MemberType.PRINCIPAL.equals(memberType)) { 286 //If the member is a principal, ensure that the principal exists and does not conflict with any existing principal ID information. 287 PrincipalContract tempPrincipal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(newRoleMember.getPrincipalName()); 288 if (tempPrincipal == null) { 289 throw new UnmarshalException("Cannot create principal role member with principal name \"" + 290 newRoleMember.getPrincipalName() + "\" because such a person does not exist"); 291 } else if (StringUtils.isBlank(newRoleMember.getPrincipalId())) { 292 // If no principal ID was given, assign one from the retrieved principal. 293 newRoleMember.setPrincipalId(tempPrincipal.getPrincipalId()); 294 } else if (!newRoleMember.getPrincipalId().equals(tempPrincipal.getPrincipalId())) { 295 throw new UnmarshalException("Cannot create principal role member with principal ID \"" + newRoleMember.getPrincipalId() + 296 "\" and principal name \"" + newRoleMember.getPrincipalName() + "\" because the principal with that name has an ID of \"" + 297 tempPrincipal.getPrincipalId() + "\" instead"); 298 } 299 } else if (MemberType.GROUP.equals(memberType)) { 300 // If the member is a group, ensure that the group exists and does not conflict with any existing group ID information. 301 NameAndNamespacePair groupNameAndNamespace = newRoleMember.getGroupName(); 302 GroupContract tempGroup = KimApiServiceLocator.getGroupService().getGroupByNamespaceCodeAndName( 303 groupNameAndNamespace.getNamespaceCode(), groupNameAndNamespace.getName()); 304 if (tempGroup == null) { 305 throw new UnmarshalException("Cannot create group role member with namespace \"" + groupNameAndNamespace.getNamespaceCode() + 306 "\" and name \"" + groupNameAndNamespace.getName() + "\" because such a group does not exist"); 307 } else if (StringUtils.isBlank(newRoleMember.getGroupId())) { 308 // If no group ID was given, assign one from the retrieved group. 309 newRoleMember.setGroupId(tempGroup.getId()); 310 } else if (!newRoleMember.getGroupId().equals(tempGroup.getId())) { 311 throw new UnmarshalException("Cannot create group role member with ID \"" + newRoleMember.getGroupId() + "\", namespace \"" + 312 groupNameAndNamespace.getNamespaceCode() + "\", and name \"" + groupNameAndNamespace.getName() + 313 "\" because the group with that namespace and name has an ID of \"" + tempGroup.getId() + "\" instead"); 314 } 315 } else if (MemberType.ROLE.equals(memberType)) { 316 // If the member is another role, ensure that the role exists, does not conflict with any existing role ID information, and is not the member's role. 317 NameAndNamespacePair roleNameAndNamespace = newRoleMember.getRoleNameAsMember(); 318 RoleContract tempRole = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName( 319 roleNameAndNamespace.getNamespaceCode(), roleNameAndNamespace.getName()); 320 if (tempRole == null) { 321 throw new UnmarshalException("Cannot use role with namespace \"" + roleNameAndNamespace.getNamespaceCode() + 322 "\" and name \"" + roleNameAndNamespace.getName() + "\" as a role member because such a role does not exist"); 323 } else if (newRoleMember.getRoleId().equals(tempRole.getId())) { 324 throw new UnmarshalException("The role with namespace \"" + roleNameAndNamespace.getNamespaceCode() + 325 "\" and name \"" + roleNameAndNamespace.getName() + "\" cannot be made a member of itself"); 326 } else if (StringUtils.isBlank(newRoleMember.getRoleId())) { 327 // If no role ID was given, assign one from the retrieved role. 328 newRoleMember.setRoleIdAsMember(tempRole.getId()); 329 } else if (!newRoleMember.getRoleId().equals(tempRole.getId())) { 330 throw new RuntimeException("Cannot use role with ID \"" + newRoleMember.getRoleId() + "\", namespace \"" + 331 roleNameAndNamespace.getNamespaceCode() + "\", and name \"" + roleNameAndNamespace.getName() + 332 "\" as a role member because the role with that namespace and name has an ID of \"" + 333 tempRole.getId() + "\" instead"); 334 } 335 } 336 } 337 338 // Ensure that a member ID was either explicitly defined or was derived from the member name (and namespace, if applicable). 339 if (StringUtils.isBlank(newRoleMember.getMemberId())) { 340 throw new RuntimeException("Cannot create a role member with no member principal/group/role identification information specified"); 341 } 342 343 } 344 345 /** 346 * Validates a role permission's role and permission identification information, and assigns its role ID if needed. 347 */ 348 private static void validateAndPrepareRolePermission(RolePermissionXmlDTO newRolePermission) throws UnmarshalException { 349 350 // If this is a standalone role permission, derive and validate its role information accordingly. 351 if (newRolePermission instanceof RolePermissionXmlDTO.OutsideOfRole) { 352 RolePermissionXmlDTO.OutsideOfRole standaloneRolePerm = (RolePermissionXmlDTO.OutsideOfRole) newRolePermission; 353 if (standaloneRolePerm.getRoleNameAndNamespace() != null) { 354 // If a role name + namespace is given, assign or validate the role ID accordingly. 355 String tempRoleId = KimApiServiceLocator.getRoleService().getRoleIdByNamespaceCodeAndName( 356 standaloneRolePerm.getRoleNamespaceCode(), standaloneRolePerm.getRoleName()); 357 if (StringUtils.isBlank(tempRoleId)) { 358 throw new UnmarshalException("Cannot assign permission to role with namespace \"" + standaloneRolePerm.getRoleNamespaceCode() + 359 "\" and name \"" + standaloneRolePerm.getRoleName() + "\" because that role does not exist"); 360 } else if (StringUtils.isBlank(standaloneRolePerm.getRoleId())) { 361 // If no role ID was given, assign one from the retrieved role. 362 standaloneRolePerm.setRoleId(standaloneRolePerm.getRoleId()); 363 } else if (!standaloneRolePerm.getRoleId().equals(tempRoleId)) { 364 throw new UnmarshalException("Cannot assign permission to role with ID \"" + standaloneRolePerm.getRoleId() + "\", namespace \"" + 365 standaloneRolePerm.getRoleNamespaceCode() + "\", and name \"" + standaloneRolePerm.getRoleName() + 366 "\" because the existing role with that name and namespace has an ID of \"" + tempRoleId + "\" instead"); 367 } 368 } else if (StringUtils.isBlank(standaloneRolePerm.getRoleId())) { 369 throw new UnmarshalException( 370 "Cannot assign permission to role without providing the role ID or role name + namespace that the permission is assigned to"); 371 } else if (KimApiServiceLocator.getRoleService().getRole(standaloneRolePerm.getRoleId()) == null) { 372 throw new UnmarshalException("Cannot assign permission to role with ID \"" + standaloneRolePerm.getRoleId() + 373 "\" because that role does not exist"); 374 } 375 } 376 377 // Ensure that a role ID was explicitly defined or was derived from a name + namespace combo. 378 if (StringUtils.isBlank(newRolePermission.getRoleId())) { 379 throw new UnmarshalException("Cannot assign permission to role without providing the role ID or role name + namespace that the permission is assigned to"); 380 } 381 382 // If the permission is being identified by name and namespace, derive or validate its permission ID accordingly. 383 if (newRolePermission.getPermissionNameAndNamespace() != null) { 384 PermissionContract permission = KimApiServiceLocator.getPermissionService().findPermByNamespaceCodeAndName( 385 newRolePermission.getPermissionNamespaceCode(), newRolePermission.getPermissionName()); 386 if (permission == null) { 387 throw new UnmarshalException("Cannot get role assigned to permission with namespace \"" + newRolePermission.getPermissionNamespaceCode() + 388 "\" and name \"" + newRolePermission.getPermissionName() + "\" because that permission does not exist"); 389 } else if (StringUtils.isBlank(newRolePermission.getPermissionId())) { 390 // If no permission ID was given, assign one from the retrieved permission. 391 newRolePermission.setPermissionId(permission.getId()); 392 } else if (!newRolePermission.getPermissionId().equals(permission.getId())) { 393 throw new UnmarshalException("Cannot get role assigned to permission with ID \"" + newRolePermission.getPermissionId() + "\", namespace \"" + 394 newRolePermission.getPermissionNamespaceCode() + "\", and name \"" + newRolePermission.getPermissionName() + 395 "\" because the existing permission with that name and namespace has an ID of \"" + permission.getId() + "\" instead"); 396 } 397 } else if (StringUtils.isBlank(newRolePermission.getPermissionId())) { 398 throw new UnmarshalException("Cannot assign permission to role without specifying the ID or name and namespace of the permission to assign"); 399 } else if (KimApiServiceLocator.getPermissionService().getPermission(newRolePermission.getPermissionId()) == null) { 400 throw new UnmarshalException("Cannot get role assigned to permission with ID \"" + newRolePermission.getPermissionId() + 401 "\" because that permission does not exist"); 402 } 403 } 404 405 }