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