package com.evolveum.midpoint.model.impl.security;
import java.io.IOException;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang.StringUtils;
import org.apache.cxf.configuration.security.AuthorizationPolicy;
import org.apache.cxf.message.Message;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.CredentialsExpiredException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.evolveum.midpoint.model.api.AuthenticationEvaluator;
import com.evolveum.midpoint.model.api.ModelService;
import com.evolveum.midpoint.model.api.context.AbstractAuthenticationContext;
import com.evolveum.midpoint.model.impl.ModelRestService;
import com.evolveum.midpoint.model.impl.util.RestServiceUtil;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.schema.constants.SchemaConstants;
import com.evolveum.midpoint.schema.result.OperationResult;
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.task.api.Task;
import com.evolveum.midpoint.task.api.TaskManager;
import com.evolveum.midpoint.util.exception.CommunicationException;
import com.evolveum.midpoint.util.exception.ConfigurationException;
import com.evolveum.midpoint.util.exception.ObjectNotFoundException;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SecurityViolationException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;
import com.evolveum.midpoint.xml.ns._public.common.common_3.UserType;
public abstract class MidpointRestAuthenticator<T extends AbstractAuthenticationContext> {
private static final Trace LOGGER = TraceManager.getTrace(MidpointRestAuthenticator.class);
@Autowired(required = true)
private SecurityEnforcer securityEnforcer;
@Autowired(required = true)
private SecurityHelper securityHelper;
@Autowired(required=true)
private TaskManager taskManager;
@Autowired(required=true)
private ModelService model;
protected abstract AuthenticationEvaluator<T> getAuthenticationEvaluator();
protected abstract T createAuthenticationContext(AuthorizationPolicy policy, ContainerRequestContext requestCtx);
public void handleRequest(AuthorizationPolicy policy, Message m, ContainerRequestContext requestCtx) {
if (policy == null){
RestServiceUtil.createAbortMessage(requestCtx);
return;
}
T authenticationContext = createAuthenticationContext(policy, requestCtx);
if (authenticationContext == null) {
return;
}
String enteredUsername = authenticationContext.getUsername();
if (enteredUsername == null){
RestServiceUtil.createAbortMessage(requestCtx);
return;
}
LOGGER.trace("Authenticating username '{}' to REST service", enteredUsername);
// We need to create task before attempting authentication. Task ID is also a session ID.
Task task = taskManager.createTaskInstance(ModelRestService.OPERATION_REST_SERVICE);
task.setChannel(SchemaConstants.CHANNEL_REST_URI);
ConnectionEnvironment connEnv = createConnectionEnvironment();
connEnv.setSessionId(task.getTaskIdentifier());
UsernamePasswordAuthenticationToken token;
try {
token = getAuthenticationEvaluator().authenticate(connEnv, authenticationContext);
} catch (UsernameNotFoundException | BadCredentialsException e) {
LOGGER.trace("Exception while authenticating username '{}' to REST service: {}", enteredUsername, e.getMessage(), e);
requestCtx.abortWith(Response.status(Status.UNAUTHORIZED).header("WWW-Authenticate", "Basic authentication failed. Cannot authenticate user.").build());
return;
} catch (DisabledException | LockedException | CredentialsExpiredException | AccessDeniedException
| AuthenticationCredentialsNotFoundException | AuthenticationServiceException e) {
LOGGER.trace("Exception while authenticating username '{}' to REST service: {}", enteredUsername, e.getMessage(), e);
requestCtx.abortWith(Response.status(Status.FORBIDDEN).build());
return;
}
UserType user = ((MidPointPrincipal)token.getPrincipal()).getUser();
task.setOwner(user.asPrismObject());
// m.put(RestServiceUtil.MESSAGE_PROPERTY_TASK_NAME, task);
if (!authorizeUser(user, null, enteredUsername, connEnv, requestCtx)){
return;
}
String oid = requestCtx.getHeaderString("Switch-To-Principal");
OperationResult result = task.getResult();
if (StringUtils.isNotBlank(oid)){
try {
PrismObject<UserType> authorizedUser = model.getObject(UserType.class, oid, null, task, result);
task.setOwner(authorizedUser);
if (!authorizeUser(AuthorizationConstants.AUTZ_REST_PROXY_URL, user, authorizedUser, enteredUsername, connEnv, requestCtx)){
return;
}
if (!authorizeUser(authorizedUser.asObjectable(), null, authorizedUser.getName().getOrig(), connEnv, requestCtx)){
return;
}
} catch (ObjectNotFoundException | SchemaException | SecurityViolationException
| CommunicationException | ConfigurationException e) {
LOGGER.trace("Exception while authenticating user identified with '{}' to REST service: {}", oid, e.getMessage(), e);
requestCtx.abortWith(Response.status(Status.UNAUTHORIZED).header("WWW-Authenticate", "Proxy Authentication failed. Cannot authenticate user.").build());
return;
}
}
m.put(RestServiceUtil.MESSAGE_PROPERTY_TASK_NAME, task);
LOGGER.trace("Authorized to use REST service ({})", user);
}
private boolean authorizeUser(UserType user, PrismObject<UserType> proxyUser, String enteredUsername, ConnectionEnvironment connEnv, ContainerRequestContext requestCtx) {
try {
securityEnforcer.setupPreAuthenticatedSecurityContext(user.asPrismObject());
} catch (SchemaException e) {
securityHelper.auditLoginFailure(enteredUsername, user, connEnv, "Schema error: "+e.getMessage());
requestCtx.abortWith(Response.status(Status.BAD_REQUEST).build());
return false;
}
LOGGER.trace("Authenticated to REST service as {}", user);
return authorizeUser(AuthorizationConstants.AUTZ_REST_ALL_URL, user, null, enteredUsername, connEnv, requestCtx);
}
private boolean authorizeUser(String authorization, UserType user, PrismObject<UserType> proxyUser, String enteredUsername, ConnectionEnvironment connEnv, ContainerRequestContext requestCtx){
OperationResult authorizeResult = new OperationResult("Rest authentication/authorization operation.");
try {
securityEnforcer.authorize(authorization, null, proxyUser, null, null, null, authorizeResult);
} catch (SecurityViolationException e){
securityHelper.auditLoginFailure(enteredUsername, user, connEnv, "Not authorized");
requestCtx.abortWith(Response.status(Status.FORBIDDEN).build());
return false;
} catch (SchemaException e) {
securityHelper.auditLoginFailure(enteredUsername, user, connEnv, "Schema error: "+e.getMessage());
requestCtx.abortWith(Response.status(Status.BAD_REQUEST).build());
return false;
}
return true;
}
private ConnectionEnvironment createConnectionEnvironment() {
ConnectionEnvironment connEnv = new ConnectionEnvironment();
connEnv.setChannel(SchemaConstants.CHANNEL_REST_URI);
// TODO: remote host
return connEnv;
}
public SecurityEnforcer getSecurityEnforcer() {
return securityEnforcer;
}
public ModelService getModel() {
return model;
}
public TaskManager getTaskManager() {
return taskManager;
}
}