View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
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,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kim.impl.jaxb;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.joda.time.DateTime;
20  import org.kuali.rice.core.api.criteria.QueryByCriteria;
21  import org.kuali.rice.core.api.membership.MemberType;
22  import org.kuali.rice.core.util.jaxb.NameAndNamespacePair;
23  import org.kuali.rice.kim.api.group.GroupContract;
24  import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
25  import org.kuali.rice.kim.api.permission.PermissionContract;
26  import org.kuali.rice.kim.api.role.Role;
27  import org.kuali.rice.kim.api.role.RoleContract;
28  import org.kuali.rice.kim.api.role.RoleMember;
29  import org.kuali.rice.kim.api.role.RoleMemberContract;
30  import org.kuali.rice.kim.api.role.RoleService;
31  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
32  
33  import javax.xml.bind.UnmarshalException;
34  import java.util.HashMap;
35  import java.util.List;
36  import java.util.Set;
37  
38  import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
39  
40  /**
41   * Helper class containing static methods for aiding in parsing role XML.
42   * 
43   * <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?)
44   * 
45   * <p>TODO: Should this be converted into a service instead?
46   * 
47   * @author Kuali Rice Team (rice.collab@kuali.org)
48   */
49  public final class RoleXmlUtil {
50      // Do not allow outside code to instantiate this class.
51      private RoleXmlUtil() {}
52  
53      /**
54       * Performs the necessary validation on the new role, then saves it.
55       * 
56       * @param newRole The role to persist.
57       * @return The ID of the persisted role.
58       * @throws IllegalArgumentException if newRole is null.
59       * @throws UnmarshalException if newRole contains invalid data.
60       */
61      static String validateAndPersistNewRole(RoleXmlDTO newRole) throws UnmarshalException {
62          if (newRole == null) {
63              throw new IllegalArgumentException("Cannot persist a null role");
64          }
65          
66          // Validate the role and (if applicable) retrieve the ID from an existing matching role.
67          validateAndPrepareRole(newRole);
68  
69          Role.Builder builder = Role.Builder.create();
70          builder.setActive(newRole.getActive());
71          builder.setDescription(newRole.getRoleDescription());
72          builder.setId(newRole.getRoleId());
73          builder.setKimTypeId(newRole.getKimTypeId());
74          builder.setName(newRole.getRoleName());
75          builder.setNamespaceCode(newRole.getNamespaceCode());
76  
77          //save the role
78          Role role = KimApiServiceLocator.getRoleService().createRole(builder.build());
79  
80          // 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.
81          newRole.setAlreadyPersisted(true);
82          
83          return role.getId();
84      }
85      
86      /**
87       * Performs the necessary validation on the new role member, then saves it.
88       * 
89       * @param newRoleMember The role member to save.
90       * @return The ID of the persisted role member.
91       * @throws IllegalArgumentException if newRoleMember is null.
92       * @throws UnmarshalException if newRoleMember contains invalid data.
93       */
94      static String validateAndPersistNewRoleMember(RoleMemberXmlDTO newRoleMember) throws UnmarshalException {
95          
96          if (newRoleMember == null) {
97              throw new IllegalArgumentException("Cannot persist a null role member");
98          }
99          
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 }