Alexander Wels has uploaded a new change for review. Change subject: userportal,webadmin: additional protection for GWT RPC ......................................................................
userportal,webadmin: additional protection for GWT RPC - Added additional protection to GWT-RPC calls. Change-Id: Ia4a5ad1f33eb985aa79e1376aecb48ae365d334d Signed-off-by: Alexander Wels <aw...@redhat.com> --- M frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/communication/GWTRPCCommunicationProvider.java M frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/gwtservices/GenericApiGWTService.java A frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/gwtservices/XsrfProtectedRpcService.java A frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/AbstractXsrfProtectedRpcServlet.java M frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/GenericApiGWTServiceImpl.java A frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/XsrfProtectedRpcServlet.java M frontend/webadmin/modules/frontend/src/main/resources/META-INF/web-fragment.xml M frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/FrontendActionTest.java M frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/FrontendTest.java M frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/communication/GWTRPCCommunicationProviderTest.java M frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/gin/BaseSystemModule.java M frontend/webadmin/modules/userportal-gwtp/src/main/webapp/WEB-INF/web.xml M frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml 13 files changed, 488 insertions(+), 95 deletions(-) git pull ssh://gerrit.ovirt.org:29418/ovirt-engine refs/changes/08/26408/1 diff --git a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/communication/GWTRPCCommunicationProvider.java b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/communication/GWTRPCCommunicationProvider.java index a3d8f23..dca3093 100644 --- a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/communication/GWTRPCCommunicationProvider.java +++ b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/communication/GWTRPCCommunicationProvider.java @@ -15,7 +15,12 @@ import org.ovirt.engine.core.common.queries.VdcQueryType; import org.ovirt.engine.ui.frontend.gwtservices.GenericApiGWTServiceAsync; +import com.google.gwt.core.client.GWT; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.HasRpcToken; +import com.google.gwt.user.client.rpc.ServiceDefTarget; +import com.google.gwt.user.client.rpc.XsrfToken; +import com.google.gwt.user.client.rpc.XsrfTokenServiceAsync; import com.google.inject.Inject; /** @@ -25,25 +30,67 @@ public class GWTRPCCommunicationProvider implements CommunicationProvider { /** + * Callback interface when retrieving the GWT RPC service. We need the callback in order to retrieve the + * XSRF token if it is not available yet. + */ + private interface ServiceCallback { + /** + * The callback method with the service. + * @param service The GWT RPC service that contains the appropriate token. + */ + void serviceFound(GenericApiGWTServiceAsync service); + /** + * The failure callback in case we are unable to retrieve the approriate token. + * @param exception The exception thrown. + */ + void onFailure(Throwable exception); + } + + /** * GWT RPC service. */ private final GenericApiGWTServiceAsync service; /** - * Get the GWT RPC service. - * @return instance of the GWT RPC service. + * GWT XSRF service. */ - private GenericApiGWTServiceAsync getService() { - return service; + private final XsrfTokenServiceAsync xsrfService; + + /** + * Get the GWT RPC service. + * @param callback The callback to use when determining the service. + */ + private void getService(final ServiceCallback callback) { + final HasRpcToken serviceWithToken = (HasRpcToken) service; + if (serviceWithToken.getRpcToken() != null) { + callback.serviceFound(service); + } else { + xsrfService.getNewXsrfToken(new AsyncCallback<XsrfToken>() { + @Override + public void onSuccess(XsrfToken token) { + serviceWithToken.setRpcToken(token); + callback.serviceFound(service); + } + + @Override + public void onFailure(Throwable caught) { + callback.onFailure(caught); + } + }); + } } /** * Constructor. * @param asyncService GWT RPC service. + * @param xsrfTokenService The GWT XSRF token service, used to retrieve a new XSRF token. */ @Inject - public GWTRPCCommunicationProvider(final GenericApiGWTServiceAsync asyncService) { + public GWTRPCCommunicationProvider(final GenericApiGWTServiceAsync asyncService, + final XsrfTokenServiceAsync xsrfTokenService) { this.service = asyncService; + this.xsrfService = xsrfTokenService; + ((ServiceDefTarget) this.xsrfService).setServiceEntryPoint(GWT.getModuleBaseURL() + "xsrf"); //$NON-NLS-1$ } /** @@ -70,16 +117,26 @@ * @param operation The operation to run. */ private void runPublicQuery(final VdcOperation<?, ?> operation) { - getService().RunPublicQuery((VdcQueryType) operation.getOperation(), - (VdcQueryParametersBase) operation.getParameter(), new AsyncCallback<VdcQueryReturnValue>() { + getService(new ServiceCallback() { @Override - public void onFailure(final Throwable exception) { - operation.getCallback().onFailure(operation, exception); + public void serviceFound(GenericApiGWTServiceAsync service) { + service.RunPublicQuery((VdcQueryType) operation.getOperation(), + (VdcQueryParametersBase) operation.getParameter(), new AsyncCallback<VdcQueryReturnValue>() { + @Override + public void onFailure(final Throwable exception) { + operation.getCallback().onFailure(operation, exception); + } + + @Override + public void onSuccess(final VdcQueryReturnValue result) { + operation.getCallback().onSuccess(operation, result); + } + }); } @Override - public void onSuccess(final VdcQueryReturnValue result) { - operation.getCallback().onSuccess(operation, result); + public void onFailure(Throwable exception) { + operation.getCallback().onFailure(operation, exception); } }); } @@ -89,16 +146,26 @@ * @param operation The operation to run. */ private void runQuery(final VdcOperation<?, ?> operation) { - getService().RunQuery((VdcQueryType) operation.getOperation(), - (VdcQueryParametersBase) operation.getParameter(), new AsyncCallback<VdcQueryReturnValue>() { + getService(new ServiceCallback() { @Override - public void onFailure(final Throwable exception) { - operation.getCallback().onFailure(operation, exception); + public void serviceFound(GenericApiGWTServiceAsync service) { + service.RunQuery((VdcQueryType) operation.getOperation(), + (VdcQueryParametersBase) operation.getParameter(), new AsyncCallback<VdcQueryReturnValue>() { + @Override + public void onFailure(final Throwable exception) { + operation.getCallback().onFailure(operation, exception); + } + + @Override + public void onSuccess(final VdcQueryReturnValue result) { + operation.getCallback().onSuccess(operation, result); + } + }); } @Override - public void onSuccess(final VdcQueryReturnValue result) { - operation.getCallback().onSuccess(operation, result); + public void onFailure(Throwable exception) { + operation.getCallback().onFailure(operation, exception); } }); } @@ -108,16 +175,26 @@ * @param operation The operation to run. */ private void runAction(final VdcOperation<?, ?> operation) { - getService().RunAction((VdcActionType) operation.getOperation(), - (VdcActionParametersBase) operation.getParameter(), new AsyncCallback<VdcReturnValueBase>() { + getService(new ServiceCallback() { @Override - public void onFailure(final Throwable exception) { - operation.getCallback().onFailure(operation, exception); + public void serviceFound(GenericApiGWTServiceAsync service) { + service.RunAction((VdcActionType) operation.getOperation(), + (VdcActionParametersBase) operation.getParameter(), new AsyncCallback<VdcReturnValueBase>() { + @Override + public void onFailure(final Throwable exception) { + operation.getCallback().onFailure(operation, exception); + } + + @Override + public void onSuccess(final VdcReturnValueBase result) { + operation.getCallback().onSuccess(operation, result); + } + }); } @Override - public void onSuccess(final VdcReturnValueBase result) { - operation.getCallback().onSuccess(operation, result); + public void onFailure(Throwable exception) { + operation.getCallback().onFailure(operation, exception); } }); } @@ -161,8 +238,8 @@ private void transmitMultipleQueries(final List<VdcOperation<?, ?>> queriesList) { if (queriesList.size() > 1 || (queriesList.size() == 1 && queriesList.get(0).getCallback() instanceof VdcOperationCallbackList)) { - List<VdcQueryType> queryTypes = new ArrayList<VdcQueryType>(); - List<VdcQueryParametersBase> parameters = new ArrayList<VdcQueryParametersBase>(); + final List<VdcQueryType> queryTypes = new ArrayList<VdcQueryType>(); + final List<VdcQueryParametersBase> parameters = new ArrayList<VdcQueryParametersBase>(); for (VdcOperation<?, ?> operation: new ArrayList<VdcOperation<?, ?>>(queriesList)) { if (operation.isPublic()) { @@ -174,38 +251,62 @@ } } - getService().RunMultipleQueries((ArrayList<VdcQueryType>) queryTypes, - (ArrayList<VdcQueryParametersBase>) parameters, - new AsyncCallback<ArrayList<VdcQueryReturnValue>>() { + getService(new ServiceCallback() { @Override - public void onFailure(final Throwable exception) { - Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = getCallbackMap(queriesList); - for (Map.Entry<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { - if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { - ((VdcOperationCallbackList) callbackEntry.getKey()).onFailure(callbackEntry.getValue(), exception); - } else { - ((VdcOperationCallback) callbackEntry.getKey()).onFailure(callbackEntry.getValue().get(0), exception); + public void serviceFound(GenericApiGWTServiceAsync service) { + service.RunMultipleQueries((ArrayList<VdcQueryType>) queryTypes, + (ArrayList<VdcQueryParametersBase>) parameters, + new AsyncCallback<ArrayList<VdcQueryReturnValue>>() { + @Override + public void onFailure(final Throwable exception) { + handleMultipleQueriesFailure(queriesList, exception); } - } + + @Override + public void onSuccess(final ArrayList<VdcQueryReturnValue> result) { + Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = + getCallbackMap(queriesList); + for (Map.Entry<VdcOperationCallback<?, ?>, + List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { + List<VdcQueryReturnValue> queryResult = (List<VdcQueryReturnValue>) getOperationResult( + callbackEntry.getValue(), queriesList, result); + if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { + ((VdcOperationCallbackList) callbackEntry.getKey()) + .onSuccess(callbackEntry.getValue(), queryResult); + } else { + ((VdcOperationCallback) callbackEntry.getKey()) + .onSuccess(callbackEntry.getValue().get(0), queryResult.get(0)); + } + } + } + }); + } @Override - public void onSuccess(final ArrayList<VdcQueryReturnValue> result) { - Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = getCallbackMap(queriesList); - for (Map.Entry<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { - List<VdcQueryReturnValue> queryResult = (List<VdcQueryReturnValue>) getOperationResult( - callbackEntry.getValue(), queriesList, result); - if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { - ((VdcOperationCallbackList) callbackEntry.getKey()).onSuccess(callbackEntry.getValue(), queryResult); - } else { - ((VdcOperationCallback) callbackEntry.getKey()).onSuccess(callbackEntry.getValue().get(0), - queryResult.get(0)); - } - } + public void onFailure(Throwable exception) { + handleMultipleQueriesFailure(queriesList, exception); } }); } else if (queriesList.size() == 1) { transmitOperation(queriesList.get(0)); + } + } + + /** + * Multiple queries failure handler. + * @param queriesList The queries list. + * @param exception The exception causing the failure. + */ + private void handleMultipleQueriesFailure(final List<VdcOperation<?, ?>> queriesList, + final Throwable exception) { + Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = getCallbackMap(queriesList); + for (Map.Entry<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { + if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { + ((VdcOperationCallbackList) callbackEntry.getKey()).onFailure(callbackEntry.getValue(), exception); + } else { + ((VdcOperationCallback) callbackEntry.getKey()).onFailure(callbackEntry.getValue().get(0), exception); + } } } @@ -256,43 +357,60 @@ } private void runMultipleActions(final VdcActionType actionType, final List<VdcOperation<?, ?>> operations, - List<VdcActionParametersBase> parameters, final List<VdcOperation<?, ?>> allActionOperations, + final List<VdcActionParametersBase> parameters, final List<VdcOperation<?, ?>> allActionOperations, final boolean waitForResults) { - getService().RunMultipleActions(actionType, (ArrayList<VdcActionParametersBase>) parameters, - false, waitForResults, new AsyncCallback<ArrayList<VdcReturnValueBase>>() { - + getService(new ServiceCallback() { @Override - public void onFailure(final Throwable exception) { - Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = - getCallbackMap(operations); - for (Map.Entry<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { - if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { - ((VdcOperationCallbackList) callbackEntry.getKey()).onFailure(callbackEntry.getValue(), exception); - } else { - ((VdcOperationCallback) callbackEntry.getKey()).onFailure(callbackEntry.getValue().get(0), - exception); + public void serviceFound(GenericApiGWTServiceAsync service) { + service.RunMultipleActions(actionType, (ArrayList<VdcActionParametersBase>) parameters, + false, waitForResults, new AsyncCallback<ArrayList<VdcReturnValueBase>>() { + + @Override + public void onFailure(final Throwable exception) { + handleRunMultipleActionFailure(operations, exception); } - } + + @Override + public void onSuccess(final ArrayList<VdcReturnValueBase> result) { + Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = + getCallbackMap(operations); + for (Map.Entry<VdcOperationCallback<?, ?>, + List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { + List<VdcReturnValueBase> actionResult = (List<VdcReturnValueBase>) + getOperationResult(callbackEntry.getValue(), allActionOperations, result); + if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { + ((VdcOperationCallbackList) callbackEntry.getKey()) + .onSuccess(callbackEntry.getValue(), actionResult); + } else { + ((VdcOperationCallback) callbackEntry.getKey()) + .onSuccess(callbackEntry.getValue().get(0), actionResult.get(0)); + } + } + } + }); } @Override - public void onSuccess(final ArrayList<VdcReturnValueBase> result) { - Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = - getCallbackMap(operations); - for (Map.Entry<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { - List<VdcReturnValueBase> actionResult = (List<VdcReturnValueBase>) - getOperationResult(callbackEntry.getValue(), allActionOperations, result); - if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { - ((VdcOperationCallbackList) callbackEntry.getKey()).onSuccess(callbackEntry.getValue(), - actionResult); - } else { - ((VdcOperationCallback) callbackEntry.getKey()).onSuccess(callbackEntry.getValue().get(0), - actionResult.get(0)); - } - } + public void onFailure(Throwable exception) { + handleRunMultipleActionFailure(operations, exception); } }); } + + private void handleRunMultipleActionFailure(final List<VdcOperation<?, ?>> operations, + final Throwable exception) { + Map<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackMap = + getCallbackMap(operations); + for (Map.Entry<VdcOperationCallback<?, ?>, List<VdcOperation<?, ?>>> callbackEntry: callbackMap.entrySet()) { + if (callbackEntry.getKey() instanceof VdcOperationCallbackList) { + ((VdcOperationCallbackList) callbackEntry.getKey()).onFailure(callbackEntry.getValue(), exception); + } else { + ((VdcOperationCallback) callbackEntry.getKey()).onFailure(callbackEntry.getValue().get(0), + exception); + } + } + } + /** * Map operations by callback, so we can properly call a single callback for all related operations. * @param operationList The list of operations to determine the map for. @@ -344,17 +462,27 @@ */ @Override public void login(final VdcOperation<VdcActionType, LoginUserParameters> loginOperation) { - getService().Login(loginOperation.getParameter().getLoginName(), loginOperation.getParameter().getPassword(), - loginOperation.getParameter().getProfileName(), loginOperation.getOperation(), - new AsyncCallback<VdcReturnValueBase>() { + getService(new ServiceCallback() { @Override - public void onSuccess(final VdcReturnValueBase result) { - loginOperation.getCallback().onSuccess(loginOperation, result); + public void serviceFound(GenericApiGWTServiceAsync service) { + service.Login(loginOperation.getParameter().getLoginName(), loginOperation.getParameter().getPassword(), + loginOperation.getParameter().getProfileName(), loginOperation.getOperation(), + new AsyncCallback<VdcReturnValueBase>() { + @Override + public void onSuccess(final VdcReturnValueBase result) { + loginOperation.getCallback().onSuccess(loginOperation, result); + } + + @Override + public void onFailure(final Throwable caught) { + loginOperation.getCallback().onFailure(loginOperation, caught); + } + }); } @Override - public void onFailure(final Throwable caught) { - loginOperation.getCallback().onFailure(loginOperation, caught); + public void onFailure(Throwable exception) { + loginOperation.getCallback().onFailure(loginOperation, exception); } }); } @@ -366,15 +494,29 @@ */ @Override public void logout(final Object userObject, final UserCallback callback) { - getService().logOff((DbUser) userObject, new AsyncCallback<VdcReturnValueBase>() { + getService(new ServiceCallback() { + @Override - public void onSuccess(final VdcReturnValueBase result) { - callback.onSuccess(result); + public void serviceFound(GenericApiGWTServiceAsync foundService) { + foundService.logOff((DbUser) userObject, new AsyncCallback<VdcReturnValueBase>() { + @Override + public void onSuccess(final VdcReturnValueBase result) { + //Remove the rpc token when logging out. + ((HasRpcToken)service).setRpcToken(null); + callback.onSuccess(result); + } + + @Override + public void onFailure(final Throwable caught) { + callback.onFailure(caught); + } + }); + } @Override - public void onFailure(final Throwable caught) { - callback.onFailure(caught); + public void onFailure(Throwable exception) { + callback.onFailure(exception); } }); } diff --git a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/gwtservices/GenericApiGWTService.java b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/gwtservices/GenericApiGWTService.java index 69d102d..1a0b919 100644 --- a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/gwtservices/GenericApiGWTService.java +++ b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/gwtservices/GenericApiGWTService.java @@ -10,11 +10,11 @@ import org.ovirt.engine.core.common.queries.VdcQueryReturnValue; import org.ovirt.engine.core.common.queries.VdcQueryType; -import com.google.gwt.rpc.client.RpcService; import com.google.gwt.user.client.rpc.RemoteServiceRelativePath; +import com.google.gwt.user.server.rpc.NoXsrfProtect; @RemoteServiceRelativePath("GenericApiGWTService") -public interface GenericApiGWTService extends RpcService { +public interface GenericApiGWTService extends XsrfProtectedRpcService { public VdcQueryReturnValue RunQuery(VdcQueryType search, VdcQueryParametersBase searchParameters); @@ -22,6 +22,7 @@ public VdcReturnValueBase RunAction(VdcActionType actionType, VdcActionParametersBase params); + @NoXsrfProtect public VdcQueryReturnValue RunPublicQuery(VdcQueryType queryType, VdcQueryParametersBase params); @@ -41,8 +42,10 @@ public DbUser getLoggedInUser(); + @NoXsrfProtect public VdcReturnValueBase logOff(DbUser userToLogoff); + @NoXsrfProtect public VdcReturnValueBase Login(String userName, String password, String profileName, VdcActionType loginType); } diff --git a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/gwtservices/XsrfProtectedRpcService.java b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/gwtservices/XsrfProtectedRpcService.java new file mode 100644 index 0000000..87dafd1 --- /dev/null +++ b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/gwtservices/XsrfProtectedRpcService.java @@ -0,0 +1,8 @@ +package org.ovirt.engine.ui.frontend.gwtservices; + +import com.google.gwt.rpc.client.RpcService; +import com.google.gwt.user.server.rpc.XsrfProtect; + +@XsrfProtect +public interface XsrfProtectedRpcService extends RpcService { +} diff --git a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/AbstractXsrfProtectedRpcServlet.java b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/AbstractXsrfProtectedRpcServlet.java new file mode 100644 index 0000000..b8d82ad --- /dev/null +++ b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/AbstractXsrfProtectedRpcServlet.java @@ -0,0 +1,118 @@ +package org.ovirt.engine.ui.frontend.server.gwt; + +import java.io.OutputStream; +import java.lang.reflect.Method; +import java.util.Collections; + +import com.google.gwt.rpc.client.ast.StringValueCommand; +import com.google.gwt.rpc.client.impl.RemoteException; +import com.google.gwt.rpc.server.ClientOracle; +import com.google.gwt.rpc.server.CommandServerSerializationStreamReader; +import com.google.gwt.rpc.server.RpcServlet; +import com.google.gwt.rpc.server.SimplePayloadDecoder; +import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException; +import com.google.gwt.user.client.rpc.RpcToken; +import com.google.gwt.user.client.rpc.SerializationException; +import com.google.gwt.user.server.Util; +import com.google.gwt.user.server.rpc.NoXsrfProtect; +import com.google.gwt.user.server.rpc.RPCRequest; +import com.google.gwt.user.server.rpc.XsrfProtect; + +public abstract class AbstractXsrfProtectedRpcServlet extends RpcServlet { + + /** + * Serial version UID for serialization. + */ + static final long serialVersionUID = -7274292100456700624L; + + /** + * The default constructor used by service implementations that extend this class. The servlet will delegate AJAX + * requests to the appropriate method in the subclass. + */ + public AbstractXsrfProtectedRpcServlet() { + super(); + } + + /** + * The wrapping constructor used by service implementations that are separate from this class. The servlet will + * delegate AJAX requests to the appropriate method in the given object. + * + * @param delegate + * The delegate object. + */ + public AbstractXsrfProtectedRpcServlet(Object delegate) { + super(delegate); + } + + @Override + public void processCall(ClientOracle clientOracle, String payload, OutputStream stream) + throws SerializationException { + try { + SimplePayloadDecoder decoder; + try { + decoder = new SimplePayloadDecoder(clientOracle, payload); + } catch (ClassNotFoundException e) { + throw new IncompatibleRemoteServiceException( + "Client does not have a type sent by the server", e); //$NON-NLS-1$ + } + CommandServerSerializationStreamReader streamReader = new CommandServerSerializationStreamReader(); + if (decoder.getThrownValue() != null) { + streamReader.prepareToRead(Collections.singletonList(decoder.getThrownValue())); + try { + throw new RemoteException((Throwable) streamReader.readObject()); + } catch (ClassCastException e) { + throw new SerializationException( + "The remote end threw something other than a Throwable", e); //$NON-NLS-1$ + } catch (SerializationException e) { + throw new IncompatibleRemoteServiceException( + "The remote end threw an exception which could not be deserialized", //$NON-NLS-1$ + e); + } + } else { + streamReader.prepareToRead(decoder.getValues()); + } + // Read the name of the RemoteService interface + Object firstObject = streamReader.readObject(); + if (firstObject instanceof RpcToken) { + String strippedString = payload.substring(0, 3) + + payload.substring(payload.indexOf(((StringValueCommand)decoder.getValues().get(1)).getValue())); + + super.processCall(clientOracle, strippedString, stream); + } else { + super.processCall(clientOracle, payload, stream); + } + + } catch (SerializationException ex) { + throw new IncompatibleRemoteServiceException(ex.getMessage(), ex); + } + } + + @Override + protected void onAfterRequestDeserialized(RPCRequest rpcRequest) { + if (shouldValidateXsrfToken(rpcRequest.getMethod())) { + validateXsrfToken(rpcRequest.getRpcToken(), rpcRequest.getMethod()); + } + } + + /** + * Override this method to change default XSRF enforcement logic. + * + * @param method + * Method being invoked + * @return {@code true} if XSRF token should be verified, {@code false} otherwise + */ + protected boolean shouldValidateXsrfToken(Method method) { + return Util.isMethodXsrfProtected(method, XsrfProtect.class, + NoXsrfProtect.class, RpcToken.class); + } + + /** + * Override this method to perform XSRF token verification. + * + * @param token + * {@link RpcToken} included with an RPC request. + * @param method + * method being invoked via this RPC call. + */ + protected abstract void validateXsrfToken(RpcToken token, Method method); +} diff --git a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/GenericApiGWTServiceImpl.java b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/GenericApiGWTServiceImpl.java index 68979c0..57f4dc3 100644 --- a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/GenericApiGWTServiceImpl.java +++ b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/GenericApiGWTServiceImpl.java @@ -21,10 +21,9 @@ import org.ovirt.engine.core.common.queries.VdcQueryType; import org.ovirt.engine.ui.frontend.gwtservices.GenericApiGWTService; -import com.google.gwt.rpc.server.RpcServlet; import com.google.gwt.user.client.rpc.SerializationException; -public class GenericApiGWTServiceImpl extends RpcServlet implements GenericApiGWTService { +public class GenericApiGWTServiceImpl extends XsrfProtectedRpcServlet implements GenericApiGWTService { private static final long serialVersionUID = 7395780289048030855L; diff --git a/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/XsrfProtectedRpcServlet.java b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/XsrfProtectedRpcServlet.java new file mode 100644 index 0000000..3081ea6 --- /dev/null +++ b/frontend/webadmin/modules/frontend/src/main/java/org/ovirt/engine/ui/frontend/server/gwt/XsrfProtectedRpcServlet.java @@ -0,0 +1,93 @@ +package org.ovirt.engine.ui.frontend.server.gwt; + +import java.lang.reflect.Method; + +import javax.servlet.ServletException; +import javax.servlet.http.Cookie; + +import com.google.gwt.user.client.rpc.RpcToken; +import com.google.gwt.user.client.rpc.RpcTokenException; +import com.google.gwt.user.client.rpc.XsrfToken; +import com.google.gwt.user.server.Util; +import com.google.gwt.user.server.rpc.XsrfTokenServiceServlet; +import com.google.gwt.util.tools.shared.Md5Utils; +import com.google.gwt.util.tools.shared.StringUtils; + +public class XsrfProtectedRpcServlet extends AbstractXsrfProtectedRpcServlet { + + // Can't use the one from XsrfTokenServiceServlet as it is not public. + static final String COOKIE_NAME_NOT_SET_ERROR_MSG = + "Session cookie name not set! Use context-param to specify session cookie name"; //$NON-NLS-1$ + + String sessionCookieName = null; + + /** + * Default constructor. + */ + public XsrfProtectedRpcServlet() { + this(null); + } + + /** + * Constructor with session cookie name. + * @param cookieName The session cookie name. + */ + public XsrfProtectedRpcServlet(String cookieName) { + this.sessionCookieName = cookieName; + } + + /** + * Constructor with delegate. + * @param delegate The delegate object + */ + public XsrfProtectedRpcServlet(Object delegate) { + this(delegate, null); + } + + /** + * Constructor with cookie name and delegate. + * @param delegate The delegate object + * @param cookieName The name of the session cookie. + */ + public XsrfProtectedRpcServlet(Object delegate, String cookieName) { + super(delegate); + this.sessionCookieName = cookieName; + } + + @Override + public void init() throws ServletException { + super.init(); + // do not overwrite if value is supplied in constructor + if (sessionCookieName == null) { + // servlet configuration precedes context configuration + sessionCookieName = getServletConfig().getInitParameter(XsrfTokenServiceServlet.COOKIE_NAME_PARAM); + if (sessionCookieName == null) { + sessionCookieName = getServletContext().getInitParameter(XsrfTokenServiceServlet.COOKIE_NAME_PARAM); + } + if (sessionCookieName == null) { + throw new IllegalStateException(COOKIE_NAME_NOT_SET_ERROR_MSG); + } + } + } + + @Override + protected void validateXsrfToken(RpcToken token, Method method) { + if (token == null) { + throw new RpcTokenException("XSRF token missing"); //$NON-NLS-1$ + } + Cookie sessionCookie = Util.getCookie(getThreadLocalRequest(), sessionCookieName, false); + if (sessionCookie == null || sessionCookie.getValue() == null + || sessionCookie.getValue().length() == 0) { + throw new RpcTokenException("Session cookie is missing or empty! " + //$NON-NLS-1$ + "Unable to verify XSRF cookie"); //$NON-NLS-1$ + } + + String expectedToken = StringUtils.toHexString( + Md5Utils.getMd5Digest(sessionCookie.getValue().getBytes())); + XsrfToken xsrfToken = (XsrfToken) token; + + if (!expectedToken.equals(xsrfToken.getToken())) { + throw new RpcTokenException("Invalid XSRF token"); //$NON-NLS-1$ + } + } +} diff --git a/frontend/webadmin/modules/frontend/src/main/resources/META-INF/web-fragment.xml b/frontend/webadmin/modules/frontend/src/main/resources/META-INF/web-fragment.xml index 86dabbd7..0f5d44e 100644 --- a/frontend/webadmin/modules/frontend/src/main/resources/META-INF/web-fragment.xml +++ b/frontend/webadmin/modules/frontend/src/main/resources/META-INF/web-fragment.xml @@ -3,6 +3,11 @@ <display-name>Frontend Web Fragment for GWT UI Projects</display-name> + <context-param> + <param-name>gwt.xsrf.session_cookie_name</param-name> + <param-value>JSESSIONID</param-value> + </context-param> + <filter> <filter-name>GwtCachingFilter</filter-name> <filter-class>org.ovirt.engine.ui.frontend.server.gwt.GwtCachingFilter</filter-class> @@ -81,6 +86,10 @@ <servlet-class>org.ovirt.engine.core.branding.BrandingCascadingResourceServlet</servlet-class> </servlet> + <servlet> + <servlet-name>xsrf</servlet-name> + <servlet-class>com.google.gwt.user.server.rpc.XsrfTokenServiceServlet</servlet-class> + </servlet> <!-- PageNotFound Servlet --> <servlet> <servlet-name>PageNotFoundForwardServlet</servlet-name> diff --git a/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/FrontendActionTest.java b/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/FrontendActionTest.java index 3c41bbd..09e6f4a 100644 --- a/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/FrontendActionTest.java +++ b/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/FrontendActionTest.java @@ -44,6 +44,7 @@ import com.google.gwt.event.shared.EventBus; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.StatusCodeException; +import com.google.gwt.user.client.rpc.XsrfTokenServiceAsync; @RunWith(MockitoJUnitRunner.class) /** @@ -63,6 +64,8 @@ @Mock GenericApiGWTServiceAsync mockService; + @Mock + XsrfTokenServiceAsync mockXsrfService; @Mock ErrorTranslator mockVdsmErrorsTranslator; @Mock @@ -99,7 +102,7 @@ @Before public void setUp() throws Exception { fakeScheduler = new FakeGWTScheduler(); - CommunicationProvider communicationsProvider = new GWTRPCCommunicationProvider(mockService); + CommunicationProvider communicationsProvider = new GWTRPCCommunicationProvider(mockService, mockXsrfService); OperationProcessor operationProcessor = new OperationProcessor(communicationsProvider); operationProcessor.setScheduler(fakeScheduler); VdcOperationManager operationsManager = new VdcOperationManager(operationProcessor); diff --git a/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/FrontendTest.java b/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/FrontendTest.java index 1607ced..4c0ac68 100644 --- a/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/FrontendTest.java +++ b/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/FrontendTest.java @@ -41,6 +41,7 @@ import com.google.gwt.event.shared.EventBus; import com.google.gwt.user.client.rpc.AsyncCallback; import com.google.gwt.user.client.rpc.StatusCodeException; +import com.google.gwt.user.client.rpc.XsrfTokenServiceAsync; @RunWith(MockitoJUnitRunner.class) public class FrontendTest { @@ -59,6 +60,8 @@ @Mock GenericApiGWTServiceAsync mockService; + @Mock + XsrfTokenServiceAsync mockXsrfService; @Mock ErrorTranslator mockVdsmErrorsTranslator; @Mock @@ -93,7 +96,7 @@ @Before public void setUp() throws Exception { fakeScheduler = new FakeGWTScheduler(); - CommunicationProvider communicationsProvider = new GWTRPCCommunicationProvider(mockService); + CommunicationProvider communicationsProvider = new GWTRPCCommunicationProvider(mockService, mockXsrfService); OperationProcessor operationProcessor = new OperationProcessor(communicationsProvider); operationProcessor.setScheduler(fakeScheduler); VdcOperationManager operationsManager = new VdcOperationManager(operationProcessor); diff --git a/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/communication/GWTRPCCommunicationProviderTest.java b/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/communication/GWTRPCCommunicationProviderTest.java index a8283c2..d768c1b 100644 --- a/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/communication/GWTRPCCommunicationProviderTest.java +++ b/frontend/webadmin/modules/frontend/src/test/java/org/ovirt/engine/ui/frontend/communication/GWTRPCCommunicationProviderTest.java @@ -24,11 +24,14 @@ import org.ovirt.engine.ui.frontend.gwtservices.GenericApiGWTServiceAsync; import com.google.gwt.user.client.rpc.AsyncCallback; +import com.google.gwt.user.client.rpc.XsrfTokenServiceAsync; @RunWith(MockitoJUnitRunner.class) public class GWTRPCCommunicationProviderTest { @Mock GenericApiGWTServiceAsync mockService; + @Mock + XsrfTokenServiceAsync mockXsrfService; @Mock VdcOperationCallback mockOperationCallbackSingle1; @Mock @@ -53,7 +56,7 @@ @Before public void setUp() throws Exception { - testProvider = new GWTRPCCommunicationProvider(mockService); + testProvider = new GWTRPCCommunicationProvider(mockService, mockXsrfService); } @Test diff --git a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/gin/BaseSystemModule.java b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/gin/BaseSystemModule.java index a37a081..aebd36c 100644 --- a/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/gin/BaseSystemModule.java +++ b/frontend/webadmin/modules/gwt-common/src/main/java/org/ovirt/engine/ui/common/gin/BaseSystemModule.java @@ -25,6 +25,7 @@ import com.google.gwt.event.shared.SimpleEventBus; import com.google.gwt.inject.client.AbstractGinModule; +import com.google.gwt.user.client.rpc.XsrfTokenServiceAsync; import com.google.inject.Singleton; import com.gwtplatform.mvp.client.RootPresenter; import com.gwtplatform.mvp.client.proxy.ParameterTokenFormatter; @@ -66,6 +67,7 @@ bind(OperationProcessor.class).in(Singleton.class); bind(CommunicationProvider.class).to(GWTRPCCommunicationProvider.class).in(Singleton.class); bind(GenericApiGWTServiceAsync.class).in(Singleton.class); + bind(XsrfTokenServiceAsync.class).in(Singleton.class); } protected void bindResourceConfiguration( diff --git a/frontend/webadmin/modules/userportal-gwtp/src/main/webapp/WEB-INF/web.xml b/frontend/webadmin/modules/userportal-gwtp/src/main/webapp/WEB-INF/web.xml index 8abdb78..62c0774 100644 --- a/frontend/webadmin/modules/userportal-gwtp/src/main/webapp/WEB-INF/web.xml +++ b/frontend/webadmin/modules/userportal-gwtp/src/main/webapp/WEB-INF/web.xml @@ -70,6 +70,11 @@ </servlet-mapping> <servlet-mapping> + <servlet-name>xsrf</servlet-name> + <url-pattern>/xsrf</url-pattern> + </servlet-mapping> + + <servlet-mapping> <servlet-name>BrandingServlet</servlet-name> <url-pattern>/theme/*</url-pattern> </servlet-mapping> diff --git a/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml b/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml index 90e8b93..9e92abb 100644 --- a/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml +++ b/frontend/webadmin/modules/webadmin/src/main/webapp/WEB-INF/web.xml @@ -70,6 +70,11 @@ <url-pattern>/GenericApiGWTService</url-pattern> </servlet-mapping> + <servlet-mapping> + <servlet-name>xsrf</servlet-name> + <url-pattern>/xsrf</url-pattern> + </servlet-mapping> + <servlet-mapping> <servlet-name>PluginResourceServlet</servlet-name> <url-pattern>/plugin/*</url-pattern> -- To view, visit http://gerrit.ovirt.org/26408 To unsubscribe, visit http://gerrit.ovirt.org/settings Gerrit-MessageType: newchange Gerrit-Change-Id: Ia4a5ad1f33eb985aa79e1376aecb48ae365d334d Gerrit-PatchSet: 1 Gerrit-Project: ovirt-engine Gerrit-Branch: master Gerrit-Owner: Alexander Wels <aw...@redhat.com> _______________________________________________ Engine-patches mailing list Engine-patches@ovirt.org http://lists.ovirt.org/mailman/listinfo/engine-patches