001/**
002 * Copyright 2005-2017 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.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.commons.collections.CollectionUtils;
025import org.apache.commons.lang.StringUtils;
026import org.kuali.rice.core.api.uif.RemotableAttributeError;
027import org.kuali.rice.core.api.util.RiceKeyConstants;
028import org.kuali.rice.coreservice.api.CoreServiceApiServiceLocator;
029import org.kuali.rice.coreservice.api.namespace.Namespace;
030import org.kuali.rice.kim.api.KimConstants;
031import org.kuali.rice.kim.api.group.Group;
032import org.kuali.rice.kim.api.identity.IdentityService;
033import org.kuali.rice.kim.api.identity.principal.Principal;
034import org.kuali.rice.kim.api.services.KimApiServiceLocator;
035import org.kuali.rice.kim.api.type.KimType;
036import org.kuali.rice.kim.bo.ui.GroupDocumentMember;
037import org.kuali.rice.kim.bo.ui.GroupDocumentQualifier;
038import org.kuali.rice.kim.document.IdentityManagementGroupDocument;
039import org.kuali.rice.kim.framework.services.KimFrameworkServiceLocator;
040import org.kuali.rice.kim.framework.type.KimTypeService;
041import org.kuali.rice.kim.rule.event.ui.AddGroupMemberEvent;
042import org.kuali.rice.kim.rule.ui.AddGroupMemberRule;
043import org.kuali.rice.kim.rules.ui.GroupDocumentMemberRule;
044import org.kuali.rice.kns.rules.TransactionalDocumentRuleBase;
045import org.kuali.rice.krad.document.Document;
046import org.kuali.rice.krad.util.GlobalVariables;
047import org.kuali.rice.krad.util.KRADConstants;
048import org.kuali.rice.krad.util.MessageMap;
049
050/**
051 * @author Kuali Rice Team (rice.collab@kuali.org)
052 */
053public class IdentityManagementGroupDocumentRule extends TransactionalDocumentRuleBase implements AddGroupMemberRule {
054
055        protected AddGroupMemberRule addGroupMemberRule;
056        protected AttributeValidationHelper attributeValidationHelper = new AttributeValidationHelper();
057        
058        protected Class<? extends GroupDocumentMemberRule> addGroupMemberRuleClass = GroupDocumentMemberRule.class;
059
060        protected IdentityService identityService; 
061        
062    public IdentityService getIdentityService() {
063        if ( identityService == null) {
064            identityService = KimApiServiceLocator.getIdentityService();
065        }
066        return identityService;
067    }
068
069    @Override
070    protected boolean processCustomSaveDocumentBusinessRules(Document document) {
071        if (!(document instanceof IdentityManagementGroupDocument)) {
072            return false;
073        }
074
075        IdentityManagementGroupDocument groupDoc = (IdentityManagementGroupDocument)document;
076
077        boolean valid = true;
078        GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
079        valid &= validAssignGroup(groupDoc);
080        valid &= validDuplicateGroupName(groupDoc);
081        getDictionaryValidationService().validateDocumentAndUpdatableReferencesRecursively(document, getMaxDictionaryValidationDepth(), true, false);
082        valid &= validateGroupQualifier(groupDoc.getQualifiers(), groupDoc.getKimType());
083        valid &= validGroupMemberActiveDates(groupDoc.getMembers());
084        //KULRICE-6858 Validate group members are in identity system
085        valid &= validGroupMemberPrincipalIDs(groupDoc.getMembers());
086        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
087
088        return valid;
089    }
090
091    //Added method to validate existence of a group namespace on a submitted IdentityManagementGroupDocument
092    protected boolean validGroupNamespace(IdentityManagementGroupDocument document){
093        boolean rulePassed = true;
094        String namespacePassed = document.getGroupNamespace();
095        if(StringUtils.isNotBlank(namespacePassed)){
096            rulePassed = true;
097            Namespace matchedNamespace = CoreServiceApiServiceLocator.getNamespaceService().getNamespace(namespacePassed);
098            if(matchedNamespace == null){
099                rulePassed = false;
100            }
101        }else{
102            rulePassed = false;
103        }
104        if(!rulePassed){
105            GlobalVariables.getMessageMap().putError("document.groupNamespace",
106                    RiceKeyConstants.ERROR_REQUIRED,
107                    new String[] {"Group Namespace"});
108        }
109        return rulePassed;
110    }
111
112        protected boolean validAssignGroup(IdentityManagementGroupDocument document){
113        boolean rulePassed = true;
114        Map<String,String> additionalPermissionDetails = new HashMap<String,String>();
115        additionalPermissionDetails.put(KimConstants.AttributeConstants.NAMESPACE_CODE, document.getGroupNamespace());
116        additionalPermissionDetails.put(KimConstants.AttributeConstants.GROUP_NAME, document.getGroupName());
117                if(document.getMembers()!=null && document.getMembers().size()>0){
118                        if(!getDocumentDictionaryService().getDocumentAuthorizer(document).isAuthorizedByTemplate(
119                                        document, KimConstants.NAMESPACE_CODE, KimConstants.PermissionTemplateNames.POPULATE_GROUP,
120                                        GlobalVariables.getUserSession().getPrincipalId(), additionalPermissionDetails, null)){
121                        GlobalVariables.getMessageMap().putError("document.groupName", 
122                                        RiceKeyConstants.ERROR_ASSIGN_GROUP, 
123                                        new String[] {document.getGroupNamespace(), document.getGroupName()});
124                    rulePassed = false;
125                        }
126                }
127                return rulePassed;
128        }
129
130        protected boolean validDuplicateGroupName(IdentityManagementGroupDocument groupDoc){
131        Group group = null;
132        if(null != groupDoc.getGroupNamespace() && null != groupDoc.getGroupName()){
133            group = KimApiServiceLocator.getGroupService().getGroupByNamespaceCodeAndName(
134                groupDoc.getGroupNamespace(), groupDoc.getGroupName());
135        }
136        boolean rulePassed = true;
137        if(group!=null){
138                if(group.getId().equals(groupDoc.getGroupId())) {
139                        rulePassed = true;
140            }
141                else{
142                        GlobalVariables.getMessageMap().putError("document.groupName", 
143                                        RiceKeyConstants.ERROR_DUPLICATE_ENTRY, new String[] {"Group Name"});
144                        rulePassed = false;
145                }
146        }
147        return rulePassed;
148    }
149    
150    protected boolean validGroupMemberActiveDates(List<GroupDocumentMember> groupMembers) {
151        boolean valid = true;
152                int i = 0;
153        for(GroupDocumentMember groupMember: groupMembers) {
154                        valid &= validateActiveDate("document.members["+i+"].activeToDate", groupMember.getActiveFromDate(), groupMember.getActiveToDate());
155                i++;
156        }
157        return valid;
158    }
159
160    protected boolean validGroupMemberPrincipalIDs(List<GroupDocumentMember> groupMembers) {
161        boolean valid = true;
162        List<String> principalIds = new ArrayList<String>();
163        for(GroupDocumentMember groupMember: groupMembers) {
164            if (StringUtils.equals(groupMember.getMemberTypeCode(), KimConstants.KimGroupMemberTypes.PRINCIPAL_MEMBER_TYPE.getCode()) ) {
165                principalIds.add(groupMember.getMemberId());
166            }
167        }
168        if(!principalIds.isEmpty())       {
169            // retrieve valid principals/principal-ids from identity service
170            List<Principal> validPrincipals = getIdentityService().getPrincipals(principalIds);
171            List<String> validPrincipalIds = new ArrayList<String>(validPrincipals.size());
172            for (Principal principal : validPrincipals) {
173                validPrincipalIds.add(principal.getPrincipalId());
174            }
175            // check that there are no invalid principals in the principal list, return false
176            List<String> invalidPrincipalIds = new ArrayList<String>(CollectionUtils.subtract(principalIds, validPrincipalIds));
177            // if list is not empty add error messages and return false
178            if(CollectionUtils.isNotEmpty(invalidPrincipalIds)) {
179                GlobalVariables.getMessageMap().putError("document.member.memberId", RiceKeyConstants.ERROR_MEMBERID_MEMBERTYPE_MISMATCH,
180                        invalidPrincipalIds.toArray(new String[invalidPrincipalIds.size()]));
181                valid = false;
182            }
183        }
184        return valid;
185    }
186
187    protected boolean validateGroupQualifier(List<GroupDocumentQualifier> groupQualifiers, KimType kimType){
188                List<RemotableAttributeError> validationErrors = new ArrayList<RemotableAttributeError>();
189
190                List<RemotableAttributeError> errorsTemp;
191                Map<String, String> mapToValidate;
192        KimTypeService kimTypeService = KimFrameworkServiceLocator.getKimTypeService(kimType);
193        GlobalVariables.getMessageMap().removeFromErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
194                mapToValidate = attributeValidationHelper.convertQualifiersToMap(groupQualifiers);
195                errorsTemp = kimTypeService.validateAttributes(kimType.getId(), mapToValidate);
196                validationErrors.addAll(attributeValidationHelper.convertErrors("",
197                attributeValidationHelper.convertQualifiersToAttrIdxMap(groupQualifiers), errorsTemp));
198                GlobalVariables.getMessageMap().addToErrorPath(KRADConstants.DOCUMENT_PROPERTY_NAME);
199                
200        if (validationErrors.isEmpty()) {
201                return true;
202        } 
203        attributeValidationHelper.moveValidationErrorsToErrorMap(validationErrors);
204        return false;
205    }
206    
207        protected boolean validateActiveDate(String errorPath, Timestamp activeFromDate, Timestamp activeToDate) {
208                // TODO : do not have detail bus rule yet, so just check this for now.
209                boolean valid = true;
210                if (activeFromDate != null && activeToDate !=null && activeToDate.before(activeFromDate)) {
211                MessageMap errorMap = GlobalVariables.getMessageMap();
212            errorMap.putError(errorPath, RiceKeyConstants.ERROR_ACTIVE_TO_DATE_BEFORE_FROM_DATE);
213            valid = false;
214                        
215                }
216                return valid;
217        }
218        
219        /**
220         * @return the addGroupMemberRule
221         */
222        public AddGroupMemberRule getAddGroupMemberRule() {
223                if(addGroupMemberRule == null){
224                        try {
225                                addGroupMemberRule = addGroupMemberRuleClass.newInstance();
226                        } catch ( Exception ex ) {
227                                throw new RuntimeException( "Unable to create AddMemberRule instance using class: " + addGroupMemberRuleClass, ex );
228                        }
229                }
230                return addGroupMemberRule;
231        }
232
233    public boolean processAddGroupMember(AddGroupMemberEvent addGroupMemberEvent) {
234        return getAddGroupMemberRule().processAddGroupMember(addGroupMemberEvent);    
235    }
236
237}