001/**
002 * Copyright 2005-2015 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.document.rule;
017
018import java.sql.Timestamp;
019import java.util.ArrayList;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.Set;
024import java.util.List;
025import java.util.Map;
026
027import javax.xml.namespace.QName;
028
029import org.apache.commons.lang.StringUtils;
030import org.kuali.rice.core.api.CoreConstants;
031import org.kuali.rice.core.api.membership.MemberType;
032import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
033import org.kuali.rice.core.api.uif.RemotableAttributeError;
034import org.kuali.rice.core.api.util.RiceKeyConstants;
035import org.kuali.rice.core.api.util.VersionHelper;
036import org.kuali.rice.kim.api.KimConstants;
037import org.kuali.rice.kim.api.identity.IdentityService;
038import org.kuali.rice.kim.api.identity.principal.Principal;
039import org.kuali.rice.kim.api.permission.Permission;
040import org.kuali.rice.kim.api.responsibility.Responsibility;
041import org.kuali.rice.kim.api.role.Role;
042import org.kuali.rice.kim.api.role.RoleService;
043import org.kuali.rice.kim.api.services.KimApiServiceLocator;
044import org.kuali.rice.kim.api.type.KimAttributeField;
045import org.kuali.rice.kim.api.type.KimType;
046import org.kuali.rice.kim.bo.ui.KimDocumentRoleMember;
047import org.kuali.rice.kim.bo.ui.KimDocumentRolePermission;
048import org.kuali.rice.kim.bo.ui.KimDocumentRoleQualifier;
049import org.kuali.rice.kim.bo.ui.KimDocumentRoleResponsibility;
050import org.kuali.rice.kim.bo.ui.KimDocumentRoleResponsibilityAction;
051import org.kuali.rice.kim.bo.ui.RoleDocumentDelegationMember;
052import org.kuali.rice.kim.bo.ui.RoleDocumentDelegationMemberQualifier;
053import org.kuali.rice.kim.document.IdentityManagementRoleDocument;
054import org.kuali.rice.kim.framework.role.RoleTypeService;
055import org.kuali.rice.kim.framework.services.KimFrameworkServiceLocator;
056import org.kuali.rice.kim.framework.type.KimTypeService;
057import org.kuali.rice.kim.impl.common.attribute.KimAttributeBo;
058import org.kuali.rice.kim.impl.responsibility.AddResponsibilityEvent;
059import org.kuali.rice.kim.impl.responsibility.AddResponsibilityRule;
060import org.kuali.rice.kim.impl.responsibility.KimDocumentResponsibilityRule;
061import org.kuali.rice.kim.impl.responsibility.ResponsibilityBo;
062import org.kuali.rice.kim.impl.responsibility.ResponsibilityInternalService;
063import org.kuali.rice.kim.impl.services.KimImplServiceLocator;
064import org.kuali.rice.kim.impl.type.KimTypeLookupableHelperServiceImpl;
065import org.kuali.rice.kim.rule.event.ui.AddDelegationEvent;
066import org.kuali.rice.kim.rule.event.ui.AddDelegationMemberEvent;
067import org.kuali.rice.kim.rule.event.ui.AddMemberEvent;
068import org.kuali.rice.kim.rule.event.ui.AddPermissionEvent;
069import org.kuali.rice.kim.rule.ui.AddDelegationMemberRule;
070import org.kuali.rice.kim.rule.ui.AddDelegationRule;
071import org.kuali.rice.kim.rule.ui.AddMemberRule;
072import org.kuali.rice.kim.rule.ui.AddPermissionRule;
073import org.kuali.rice.kim.rules.ui.KimDocumentMemberRule;
074import org.kuali.rice.kim.rules.ui.KimDocumentPermissionRule;
075import org.kuali.rice.kim.rules.ui.RoleDocumentDelegationMemberRule;
076import org.kuali.rice.kim.rules.ui.RoleDocumentDelegationRule;
077import org.kuali.rice.kns.kim.type.DataDictionaryTypeServiceHelper;
078import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase;
079import org.kuali.rice.krad.data.KradDataServiceLocator;
080import org.kuali.rice.krad.document.Document;
081import org.kuali.rice.krad.util.GlobalVariables;
082import org.kuali.rice.krad.util.KRADConstants;
083import org.kuali.rice.krad.util.MessageMap;
084import org.kuali.rice.ksb.api.KsbApiServiceLocator;
085import org.kuali.rice.ksb.api.bus.Endpoint;
086import org.kuali.rice.ksb.api.bus.ServiceBus;
087
088/**
089 * @author Kuali Rice Team (rice.collab@kuali.org)
090 */
091public class IdentityManagementRoleDocumentRule extends TransactionalDocumentRuleBase implements AddPermissionRule, AddResponsibilityRule, AddMemberRule, AddDelegationRule, AddDelegationMemberRule {
092//      protected static final Logger LOG = Logger.getLogger( IdentityManagementRoleDocumentRule.class );
093
094    public static final int PRIORITY_NUMBER_MIN_VALUE = 1;
095    public static final int PRIORITY_NUMBER_MAX_VALUE = 11;
096
097        protected AddResponsibilityRule addResponsibilityRule;
098        protected AddPermissionRule  addPermissionRule;
099        protected AddMemberRule  addMemberRule;
100        protected AddDelegationRule addDelegationRule;
101        protected AddDelegationMemberRule addDelegationMemberRule;
102        protected Class<? extends AddResponsibilityRule> addResponsibilityRuleClass = KimDocumentResponsibilityRule.class;
103        protected Class<? extends AddPermissionRule> addPermissionRuleClass = KimDocumentPermissionRule.class;
104        protected Class<? extends AddMemberRule> addMemberRuleClass = KimDocumentMemberRule.class;
105        protected Class<? extends AddDelegationRule> addDelegationRuleClass = RoleDocumentDelegationRule.class;
106        protected Class<? extends AddDelegationMemberRule> addDelegationMemberRuleClass = RoleDocumentDelegationMemberRule.class;
107
108        private static IdentityService identityService;
109        private static ResponsibilityInternalService responsibilityInternalService;
110
111        protected AttributeValidationHelper attributeValidationHelper = new AttributeValidationHelper();
112
113        // KULRICE-4153
114        protected ActiveRoleMemberHelper activeRoleMemberHelper = new ActiveRoleMemberHelper();
115
116    protected IdentityService getIdentityService() {
117        if ( identityService == null) {
118            identityService = KimApiServiceLocator.getIdentityService();
119        }
120        return identityService;
121    }
122
123    @Override
124    protected boolean processCustomSaveDocumentBusinessRules(Document document) {
125        if (!(document instanceof IdentityManagementRoleDocument)) {
126            return false;
127        }
128
129        IdentityManagementRoleDocument roleDoc = (IdentityManagementRoleDocument)document;
130
131        boolean valid = true;
132        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
133        boolean validRoleNamespace = validRoleNamespace(roleDoc);
134        boolean validRoleName = validRoleName(roleDoc);
135        if (!(validRoleNamespace && validRoleName)) {
136            return false;
137        }
138        valid &= validDuplicateRoleName(roleDoc);
139        valid &= validPermissions(roleDoc);
140        valid &= validResponsibilities(roleDoc);
141        //KULRICE-6858 Validate group members are in identity system
142        valid &= validRoleMemberPrincipalIDs(roleDoc.getModifiedMembers());
143        getDictionaryValidationService().validateDocumentAndUpdatableReferencesRecursively(document, getMaxDictionaryValidationDepth(), true, false);
144        if ( !KimTypeLookupableHelperServiceImpl.hasDerivedRoleTypeService(roleDoc.getKimType())
145                && canUserAssignRoleMembers(roleDoc) ) {
146                //valid &= validAssignRole(roleDoc);
147                // KULRICE-4153 & KULRICE-4818
148                // Use the Active Role Member Helper to retrieve only those Role Members & Delegation member that are active
149                // If a member or delegation is active on a Role, and they have an inactive Role Qualifier, Role Qualifier validation will fail
150                // If a member or delegation is inactive on a Role, and they have an inactive Role Qualifier, Role Qualifier validation will pass
151            List<KimDocumentRoleMember> activeRoleMembers = activeRoleMemberHelper.getActiveRoleMembers(roleDoc.getMembers());
152            List<KimDocumentRoleMember> newActiveRoleMembers = activeRoleMemberHelper.getActiveRoleMembers(roleDoc.getModifiedMembers());
153            List<RoleDocumentDelegationMember> activeRoleDelegationMembers = activeRoleMemberHelper.getActiveDelegationRoleMembers(roleDoc.getDelegationMembers());
154
155            valid &= validateRoleQualifier(newActiveRoleMembers, roleDoc.getKimType());
156                valid &= validRoleMemberActiveDates(roleDoc.getModifiedMembers());
157                valid &= validateDelegationMemberRoleQualifier(newActiveRoleMembers, activeRoleDelegationMembers, roleDoc.getKimType(), activeRoleMembers);
158                valid &= validDelegationMemberActiveDates(roleDoc.getDelegationMembers());
159                valid &= validRoleMembersResponsibilityActions(roleDoc.getModifiedMembers());
160        }
161        valid &= validRoleResponsibilitiesActions(roleDoc.getResponsibilities());
162        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
163
164        return valid;
165    }
166    /**
167     * Ensures the {@link IdentityManagementRoleDocument} role namespace is not null or an empty string.
168     * @param roleDoc the {@link IdentityManagementRoleDocument} to validate.
169     * @return TRUE if the role namespace is not null or an empty string, FALSE otherwise.
170     */
171    protected boolean validRoleNamespace(IdentityManagementRoleDocument roleDoc) {
172        boolean validRoleNamespace = false;
173
174        if(StringUtils.isNotBlank(roleDoc.getRoleNamespace())) {
175            validRoleNamespace = true;
176        }
177        else{
178            GlobalVariables.getMessageMap().putError("document.roleNamespace",
179                    RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Role Namespace"});
180        }
181
182        return validRoleNamespace;
183    }
184    /**
185     * ensures the {@link IdentitymangaementRoleDocument} role name is not null or an empty string
186     * @param roleDoc the {@link IdentityManagementRoleDocument} to validate.
187     * @return TRUE if the role name is not null or an empty string, FALSE otherwise.
188     */
189    protected boolean validRoleName(IdentityManagementRoleDocument roleDoc) {
190        boolean validRoleName = false;
191
192        if(StringUtils.isNotBlank(roleDoc.getRoleName())) {
193            validRoleName = true;
194        }
195        else{
196            GlobalVariables.getMessageMap().putError("document.roleName",
197                    RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Role Name"});
198        }
199
200        return validRoleName;
201    }
202
203        protected boolean canUserAssignRoleMembers(IdentityManagementRoleDocument document){
204        boolean rulePassed = true;
205        Map<String,String> additionalPermissionDetails = new HashMap<String,String>();
206        additionalPermissionDetails.put(KimConstants.AttributeConstants.NAMESPACE_CODE, document.getRoleNamespace());
207        additionalPermissionDetails.put(KimConstants.AttributeConstants.ROLE_NAME, document.getRoleName());
208                if((document.getMembers()!=null && document.getMembers().size()>0) ||
209                                (document.getDelegationMembers()!=null && document.getDelegationMembers().size()>0)){
210                        if(!getDocumentDictionaryService().getDocumentAuthorizer(document).isAuthorizedByTemplate(
211                                        document, KimConstants.NAMESPACE_CODE, KimConstants.PermissionTemplateNames.ASSIGN_ROLE,
212                                        GlobalVariables.getUserSession().getPrincipalId(), additionalPermissionDetails, null)){
213                    rulePassed = false;
214                        }
215                }
216                return rulePassed;
217        }
218
219    protected boolean validRoleMemberPrincipalIDs(List<KimDocumentRoleMember> roleMembers) {
220        boolean valid = true;
221        List<String> principalIds = new ArrayList<String>();
222        for(KimDocumentRoleMember roleMember: roleMembers) {
223            if (StringUtils.equals(roleMember.getMemberTypeCode(), KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode()) ) {
224                principalIds.add(roleMember.getMemberId());
225            }
226        }
227        if(!principalIds.isEmpty())       {
228            List<Principal> validPrincipals = getIdentityService().getPrincipals(principalIds);
229            for(KimDocumentRoleMember roleMember: roleMembers) {
230                if (StringUtils.equals(roleMember.getMemberTypeCode(), MemberType.PRINCIPAL.getCode()) ) {
231                    boolean validPrincipalId = false;
232                    if(validPrincipals != null && !validPrincipals.isEmpty())       {
233                        for(Principal validPrincipal: validPrincipals) {
234                            if(roleMember.getMemberId().equals(validPrincipal.getPrincipalId()))     {
235                             validPrincipalId = true;
236                            }
237                        }
238                    }
239                    if(!validPrincipalId) {
240                        GlobalVariables.getMessageMap().putError("document.member.memberId", RiceKeyConstants.ERROR_MEMBERID_MEMBERTYPE_MISMATCH,
241                                new String[] {roleMember.getMemberId()});
242                        valid = false;
243                    }
244                }
245            }
246        }
247        return valid;
248    }
249
250    @SuppressWarnings("unchecked")
251        protected boolean validDuplicateRoleName(IdentityManagementRoleDocument roleDoc){
252        Role role = KimApiServiceLocator.getRoleService().getRoleByNamespaceCodeAndName(roleDoc.getRoleNamespace(),
253                roleDoc.getRoleName());
254        boolean rulePassed = true;
255        if(role!=null){
256                if(role.getId().equals(roleDoc.getRoleId())) {
257                rulePassed = true;
258            }
259                else{
260                        GlobalVariables.getMessageMap().putError("document.roleName",
261                                        RiceKeyConstants.ERROR_DUPLICATE_ENTRY, new String[] {"Role Name"});
262                        rulePassed = false;
263                }
264        }
265        return rulePassed;
266    }
267
268    protected boolean validRoleMemberActiveDates(List<KimDocumentRoleMember> roleMembers) {
269        boolean valid = true;
270                int i = 0;
271        for(KimDocumentRoleMember roleMember: roleMembers) {
272                        valid &= validateActiveDate("document.members["+i+"].activeToDate", roleMember.getActiveFromDate(), roleMember.getActiveToDate());
273                i++;
274        }
275        return valid;
276    }
277
278    protected boolean validDelegationMemberActiveDates(List<RoleDocumentDelegationMember> delegationMembers) {
279        boolean valid = true;
280                int i = 0;
281        for(RoleDocumentDelegationMember delegationMember: delegationMembers) {
282                        valid &= validateActiveDate("document.delegationMembers[" + i + "].activeToDate",
283                       delegationMember.getActiveFromDate(), delegationMember.getActiveToDate());
284                i++;
285        }
286        return valid;
287    }
288
289    protected boolean validPermissions(IdentityManagementRoleDocument document){
290        Permission kimPermissionInfo;
291        boolean valid = true;
292        int i = 0;
293        for(KimDocumentRolePermission permission: document.getPermissions()){
294                kimPermissionInfo = permission.getPermission();
295                if(!permission.isActive() && !hasPermissionToGrantPermission(permission.getPermission(), document)){
296                GlobalVariables.getMessageMap().putError("permissions["+i+"].active", RiceKeyConstants.ERROR_ASSIGN_PERMISSION,
297                                new String[] {kimPermissionInfo.getNamespaceCode(), kimPermissionInfo.getTemplate().getName()});
298                valid = false;
299                }
300                i++;
301        }
302        return valid;
303    }
304
305    protected boolean validResponsibilities(IdentityManagementRoleDocument document){
306        ResponsibilityBo kimResponsibilityImpl;
307        boolean valid = true;
308        int i = 0;
309        for(KimDocumentRoleResponsibility responsibility: document.getResponsibilities()){
310                kimResponsibilityImpl = responsibility.getKimResponsibility();
311                if(!responsibility.isActive() && !hasPermissionToGrantResponsibility(ResponsibilityBo.to(responsibility.getKimResponsibility()), document)){
312                GlobalVariables.getMessageMap().putError("responsibilities["+i+"].active", RiceKeyConstants.ERROR_ASSIGN_RESPONSIBILITY,
313                                new String[] {kimResponsibilityImpl.getNamespaceCode(), kimResponsibilityImpl.getTemplate().getName()});
314                valid = false;
315                }
316                i++;
317        }
318        return valid;
319    }
320
321    protected boolean validRoleResponsibilitiesActions(List<KimDocumentRoleResponsibility> roleResponsibilities){
322        int i = 0;
323        boolean rulePassed = true;
324        for(KimDocumentRoleResponsibility roleResponsibility: roleResponsibilities){
325                if(!getResponsibilityInternalService().areActionsAtAssignmentLevelById(roleResponsibility.getResponsibilityId())) {
326                        validateRoleResponsibilityAction("document.responsibilities["+i+"].roleRspActions[0].priorityNumber", roleResponsibility.getRoleRspActions().get(0));
327            }
328                i++;
329        }
330        return rulePassed;
331    }
332
333    protected boolean validRoleMembersResponsibilityActions(List<KimDocumentRoleMember> roleMembers){
334        int i = 0;
335        int j;
336        boolean rulePassed = true;
337        for(KimDocumentRoleMember roleMember: roleMembers){
338                j = 0;
339                if(roleMember.getRoleRspActions()!=null && !roleMember.getRoleRspActions().isEmpty()){
340                        for(KimDocumentRoleResponsibilityAction roleRspAction: roleMember.getRoleRspActions()){
341                                validateRoleResponsibilityAction("document.members["+i+"].roleRspActions["+j+"].priorityNumber", roleRspAction);
342                                j++;
343                        }
344                }
345                i++;
346        }
347        return rulePassed;
348    }
349
350    protected boolean validateRoleResponsibilityAction(String errorPath, KimDocumentRoleResponsibilityAction roleRspAction){
351        boolean rulePassed = true;
352        /*if(StringUtils.isBlank(roleRspAction.getActionPolicyCode())){
353                GlobalVariables.getErrorMap().putError(errorPath,
354                                RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Action Policy Code"});
355                rulePassed = false;
356        }
357        if(roleRspAction.getPriorityNumber()==null){
358                GlobalVariables.getErrorMap().putError(errorPath,
359                                RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Priority Number"});
360                rulePassed = false;
361        }
362        if(StringUtils.isBlank(roleRspAction.getActionTypeCode())){
363                GlobalVariables.getErrorMap().putError(errorPath,
364                                RiceKeyConstants.ERROR_EMPTY_ENTRY, new String[] {"Action Type Code"});
365                rulePassed = false;
366        }*/
367        if(roleRspAction.getPriorityNumber()!=null &&
368                        (roleRspAction.getPriorityNumber()<PRIORITY_NUMBER_MIN_VALUE
369                                        || roleRspAction.getPriorityNumber()>PRIORITY_NUMBER_MAX_VALUE)){
370                GlobalVariables.getMessageMap().putError(errorPath,
371                                RiceKeyConstants.ERROR_PRIORITY_NUMBER_RANGE, new String[] {PRIORITY_NUMBER_MIN_VALUE+"", PRIORITY_NUMBER_MAX_VALUE+""});
372                rulePassed = false;
373        }
374
375        return rulePassed;
376    }
377
378    protected boolean validateRoleQualifier(List<KimDocumentRoleMember> roleMembers, KimType kimType){
379                List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
380
381                int memberCounter = 0;
382                int roleMemberCount = 0;
383                List<RemotableAttributeError> errorsTemp;
384                Map<String, String> mapToValidate;
385        KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
386        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
387        final List<KimAttributeField> attributeDefinitions = kimTypeService.getAttributeDefinitions(kimType.getId());
388        final Set<String> uniqueAttributeNames = figureOutUniqueQualificationSet(roleMembers, attributeDefinitions);
389
390                for(KimDocumentRoleMember roleMember: roleMembers) {
391                        errorsTemp = Collections.emptyList();
392                        mapToValidate = attributeValidationHelper.convertQualifiersToMap(roleMember.getQualifiers());
393
394            VersionedService<RoleTypeService> versionedRoleTypeService = getVersionedRoleTypeService(kimType);
395            boolean shouldNotValidate = true;
396            if (versionedRoleTypeService != null) {
397                boolean versionOk = VersionHelper.compareVersion(versionedRoleTypeService.getVersion(),
398                        CoreConstants.Versions.VERSION_2_1_2)!=-1? true:false;
399                if(versionOk) {
400                    shouldNotValidate = versionedRoleTypeService.getService().shouldValidateQualifiersForMemberType( MemberType.fromCode(roleMember.getMemberTypeCode()));
401                } else {
402                    shouldNotValidate = false;
403                }
404            }
405            if(!shouldNotValidate){
406                                errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
407                                validationErrors.addAll(attributeValidationHelper.convertErrorsForMappedFields(
408                        "members[" + memberCounter + "]", errorsTemp));
409                        memberCounter++;
410                        }
411                        if (uniqueAttributeNames.size() > 0) {
412                                validateUniquePersonRoleQualifiersUniqueForRoleMembership(roleMember, roleMemberCount, roleMembers, uniqueAttributeNames, validationErrors);
413                        }
414
415                        roleMemberCount += 1;
416        }
417
418                GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
419
420        if (validationErrors.isEmpty()) {
421                return true;
422        } 
423        attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
424        return false;
425    }
426
427    /**
428     * Finds the names of the unique qualification attributes which this role should be checking against
429     *
430     * @param memberships the memberships (we take the qualification from the first)
431     * @param attributeDefinitions information about the attributeDefinitions
432     * @return a Set of unique attribute ids (with their indices, for error reporting)
433     */
434    protected Set<String> figureOutUniqueQualificationSet(List<KimDocumentRoleMember> memberships, List<KimAttributeField> attributeDefinitions) {
435        Set<String> uniqueAttributeIds = new HashSet<String>();
436
437        if (memberships != null && memberships.size() > 1) { // if there aren't two or more members, doing this whole check is kinda silly
438                KimDocumentRoleMember membership = memberships.get(0);
439
440                for (KimDocumentRoleQualifier qualifier : membership.getQualifiers()) {
441                        if (qualifier != null && qualifier.getKimAttribute() != null && !StringUtils.isBlank(qualifier.getKimAttribute().getAttributeName())) {
442                        final KimAttributeField relatedDefinition = DataDictionaryTypeServiceHelper.findAttributeField(
443                            qualifier.getKimAttribute().getAttributeName(), attributeDefinitions);
444
445                        if (relatedDefinition != null && relatedDefinition.isUnique()) {
446                                uniqueAttributeIds.add(qualifier.getKimAttrDefnId()); // it's unique - add it to the Set
447                        }
448                        }
449                }
450        }
451
452        return uniqueAttributeIds;
453    }
454
455    /**
456     * Checks all the qualifiers for the given membership, so that all qualifiers which should be unique are guaranteed to be unique
457     *
458     * @param membershipToCheck the membership to check
459     * @param membershipToCheckIndex the index of the person's membership in the role (for error reporting purposes)
460     * @param validationErrors Map<String, String> of errors to report
461     * @return true if all unique values are indeed unique, false otherwise
462     */
463    protected boolean validateUniquePersonRoleQualifiersUniqueForRoleMembership(KimDocumentRoleMember membershipToCheck, int membershipToCheckIndex, List<KimDocumentRoleMember> memberships, Set<String> uniqueQualifierIds, List<RemotableAttributeError> validationErrors) {
464        boolean foundError = false;
465        int count = 0;
466
467        for (KimDocumentRoleMember membership : memberships) {
468                if (membershipToCheckIndex != count) {
469                        if (sameMembership(membershipToCheck, membership)) {
470                                if (sameUniqueMembershipQualifications(membershipToCheck, membership, uniqueQualifierIds)) {
471                                        foundError = true;
472                                        // add error to each qualifier which is supposed to be unique
473                                        int qualifierCount = 0;
474
475                                        for (KimDocumentRoleQualifier qualifier : membership.getQualifiers()) {
476                                                if (qualifier != null && uniqueQualifierIds.contains(qualifier.getKimAttrDefnId())) {
477                                // for new member lines, KimAttribute is not preloaded
478                                // make sure to load it here in order to obtain the name for use in error message
479                                KimAttributeBo attr = qualifier.getKimAttribute();
480                                String attrName = "<unknown>";
481                                if (attr == null && qualifier.getKimAttrDefnId() != null) {
482                                    attr = KradDataServiceLocator.getDataObjectService().find(KimAttributeBo.class, qualifier.getKimAttrDefnId());
483                                }
484                                if (attr != null) {
485                                    attrName = attr.getAttributeName();
486                                }
487                                validationErrors.add(RemotableAttributeError.Builder.create("document.members["+membershipToCheckIndex+"].qualifiers["+qualifierCount+"].attrVal", RiceKeyConstants.ERROR_DOCUMENT_IDENTITY_MANAGEMENT_PERSON_QUALIFIER_VALUE_NOT_UNIQUE+":"+membership.getMemberId()+";"+attrName+";"+qualifier.getAttrVal()).build());
488                                                }
489                                                qualifierCount += 1;
490                                        }
491                                }
492                        }
493                }
494                count += 1;
495        }
496
497        return foundError;
498    }
499
500    /**
501     * Determines if two memberships represent the same member being added: that is, the two memberships have the same type code and id
502     *
503     * @param membershipA the first membership to check
504     * @param membershipB the second membership to check
505     * @return true if the two memberships represent the same member; false if they do not, or if it could not be profitably determined if the members were the same
506     */
507    protected boolean sameMembership(KimDocumentRoleMember membershipA, KimDocumentRoleMember membershipB) {
508        if (!StringUtils.isBlank(membershipA.getMemberTypeCode()) && !StringUtils.isBlank(membershipB.getMemberTypeCode()) && !StringUtils.isBlank(membershipA.getMemberId()) && !StringUtils.isBlank(membershipB.getMemberId())) {
509                return membershipA.getMemberTypeCode().equals(membershipB.getMemberTypeCode()) && membershipA.getMemberId().equals(membershipB.getMemberId());
510        }
511        return false;
512    }
513
514    /**
515     * Given two memberships which represent the same member, do they share qualifications?
516     *
517     * @param membershipA the first membership to check
518     * @param membershipB the second membership to check
519     * @param uniqueAttributeIds the Set of attribute definition ids which should be unique
520     * @return
521     */
522    protected boolean sameUniqueMembershipQualifications(KimDocumentRoleMember membershipA, KimDocumentRoleMember membershipB, Set<String> uniqueAttributeIds) {
523        boolean equalSoFar = true;
524        for (String kimAttributeDefinitionId : uniqueAttributeIds) {
525                final KimDocumentRoleQualifier qualifierA = membershipA.getQualifier(kimAttributeDefinitionId);
526                final KimDocumentRoleQualifier qualifierB = membershipB.getQualifier(kimAttributeDefinitionId);
527
528                if (qualifierA != null && qualifierB != null) {
529                        equalSoFar &= (qualifierA.getAttrVal() == null && qualifierB.getAttrVal() == null) || (qualifierA.getAttrVal() == null || qualifierA.getAttrVal().equals(qualifierB.getAttrVal()));
530                }
531        }
532        return equalSoFar;
533    }
534
535    protected KimDocumentRoleMember getRoleMemberForDelegation(
536                List<KimDocumentRoleMember> roleMembers, RoleDocumentDelegationMember delegationMember, List<KimDocumentRoleMember> modifiedRoleMembers) {
537        if((roleMembers==null && modifiedRoleMembers==null) || delegationMember==null || delegationMember.getRoleMemberId()==null) { return null; }
538        for(KimDocumentRoleMember roleMember: modifiedRoleMembers){
539            if(delegationMember.getRoleMemberId().equals(roleMember.getRoleMemberId())) {
540                return roleMember;
541            }
542        }
543        for(KimDocumentRoleMember roleMember: roleMembers){
544                if(delegationMember.getRoleMemberId().equals(roleMember.getRoleMemberId())) {
545                return roleMember;
546            }
547        }
548        return null;
549    }
550
551    protected boolean validateDelegationMemberRoleQualifier(List<KimDocumentRoleMember> modifiedRoleMembers,
552                List<RoleDocumentDelegationMember> delegationMembers, KimType kimType, List<KimDocumentRoleMember> nonModifiedRoleMembers){
553                List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
554                boolean valid;
555                int memberCounter = 0;
556                List<RemotableAttributeError> errorsTemp;
557                Map<String, String> mapToValidate;
558        KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
559        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
560        KimDocumentRoleMember roleMember;
561        String errorPath;
562        final List<KimAttributeField> attributeDefinitions = kimTypeService.getAttributeDefinitions(kimType.getId());
563        final Set<String> uniqueQualifierAttributes = figureOutUniqueQualificationSetForDelegation(delegationMembers, attributeDefinitions);
564
565                for(RoleDocumentDelegationMember delegationMember: delegationMembers) {
566                        errorPath = "delegationMembers["+memberCounter+"]";
567                        mapToValidate = attributeValidationHelper.convertQualifiersToMap(delegationMember.getQualifiers());
568                        if(!delegationMember.isRole()){
569                                errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
570                                validationErrors.addAll(
571                                                attributeValidationHelper.convertErrorsForMappedFields(errorPath, errorsTemp));
572                        }
573                        roleMember = getRoleMemberForDelegation(nonModifiedRoleMembers, delegationMember, modifiedRoleMembers);
574                        if(roleMember==null){
575                                valid = false;
576                                GlobalVariables.getMessageMap().putError("document.delegationMembers["+memberCounter+"]", RiceKeyConstants.ERROR_DELEGATE_ROLE_MEMBER_ASSOCIATION, new String[]{});
577                        } else{
578                                errorsTemp = kimTypeService.validateUnmodifiableAttributes(
579                                                                kimType.getId(),
580                                                                attributeValidationHelper.convertQualifiersToMap(roleMember.getQualifiers()),
581                                                                mapToValidate);
582                                validationErrors.addAll(
583                                                attributeValidationHelper.convertErrorsForMappedFields(errorPath, errorsTemp) );
584                        }
585                        if (uniqueQualifierAttributes.size() > 0) {
586                                validateUniquePersonRoleQualifiersUniqueForRoleDelegation(delegationMember, memberCounter, delegationMembers, uniqueQualifierAttributes, validationErrors);
587                        }
588                memberCounter++;
589        }
590                GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
591        if (validationErrors.isEmpty()) {
592                valid = true;
593        } else {
594                attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
595                valid = false;
596        }
597        return valid;
598    }
599
600    /**
601     * Finds the names of the unique qualification attributes which this role should be checking against
602     *
603     * @param memberships the memberships (we take the qualification from the first)
604     * @param attributeDefinitions information about the attributeDefinitions
605     * @return a Set of unique attribute ids (with their indices, for error reporting)
606     */
607    protected Set<String> figureOutUniqueQualificationSetForDelegation(List<RoleDocumentDelegationMember> memberships, List<KimAttributeField> attributeDefinitions) {
608        Set<String> uniqueAttributeIds = new HashSet<String>();
609
610        if (memberships != null && memberships.size() > 1) { // if there aren't two or more members, doing this whole check is kinda silly
611                RoleDocumentDelegationMember membership = memberships.get(0);
612
613                for (RoleDocumentDelegationMemberQualifier qualifier : membership.getQualifiers()) {
614                        if (qualifier != null && qualifier.getKimAttribute() != null && !StringUtils.isBlank(qualifier.getKimAttribute().getAttributeName())) {
615                        final KimAttributeField relatedDefinition = DataDictionaryTypeServiceHelper.findAttributeField(
616                            qualifier.getKimAttribute().getAttributeName(), attributeDefinitions);
617
618                        if (relatedDefinition.isUnique()) {
619                                uniqueAttributeIds.add(qualifier.getKimAttrDefnId()); // it's unique - add it to the Set
620                        }
621                        }
622                }
623        }
624
625        return uniqueAttributeIds;
626    }
627
628    /**
629     * Checks all the qualifiers for the given membership, so that all qualifiers which should be unique are guaranteed to be unique
630     *
631     * @param delegationMembershipToCheck the membership to check
632     * @param membershipToCheckIndex the index of the person's membership in the role (for error reporting purposes)
633     * @param validationErrors Map<String, String> of errors to report
634     * @return true if all unique values are indeed unique, false otherwise
635     */
636    protected boolean validateUniquePersonRoleQualifiersUniqueForRoleDelegation(RoleDocumentDelegationMember delegationMembershipToCheck, int membershipToCheckIndex, List<RoleDocumentDelegationMember> delegationMemberships, Set<String> uniqueQualifierIds, List<RemotableAttributeError> validationErrors) {
637        boolean foundError = false;
638        int count = 0;
639
640        for (RoleDocumentDelegationMember delegationMembership : delegationMemberships) {
641                if (membershipToCheckIndex != count) {
642                        if (sameDelegationMembership(delegationMembershipToCheck, delegationMembership)) {
643                                if (sameUniqueDelegationMembershipQualifications(delegationMembershipToCheck, delegationMembership, uniqueQualifierIds)) {
644                                        foundError = true;
645                                        // add error to each qualifier which is supposed to be unique
646                                        int qualifierCount = 0;
647
648                                        for (RoleDocumentDelegationMemberQualifier qualifier : delegationMembership.getQualifiers()) {
649                                                if (qualifier != null && uniqueQualifierIds.contains(qualifier.getKimAttrDefnId())) {
650                                                        validationErrors.add(RemotableAttributeError.Builder.create("document.delegationMembers["+membershipToCheckIndex+"].qualifiers["+qualifierCount+"].attrVal", RiceKeyConstants.ERROR_DOCUMENT_IDENTITY_MANAGEMENT_PERSON_QUALIFIER_VALUE_NOT_UNIQUE+":"+qualifier.getKimAttribute().getAttributeName()+";"+qualifier.getAttrVal()).build());
651                                                }
652                                                qualifierCount += 1;
653                                        }
654                                }
655                        }
656                }
657                count += 1;
658        }
659
660        return foundError;
661    }
662
663    /**
664     * Determines if two memberships represent the same member being added: that is, the two memberships have the same type code and id
665     *
666     * @param membershipA the first membership to check
667     * @param membershipB the second membership to check
668     * @return true if the two memberships represent the same member; false if they do not, or if it could not be profitably determined if the members were the same
669     */
670    protected boolean sameDelegationMembership(RoleDocumentDelegationMember membershipA, RoleDocumentDelegationMember membershipB) {
671        if (!StringUtils.isBlank(membershipA.getMemberTypeCode()) && !StringUtils.isBlank(membershipB.getMemberTypeCode()) && !StringUtils.isBlank(membershipA.getMemberId()) && !StringUtils.isBlank(membershipB.getMemberId())) {
672                return membershipA.getMemberTypeCode().equals(membershipB.getMemberTypeCode()) && membershipA.getMemberId().equals(membershipB.getMemberId());
673        }
674        return false;
675    }
676
677    /**
678     * Given two memberships which represent the same member, do they share qualifications?
679     *
680     * @param membershipA the first membership to check
681     * @param membershipB the second membership to check
682     * @param uniqueAttributeIds the Set of attribute definition ids which should be unique
683     * @return
684     */
685    protected boolean sameUniqueDelegationMembershipQualifications(RoleDocumentDelegationMember membershipA, RoleDocumentDelegationMember membershipB, Set<String> uniqueAttributeIds) {
686        boolean equalSoFar = true;
687        for (String kimAttributeDefinitionId : uniqueAttributeIds) {
688                final RoleDocumentDelegationMemberQualifier qualifierA = membershipA.getQualifier(kimAttributeDefinitionId);
689                final RoleDocumentDelegationMemberQualifier qualifierB = membershipB.getQualifier(kimAttributeDefinitionId);
690
691                if (qualifierA != null && qualifierB != null) {
692                        equalSoFar &= (qualifierA.getAttrVal() == null && qualifierB.getAttrVal() == null) || (qualifierA.getAttrVal() == null || qualifierA.getAttrVal().equals(qualifierB.getAttrVal()));
693                }
694        }
695        return equalSoFar;
696    }
697
698        protected boolean validateActiveDate(String errorPath, Timestamp activeFromDate, Timestamp activeToDate) {
699                // TODO : do not have detail bus rule yet, so just check this for now.
700                boolean valid = true;
701                if (activeFromDate != null && activeToDate !=null && activeToDate.before(activeFromDate)) {
702                MessageMap errorMap = GlobalVariables.getMessageMap();
703            errorMap.putError(errorPath, RiceKeyConstants.ERROR_ACTIVE_TO_DATE_BEFORE_FROM_DATE);
704            valid = false;
705
706                }
707                return valid;
708        }
709
710        /**
711         *
712         * This method checks to see if adding a role to role membership
713         * creates a circular reference.
714         *
715         * @param addMemberEvent
716         * @return true  - ok to assign, no circular references
717         *         false - do not make assignment, will create circular reference.
718         */
719        protected boolean checkForCircularRoleMembership(AddMemberEvent addMemberEvent)
720        {
721                KimDocumentRoleMember newMember = addMemberEvent.getMember();
722                if (newMember == null || StringUtils.isBlank(newMember.getMemberId())){
723                        MessageMap errorMap = GlobalVariables.getMessageMap();
724                        errorMap.putError("member.memberId", RiceKeyConstants.ERROR_INVALID_ROLE, new String[] {""});
725                        return false;
726                }
727                Set<String> roleMemberIds = null;
728                // if the role member is a role, check to make sure we won't be creating a circular reference.
729                // Verify that the new role is not already related to the role either directly or indirectly
730                if (newMember.isRole()){
731                        // get all nested role member ids that are of type role
732                        RoleService roleService = KimApiServiceLocator.getRoleService();
733                        roleMemberIds = roleService.getRoleTypeRoleMemberIds(newMember.getMemberId());
734
735                        // check to see if the document role is not a member of the new member role
736                        IdentityManagementRoleDocument document = (IdentityManagementRoleDocument)addMemberEvent.getDocument();
737                        if (roleMemberIds.contains(document.getRoleId())){
738                                MessageMap errorMap = GlobalVariables.getMessageMap();
739                                errorMap.putError("member.memberId", RiceKeyConstants.ERROR_ASSIGN_ROLE_MEMBER_CIRCULAR, new String[] {newMember.getMemberId()});
740                                return false;
741                        }
742                }
743                return true;
744        }
745
746        /**
747         * @return the addResponsibilityRule
748         */
749        public AddResponsibilityRule getAddResponsibilityRule() {
750                if(addResponsibilityRule == null){
751                        try {
752                                addResponsibilityRule = addResponsibilityRuleClass.newInstance();
753                        } catch ( Exception ex ) {
754                                throw new RuntimeException( "Unable to create AddResponsibilityRule instance using class: " + addResponsibilityRuleClass, ex );
755                        }
756                }
757                return addResponsibilityRule;
758        }
759
760        /**
761         * @return the addPermissionRule
762         */
763        public AddPermissionRule getAddPermissionRule() {
764                if(addPermissionRule == null){
765                        try {
766                                addPermissionRule = addPermissionRuleClass.newInstance();
767                        } catch ( Exception ex ) {
768                                throw new RuntimeException( "Unable to create AddPermissionRule instance using class: " + addPermissionRuleClass, ex );
769                        }
770                }
771                return addPermissionRule;
772        }
773
774        /**
775         * @return the addMemberRule
776         */
777        public AddMemberRule getAddMemberRule() {
778                if(addMemberRule == null){
779                        try {
780                                addMemberRule = addMemberRuleClass.newInstance();
781                        } catch ( Exception ex ) {
782                                throw new RuntimeException( "Unable to create AddMemberRule instance using class: " + addMemberRuleClass, ex );
783                        }
784                }
785                return addMemberRule;
786        }
787
788        /**
789         * @return the addDelegationRule
790         */
791        public AddDelegationRule getAddDelegationRule() {
792                if(addDelegationRule == null){
793                        try {
794                                addDelegationRule = addDelegationRuleClass.newInstance();
795                        } catch ( Exception ex ) {
796                                throw new RuntimeException( "Unable to create AddDelegationRule instance using class: " + addDelegationRuleClass, ex );
797                        }
798                }
799                return addDelegationRule;
800        }
801
802        /**
803         * @return the addDelegationMemberRule
804         */
805        public AddDelegationMemberRule getAddDelegationMemberRule() {
806                if(addDelegationMemberRule == null){
807                        try {
808                                addDelegationMemberRule = addDelegationMemberRuleClass.newInstance();
809                        } catch ( Exception ex ) {
810                                throw new RuntimeException( "Unable to create AddDelegationMemberRule instance using class: " + addDelegationMemberRuleClass, ex );
811                        }
812                }
813                return addDelegationMemberRule;
814        }
815
816    public boolean processAddPermission(AddPermissionEvent addPermissionEvent) {
817        return getAddPermissionRule().processAddPermission(addPermissionEvent);
818    }
819
820    public boolean hasPermissionToGrantPermission(Permission kimPermissionInfo , IdentityManagementRoleDocument document){
821        return getAddPermissionRule().hasPermissionToGrantPermission(kimPermissionInfo, document);
822    }
823
824    public boolean processAddResponsibility(AddResponsibilityEvent addResponsibilityEvent) {
825        return getAddResponsibilityRule().processAddResponsibility(addResponsibilityEvent);
826    }
827
828    public boolean hasPermissionToGrantResponsibility(Responsibility kimResponsibilityInfo, IdentityManagementRoleDocument document) {
829        return getAddResponsibilityRule().hasPermissionToGrantResponsibility(kimResponsibilityInfo, document);
830    }
831
832    public boolean processAddMember(AddMemberEvent addMemberEvent) {
833        boolean success = new KimDocumentMemberRule().processAddMember(addMemberEvent);
834        success &= validateActiveDate("member.activeFromDate", addMemberEvent.getMember().getActiveFromDate(), addMemberEvent.getMember().getActiveToDate());
835        success &= checkForCircularRoleMembership(addMemberEvent);
836        return success;
837    }
838
839    public boolean processAddDelegation(AddDelegationEvent addDelegationEvent) {
840        return getAddDelegationRule().processAddDelegation(addDelegationEvent);
841    }
842
843    public boolean processAddDelegationMember(AddDelegationMemberEvent addDelegationMemberEvent) {
844        boolean success = new RoleDocumentDelegationMemberRule().processAddDelegationMember(addDelegationMemberEvent);
845        RoleDocumentDelegationMember roleDocumentDelegationMember = addDelegationMemberEvent.getDelegationMember();
846        success &= validateActiveDate("delegationMember.activeFromDate", roleDocumentDelegationMember.getActiveFromDate(), roleDocumentDelegationMember.getActiveToDate());
847        return success;
848    }
849
850    public ResponsibilityInternalService getResponsibilityInternalService() {
851        if ( responsibilityInternalService == null ) {
852                responsibilityInternalService = KimImplServiceLocator.getResponsibilityInternalService();
853        }
854                return responsibilityInternalService;
855        }
856
857    protected RoleTypeService getRoleTypeService(KimType typeInfo) {
858        String serviceName = typeInfo.getServiceName();
859        if (serviceName != null) {
860            try {
861                KimTypeService service = (KimTypeService) GlobalResourceLoader.getService(QName.valueOf(serviceName));
862                if (service != null && service instanceof RoleTypeService) {
863                    return (RoleTypeService) service;
864                }
865                return (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
866            } catch (Exception ex) {
867                return (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
868            }
869        }
870        return null;
871    }
872
873    private static class VersionedService<T> {
874
875        String version;
876        T service;
877
878        VersionedService(String version, T service) {
879            this.version = version;
880            this.service = service;
881        }
882
883        T getService() {
884            return this.service;
885        }
886
887        String getVersion() {
888            return this.version;
889        }
890
891    }
892
893    protected VersionedService<RoleTypeService> getVersionedRoleTypeService(KimType typeInfo) {
894        String serviceName = typeInfo.getServiceName();
895        if (serviceName != null) {
896            String version = "2.0.0"; // default version since the base services have been available since then
897            RoleTypeService roleTypeService = null;
898
899            try {
900
901                ServiceBus serviceBus = KsbApiServiceLocator.getServiceBus();
902                Endpoint endpoint = serviceBus.getEndpoint(QName.valueOf(serviceName));
903                if (endpoint != null) {
904                    version = endpoint.getServiceConfiguration().getServiceVersion();
905                }
906                KimTypeService service = (KimTypeService) GlobalResourceLoader.getService(QName.valueOf(serviceName));
907                if (service != null && service instanceof RoleTypeService) {
908                    roleTypeService = (RoleTypeService) service;
909                } else {
910                    roleTypeService = (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
911                }
912            } catch (Exception ex) {
913                roleTypeService = (RoleTypeService) KimImplServiceLocator.getService("kimNoMembersRoleTypeService");
914            }
915
916            return new VersionedService<RoleTypeService>(version, roleTypeService);
917        }
918
919        return null;
920    }
921
922}