/*
* The Kuali Financial System, a comprehensive financial management system for higher education.
*
* Copyright 2005-2014 The Kuali Foundation
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kuali.kfs.sec.document;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.kuali.kfs.sec.businessobject.SecurityDefinition;
import org.kuali.kfs.sec.businessobject.SecurityModel;
import org.kuali.kfs.sec.businessobject.SecurityModelDefinition;
import org.kuali.kfs.sec.businessobject.SecurityModelMember;
import org.kuali.kfs.sec.businessobject.SecurityPrincipal;
import org.kuali.kfs.sys.KFSConstants;
import org.kuali.kfs.sys.context.SpringContext;
import org.kuali.rice.core.api.membership.MemberType;
import org.kuali.rice.kew.api.exception.WorkflowException;
import org.kuali.rice.kim.api.group.GroupService;
import org.kuali.rice.kim.api.role.Role;
import org.kuali.rice.kim.api.role.RoleMember;
import org.kuali.rice.kim.api.role.RoleService;
import org.kuali.rice.kim.api.services.KimApiServiceLocator;
import org.kuali.rice.kns.document.MaintenanceDocument;
import org.kuali.rice.krad.bo.DocumentHeader;
import org.kuali.rice.krad.service.BusinessObjectService;
import org.kuali.rice.krad.service.DocumentService;
import org.kuali.rice.krad.util.KRADConstants;
import org.springframework.util.ObjectUtils;
/**
* Maintainable implementation for the Security Model maintenance document. Hooks into Post processing to create a KIM role from
* Model and assigns users/permissions to role based on Model
*/
public class SecurityModelMaintainableImpl extends AbstractSecurityModuleMaintainable {
private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(SecurityModelMaintainableImpl.class);
/**
* @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#doRouteStatusChange(org.kuali.rice.krad.bo.DocumentHeader)
*/
@Override
public void doRouteStatusChange(DocumentHeader documentHeader) {
super.doRouteStatusChange(documentHeader);
if (documentHeader.getWorkflowDocument().isProcessed()) {
DocumentService documentService = SpringContext.getBean(DocumentService.class);
try {
MaintenanceDocument document = (MaintenanceDocument) documentService.getByDocumentHeaderId(documentHeader.getDocumentNumber());
SecurityModel oldSecurityModel = (SecurityModel) document.getOldMaintainableObject().getBusinessObject();
SecurityModel newSecurityModel = (SecurityModel) document.getNewMaintainableObject().getBusinessObject();
boolean newMaintenanceAction = getMaintenanceAction().equalsIgnoreCase(KRADConstants.MAINTENANCE_NEW_ACTION) || getMaintenanceAction().equalsIgnoreCase(KRADConstants.MAINTENANCE_COPY_ACTION);
Role modelRole = createOrUpdateModelRole(newSecurityModel);
assignOrUpdateModelMembershipToDefinitionRoles(modelRole, oldSecurityModel, newSecurityModel, newMaintenanceAction);
assignOrUpdateModelMembers(modelRole, newSecurityModel);
if (!newSecurityModel.isActive()) {
inactivateModelRole(modelRole);
}
}
catch (WorkflowException e) {
LOG.error("caught exception while handling handleRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e);
throw new RuntimeException("caught exception while handling handleRouteStatusChange -> documentService.getByDocumentHeaderId(" + documentHeader.getDocumentNumber() + "). ", e);
}
}
}
/**
* Creates a new role for the model (if the model is new), otherwise updates the role
*
* @param oldSecurityModel SecurityModel record before updates
* @param newSecurityModel SecurityModel after updates
*/
protected Role createOrUpdateModelRole(SecurityModel newSecurityModel) {
RoleService roleService = KimApiServiceLocator.getRoleService();
// the roles are created in the KFS-SEC namespace with the same name as the model
Role modelRole = roleService.getRoleByNamespaceCodeAndName(KFSConstants.CoreModuleNamespaces.ACCESS_SECURITY, newSecurityModel.getName());
if ( modelRole != null ) {
// always set the role as active so we can add members and definitions, after processing the indicator will be updated to
// the appropriate value
Role.Builder updatedRole = Role.Builder.create(modelRole);
updatedRole.setActive(true);
updatedRole.setDescription(newSecurityModel.getDescription());
modelRole = roleService.updateRole(updatedRole.build());
} else {
String roleId = KFSConstants.CoreModuleNamespaces.ACCESS_SECURITY+"-"+newSecurityModel.getId();
Role.Builder newRole = Role.Builder.create();
newRole.setId(roleId);
newRole.setName(newSecurityModel.getName());
newRole.setNamespaceCode(KFSConstants.CoreModuleNamespaces.ACCESS_SECURITY);
newRole.setDescription(newSecurityModel.getDescription());
newRole.setKimTypeId(getDefaultRoleTypeId());
newRole.setActive(true);
modelRole = roleService.createRole(newRole.build());
}
newSecurityModel.setRoleId(modelRole.getId());
return modelRole;
}
/**
* Saves the given security model setting the active indicator to false
*
* @param newSecurityModel SecurityModel to inactivate
*/
protected void inactivateModelRole(Role modelRole) {
RoleService roleService = KimApiServiceLocator.getRoleService();
if ( modelRole != null ) {
Role.Builder updatedRole = Role.Builder.create(modelRole);
updatedRole.setActive(false);
KimApiServiceLocator.getRoleService().updateRole(updatedRole.build());
}
}
/**
* Iterates through the model definition list and assigns the model role to the definition role if necessary or updates the
* current member assignment
*
* @param oldSecurityModel SecurityModel record before updates
* @param newSecurityModel SecurityModel whose membership should be updated
* @param newMaintenanceAction boolean indicating whether this is a new record (old side will not contain data)
*/
protected void assignOrUpdateModelMembershipToDefinitionRoles(Role modelRole, SecurityModel oldSecurityModel, SecurityModel newSecurityModel, boolean newMaintenanceAction) {
RoleService roleService = KimApiServiceLocator.getRoleService();
if ( modelRole == null ) {
LOG.error( "Model Role does not exist for SecurityModel: " + newSecurityModel );
throw new RuntimeException("Model Role does not exist for SecurityModel: " + newSecurityModel );
}
for (SecurityModelDefinition securityModelDefinition : newSecurityModel.getModelDefinitions()) {
SecurityDefinition securityDefinition = securityModelDefinition.getSecurityDefinition();
Role definitionRole = roleService.getRole(securityDefinition.getRoleId());
if ( definitionRole == null ) {
LOG.error( "Definition Role does not exist for SecurityModelDefinition: " + securityDefinition );
throw new RuntimeException("Definition Role does not exist for SecurityModelDefinition: " + securityDefinition );
}
RoleMember modelRoleMembership = null;
if (!newMaintenanceAction) {
SecurityModelDefinition oldSecurityModelDefinition = null;
for (SecurityModelDefinition modelDefinition : oldSecurityModel.getModelDefinitions()) {
if ( ObjectUtils.nullSafeEquals(modelDefinition.getModelDefinitionId(), securityModelDefinition.getModelDefinitionId()) ) {
oldSecurityModelDefinition = modelDefinition;
break;
}
}
if (oldSecurityModelDefinition != null) {
modelRoleMembership = getRoleMembershipForMemberType(definitionRole.getId(),
modelRole.getId(), MemberType.ROLE.getCode(),
getRoleQualifiersFromSecurityModelDefinition(oldSecurityModelDefinition));
}
}
// only create membership if model is active and the model definition record is active
boolean membershipActive = newSecurityModel.isActive() && securityModelDefinition.isActive();
// if membership already exists, need to remove if the model definition record is now inactive or the qualifications
// need updated
if (modelRoleMembership != null) {
if (!membershipActive) {
roleService.removeRoleFromRole(modelRoleMembership.getMemberId(), definitionRole.getNamespaceCode(), definitionRole.getName(), modelRoleMembership.getAttributes());
}
}
// create of update role if membership should be active
if (membershipActive) {
if ( modelRoleMembership == null ) {
modelRoleMembership = roleService.assignRoleToRole(modelRole.getId(), definitionRole.getNamespaceCode(), definitionRole.getName(), getRoleQualifiersFromSecurityModelDefinition(securityModelDefinition));
} else {
RoleMember.Builder updatedRoleMember = RoleMember.Builder.create(modelRoleMembership);
updatedRoleMember.setActiveToDate(null);
updatedRoleMember.setAttributes(getRoleQualifiersFromSecurityModelDefinition(securityModelDefinition));
modelRoleMembership = roleService.updateRoleMember(updatedRoleMember.build());
}
}
}
}
/**
* Iterates through the model member list and assign members to the model role or updates the membership
*
* @param securityModel SecurityModel whose member list should be updated
*/
protected void assignOrUpdateModelMembers( Role modelRole, SecurityModel securityModel) {
if (modelRole == null) {
// this should throw an elegant error if either are null
String error = "Data problem with access security. KIM Role backing the security model is missing. SecurityModel: " + securityModel;
LOG.error(error);
throw new RuntimeException(error);
}
for (SecurityModelMember modelMember : securityModel.getModelMembers()) {
updateSecurityModelRoleMember(modelRole, modelMember, modelMember.getMemberTypeCode(), modelMember.getMemberId(), new HashMap<String, String>(0));
createPrincipalSecurityRecords(modelMember.getMemberId(), modelMember.getMemberTypeCode());
}
}
/**
* Creates security principal records for model members (if necessary) so that they will appear on security principal lookup for
* editing
*
* @param memberId String member id of model role
* @param memberTypeCode String member type code for member
*/
protected void createPrincipalSecurityRecords(String memberId, String memberTypeCode) {
Collection<String> principalIds = new HashSet<String>();
if (MemberType.PRINCIPAL.getCode().equals(memberTypeCode)) {
principalIds.add(memberId);
}
else if (MemberType.ROLE.getCode().equals(memberTypeCode)) {
Role roleInfo = KimApiServiceLocator.getRoleService().getRole(memberId);
Collection<String> rolePrincipalIds = KimApiServiceLocator.getRoleService().getRoleMemberPrincipalIds(roleInfo.getNamespaceCode(), roleInfo.getName(), null);
principalIds.addAll(rolePrincipalIds);
}
else if (MemberType.GROUP.getCode().equals(memberTypeCode)) {
List<String> groupPrincipalIds = KimApiServiceLocator.getGroupService().getMemberPrincipalIds(memberId);
principalIds.addAll(groupPrincipalIds);
}
BusinessObjectService businessObjectService = SpringContext.getBean(BusinessObjectService.class);
for (String principalId : principalIds) {
SecurityPrincipal securityPrincipal = businessObjectService.findBySinglePrimaryKey(SecurityPrincipal.class, principalId);
if (securityPrincipal == null) {
SecurityPrincipal newSecurityPrincipal = new SecurityPrincipal();
newSecurityPrincipal.setPrincipalId(principalId);
businessObjectService.save(newSecurityPrincipal);
}
}
}
/**
* Determines whether the given definition is part of the SecurityModel associated definitions
*
* @param definitionName name of definition to look for
* @param securityModel SecurityModel to check
* @return boolean true if the definition is in the security model, false if not
*/
protected boolean isDefinitionInModel(String definitionName, SecurityModel securityModel) {
for (SecurityModelDefinition securityModelDefinition : securityModel.getModelDefinitions()) {
if (StringUtils.equalsIgnoreCase(definitionName, securityModelDefinition.getSecurityDefinition().getName())) {
return true;
}
}
return false;
}
/**
* Override to clear out KIM role id on copy
*
* @see org.kuali.rice.kns.maintenance.KualiMaintainableImpl#processAfterCopy(org.kuali.rice.kns.document.MaintenanceDocument,
* java.util.Map)
*/
@Override
public void processAfterCopy(MaintenanceDocument document, Map<String, String[]> parameters) {
SecurityModel securityModel = (SecurityModel) document.getNewMaintainableObject().getBusinessObject();
securityModel.setRoleId("");
super.processAfterCopy(document, parameters);
}
}