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