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