View Javadoc
1   /*
2    * The Kuali Financial System, a comprehensive financial management system for higher education.
3    * 
4    * Copyright 2005-2014 The Kuali Foundation
5    * 
6    * This program is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Affero General Public License as
8    * published by the Free Software Foundation, either version 3 of the
9    * License, or (at your option) any later version.
10   * 
11   * This program is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Affero General Public License for more details.
15   * 
16   * You should have received a copy of the GNU Affero General Public License
17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package org.kuali.rice.kim.impl.jaxb;
20  
21  import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
22  
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Set;
26  
27  import javax.xml.bind.UnmarshalException;
28  
29  import org.apache.commons.lang.StringUtils;
30  import org.joda.time.DateTime;
31  import org.kuali.rice.core.api.criteria.QueryByCriteria;
32  import org.kuali.rice.core.api.membership.MemberType;
33  import org.kuali.rice.core.util.jaxb.NameAndNamespacePair;
34  import org.kuali.rice.kim.api.group.GroupContract;
35  import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
36  import org.kuali.rice.kim.api.permission.PermissionContract;
37  import org.kuali.rice.kim.api.role.Role;
38  import org.kuali.rice.kim.api.role.RoleContract;
39  import org.kuali.rice.kim.api.role.RoleMember;
40  import org.kuali.rice.kim.api.role.RoleMemberContract;
41  import org.kuali.rice.kim.api.role.RoleService;
42  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
43  
44  /**
45   * Helper class containing static methods for aiding in parsing role XML.
46   *
47   * <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?)
48   *
49   * <p>TODO: Should this be converted into a service instead?
50   *
51   * @author Kuali Rice Team (rice.collab@kuali.org)
52   */
53  public final class RoleXmlUtil {
54      // Do not allow outside code to instantiate this class.
55      private RoleXmlUtil() {}
56  
57      /**
58       * Performs the necessary validation on the new role, then saves it.
59       *
60       * @param newRole The role to persist.
61       * @return The ID of the persisted role.
62       * @throws IllegalArgumentException if newRole is null.
63       * @throws UnmarshalException if newRole contains invalid data.
64       */
65      static String validateAndPersistNewRole(RoleXmlDTO newRole) throws UnmarshalException {
66          if (newRole == null) {
67              throw new IllegalArgumentException("Cannot persist a null role");
68          }
69  
70          // Validate the role and (if applicable) retrieve the ID from an existing matching role.
71          validateAndPrepareRole(newRole);
72  
73          Role.Builder builder = Role.Builder.create();
74          builder.setActive(newRole.getActive());
75          builder.setDescription(newRole.getRoleDescription());
76          builder.setId(newRole.getRoleId());
77          builder.setKimTypeId(newRole.getKimTypeId());
78          builder.setName(newRole.getRoleName());
79          builder.setNamespaceCode(newRole.getNamespaceCode());
80  
81          //save the role
82          Role role = KimApiServiceLocator.getRoleService().createRole(builder.build());
83  
84          // 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.
85          newRole.setAlreadyPersisted(true);
86  
87          return role.getId();
88      }
89  
90      /**
91       * Performs the necessary validation on the new role member, then saves it.
92       *
93       * @param newRoleMember The role member to save.
94       * @return The ID of the persisted role member.
95       * @throws IllegalArgumentException if newRoleMember is null.
96       * @throws UnmarshalException if newRoleMember contains invalid data.
97       */
98      static String validateAndPersistNewRoleMember(RoleMemberXmlDTO newRoleMember) throws UnmarshalException {
99  
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 }