/*
* Copyright (c) 2010-2017 Evolveum
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.evolveum.midpoint.model.impl.security;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import javax.xml.namespace.QName;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import org.apache.commons.lang.StringUtils;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.phase.PhaseInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.ws.commons.schema.utils.DOMUtil;
import org.apache.wss4j.common.ext.WSSecurityException;
import org.apache.wss4j.common.ext.WSSecurityException.ErrorCode;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import com.evolveum.midpoint.common.ActivationComputer;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.security.api.AuthorizationConstants;
import com.evolveum.midpoint.security.api.ConnectionEnvironment;
import com.evolveum.midpoint.security.api.MidPointPrincipal;
import com.evolveum.midpoint.security.api.SecurityEnforcer;
import com.evolveum.midpoint.security.api.UserProfileService;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.AuthorizationPhaseType;
/**
* Responsible to inject Spring authentication object before we call WS method
*/
public class SpringAuthenticationInjectorInterceptor implements PhaseInterceptor<SoapMessage> {
private static final Trace LOGGER = TraceManager.getTrace(SpringAuthenticationInjectorInterceptor.class);
private String phase;
private Set<String> before = new HashSet<String>();
private Set<String> after = new HashSet<String>();
private String id;
private UserProfileService userDetailsService;
private SecurityEnforcer securityEnforcer;
private SecurityHelper securityHelper;
private ActivationComputer activationComputer;
public SpringAuthenticationInjectorInterceptor(UserProfileService userDetailsService,
SecurityEnforcer securityEnforcer, SecurityHelper securityHelper, ActivationComputer activationComputer) {
super();
this.userDetailsService = userDetailsService;
this.securityEnforcer = securityEnforcer;
this.securityHelper = securityHelper;
this.activationComputer = activationComputer;
id = getClass().getName();
phase = Phase.PRE_PROTOCOL;
getAfter().add(WSS4JInInterceptor.class.getName());
}
@Override
public Set<String> getAfter() {
return after;
}
@Override
public Set<String> getBefore() {
return before;
}
@Override
public String getId() {
return id;
}
@Override
public String getPhase() {
return phase;
}
@Override
public Collection<PhaseInterceptor<? extends Message>> getAdditionalInterceptors() {
return null;
}
@Override
public void handleMessage(SoapMessage message) throws Fault {
//Note: in constructor we have specified that we will be called after we have been successfully authenticated the user through WS-Security
//Now we will only set the Spring Authentication object based on the user found in the header
LOGGER.trace("Intercepted message: {}", message);
SOAPMessage saajSoapMessage = securityHelper.getSOAPMessage(message);
if (saajSoapMessage == null) {
LOGGER.error("No soap message in handler");
throw createFault(WSSecurityException.ErrorCode.FAILURE);
}
ConnectionEnvironment connEnv = new ConnectionEnvironment();
connEnv.setChannel(SchemaConstants.CHANNEL_WEB_SERVICE_URI);
String username = null;
try {
username = securityHelper.getUsernameFromMessage(saajSoapMessage);
LOGGER.trace("Attempt to authenticate user '{}'", username);
if (StringUtils.isBlank(username)) {
message.put(SecurityHelper.CONTEXTUAL_PROPERTY_AUDITED_NAME, true);
securityHelper.auditLoginFailure(username, null, connEnv, "Empty username");
throw createFault(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
}
MidPointPrincipal principal;
try {
principal = userDetailsService.getPrincipal(username);
} catch (SchemaException e) {
LOGGER.debug("Access to web service denied for user '{}': schema error: {}",
username, e.getMessage(), e);
message.put(SecurityHelper.CONTEXTUAL_PROPERTY_AUDITED_NAME, true);
securityHelper.auditLoginFailure(username, null, connEnv, "Schema error: "+e.getMessage());
throw new Fault(e);
}
LOGGER.trace("Principal: {}", principal);
if (principal == null) {
message.put(SecurityHelper.CONTEXTUAL_PROPERTY_AUDITED_NAME, true);
securityHelper.auditLoginFailure(username, null, connEnv, "No user");
throw createFault(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
}
// Account validity and credentials and all this stuff should be already checked
// in the password callback
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, null);
SecurityContextHolder.getContext().setAuthentication(authentication);
String operationName;
try {
operationName = DOMUtil.getFirstChildElement(saajSoapMessage.getSOAPBody()).getLocalName();
} catch (SOAPException e) {
LOGGER.debug("Access to web service denied for user '{}': SOAP error: {}",
username, e.getMessage(), e);
message.put(SecurityHelper.CONTEXTUAL_PROPERTY_AUDITED_NAME, true);
securityHelper.auditLoginFailure(username, principal.getUser(), connEnv, "SOAP error: "+e.getMessage());
throw new Fault(e);
}
// AUTHORIZATION
boolean isAuthorized;
try {
isAuthorized = securityEnforcer.isAuthorized(AuthorizationConstants.AUTZ_WS_ALL_URL, AuthorizationPhaseType.REQUEST, null, null, null, null);
LOGGER.trace("Determined authorization for web service access (action: {}): {}", AuthorizationConstants.AUTZ_WS_ALL_URL, isAuthorized);
} catch (SchemaException e) {
LOGGER.debug("Access to web service denied for user '{}': schema error: {}",
username, e.getMessage(), e);
message.put(SecurityHelper.CONTEXTUAL_PROPERTY_AUDITED_NAME, true);
securityHelper.auditLoginFailure(username, principal.getUser(), connEnv, "Schema error: "+e.getMessage());
throw createFault(WSSecurityException.ErrorCode.FAILURE);
}
if (!isAuthorized) {
String action = QNameUtil.qNameToUri(new QName(AuthorizationConstants.NS_AUTHORIZATION_WS, operationName));
try {
isAuthorized = securityEnforcer.isAuthorized(action, AuthorizationPhaseType.REQUEST, null, null, null, null);
LOGGER.trace("Determined authorization for web service operation {} (action: {}): {}", operationName, action, isAuthorized);
} catch (SchemaException e) {
LOGGER.debug("Access to web service denied for user '{}': schema error: {}",
username, e.getMessage(), e);
message.put(SecurityHelper.CONTEXTUAL_PROPERTY_AUDITED_NAME, true);
securityHelper.auditLoginFailure(username, principal.getUser(), connEnv, "Schema error: "+e.getMessage());
throw createFault(WSSecurityException.ErrorCode.FAILURE);
}
}
if (!isAuthorized) {
LOGGER.debug("Access to web service denied for user '{}': not authorized", username);
message.put(SecurityHelper.CONTEXTUAL_PROPERTY_AUDITED_NAME, true);
securityHelper.auditLoginFailure(username, principal.getUser(), connEnv, "Not authorized");
throw createFault(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
}
} catch (WSSecurityException e) {
LOGGER.debug("Access to web service denied for user '{}': security exception: {}",
username, e.getMessage(), e);
message.put(SecurityHelper.CONTEXTUAL_PROPERTY_AUDITED_NAME, true);
securityHelper.auditLoginFailure(username, null, connEnv, "Security exception: "+e.getMessage());
throw new Fault(e, e.getFaultCode());
} catch (ObjectNotFoundException e) {
LOGGER.debug("Access to web service denied for user '{}': object not found: {}",
username, e.getMessage(), e);
message.put(SecurityHelper.CONTEXTUAL_PROPERTY_AUDITED_NAME, true);
securityHelper.auditLoginFailure(username, null, connEnv, "No user");
throw createFault(WSSecurityException.ErrorCode.FAILED_AUTHENTICATION);
}
// Avoid auditing login attempt again if the operation fails on internal authorization
message.put(SecurityHelper.CONTEXTUAL_PROPERTY_AUDITED_NAME, true);
LOGGER.debug("Access to web service allowed for user '{}'", username);
}
private Fault createFault(ErrorCode code) {
return new Fault(new WSSecurityException(code), code.getQName());
}
@Override
public void handleFault(SoapMessage message) {
// Nothing to do
}
}