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