GEODE-3447 Implement client authorization for the new protocol Implementation of authorization checks for the new protocol.
Project: http://git-wip-us.apache.org/repos/asf/geode/repo Commit: http://git-wip-us.apache.org/repos/asf/geode/commit/a2b678aa Tree: http://git-wip-us.apache.org/repos/asf/geode/tree/a2b678aa Diff: http://git-wip-us.apache.org/repos/asf/geode/diff/a2b678aa Branch: refs/heads/feature/GEODE-3447 Commit: a2b678aa06af73d218da6e9ae8d95f2942d23a7b Parents: c0f6c84 Author: Bruce Schuchardt <[email protected]> Authored: Thu Aug 17 15:22:57 2017 -0700 Committer: Bruce Schuchardt <[email protected]> Committed: Tue Aug 22 14:46:13 2017 -0700 ---------------------------------------------------------------------- .../sockets/ClientProtocolMessageHandler.java | 3 + .../geode/security/NoOpStreamAuthenticator.java | 7 +- .../geode/security/NoOpStreamAuthorizer.java | 26 +++ .../geode/security/StreamAuthenticator.java | 10 +- .../apache/geode/security/StreamAuthorizer.java | 8 + .../GenericProtocolServerConnectionTest.java | 2 +- .../protocol/protobuf/OperationContext.java | 10 +- .../protocol/protobuf/ProtobufOpsProcessor.java | 10 + .../protobuf/ProtobufSimpleAuthenticator.java | 18 +- .../protobuf/ProtobufSimpleAuthorizer.java | 42 ++++ .../protobuf/ProtobufStreamProcessor.java | 1 + .../registry/OperationContextRegistry.java | 39 +++- .../protocol/AuthorizationIntegrationTest.java | 205 +++++++++++++++++++ .../protobuf/ProtobufStreamProcessorTest.java | 1 + 14 files changed, 361 insertions(+), 21 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ClientProtocolMessageHandler.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ClientProtocolMessageHandler.java b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ClientProtocolMessageHandler.java index 0ced3aa..0d1dfd9 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ClientProtocolMessageHandler.java +++ b/geode-core/src/main/java/org/apache/geode/internal/cache/tier/sockets/ClientProtocolMessageHandler.java @@ -15,6 +15,9 @@ package org.apache.geode.internal.cache.tier.sockets; +import org.apache.geode.internal.cache.InternalCache; +import org.apache.geode.security.StreamAuthorizer; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-core/src/main/java/org/apache/geode/security/NoOpStreamAuthenticator.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/security/NoOpStreamAuthenticator.java b/geode-core/src/main/java/org/apache/geode/security/NoOpStreamAuthenticator.java index 0a6dde1..62f49fe 100644 --- a/geode-core/src/main/java/org/apache/geode/security/NoOpStreamAuthenticator.java +++ b/geode-core/src/main/java/org/apache/geode/security/NoOpStreamAuthenticator.java @@ -23,8 +23,6 @@ import java.io.OutputStream; * returns true. */ public class NoOpStreamAuthenticator implements StreamAuthenticator { - - @Override public void receiveMessage(InputStream inputStream, OutputStream outputStream, SecurityManager securityManager) throws IOException { @@ -37,6 +35,11 @@ public class NoOpStreamAuthenticator implements StreamAuthenticator { } @Override + public StreamAuthorizer getAuthorizer() { + return new NoOpStreamAuthorizer(); + } + + @Override public String implementationID() { return "NOOP"; } http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-core/src/main/java/org/apache/geode/security/NoOpStreamAuthorizer.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/security/NoOpStreamAuthorizer.java b/geode-core/src/main/java/org/apache/geode/security/NoOpStreamAuthorizer.java new file mode 100644 index 0000000..1b21576 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/security/NoOpStreamAuthorizer.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.security; + +/** + * An implementation of {@link StreamAuthorizer} that doesn't use its parameters and always returns + * true. + */ +public class NoOpStreamAuthorizer implements StreamAuthorizer { + @Override + public boolean authorize(ResourcePermission permissionRequested) { + return true; + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-core/src/main/java/org/apache/geode/security/StreamAuthenticator.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/security/StreamAuthenticator.java b/geode-core/src/main/java/org/apache/geode/security/StreamAuthenticator.java index 7db1a2b..5cbc4f8 100644 --- a/geode-core/src/main/java/org/apache/geode/security/StreamAuthenticator.java +++ b/geode-core/src/main/java/org/apache/geode/security/StreamAuthenticator.java @@ -14,8 +14,6 @@ */ package org.apache.geode.security; -import org.apache.geode.security.SecurityManager; - import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -46,6 +44,14 @@ public interface StreamAuthenticator { boolean isAuthenticated(); /** + * Return an authorization object which can be used to determine which permissions this stream has + * according to the provided securityManager. + * + * Calling this before authentication has succeeded may result in a null return object. + */ + StreamAuthorizer getAuthorizer(); + + /** * @return a unique identifier for this particular implementation (NOOP, PASSTHROUGH, etc.) */ String implementationID(); http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-core/src/main/java/org/apache/geode/security/StreamAuthorizer.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/security/StreamAuthorizer.java b/geode-core/src/main/java/org/apache/geode/security/StreamAuthorizer.java new file mode 100644 index 0000000..c6cef33 --- /dev/null +++ b/geode-core/src/main/java/org/apache/geode/security/StreamAuthorizer.java @@ -0,0 +1,8 @@ +package org.apache.geode.security; + +/** + * Created by bschuchardt on 8/17/17. + */ +public interface StreamAuthorizer { + boolean authorize(ResourcePermission permissionRequested); +} http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/GenericProtocolServerConnectionTest.java ---------------------------------------------------------------------- diff --git a/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/GenericProtocolServerConnectionTest.java b/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/GenericProtocolServerConnectionTest.java index 383fbf0..6817b13 100644 --- a/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/GenericProtocolServerConnectionTest.java +++ b/geode-core/src/test/java/org/apache/geode/internal/cache/tier/sockets/GenericProtocolServerConnectionTest.java @@ -53,7 +53,7 @@ public class GenericProtocolServerConnectionTest { when(socketMock.getInetAddress()).thenReturn(InetAddress.getByName("localhost")); ClientProtocolMessageHandler clientProtocolMock = mock(ClientProtocolMessageHandler.class); - doThrow(new IOException()).when(clientProtocolMock).receiveMessage(any(), any(), any()); + doThrow(new IOException()).when(clientProtocolMock).receiveMessage(any(), any(), any(), any()); return new GenericProtocolServerConnection(socketMock, mock(InternalCache.class), mock(CachedRegionHelper.class), mock(CacheServerStats.class), 0, 0, "", http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/OperationContext.java ---------------------------------------------------------------------- diff --git a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/OperationContext.java b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/OperationContext.java index 5191007..488dfc4 100644 --- a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/OperationContext.java +++ b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/OperationContext.java @@ -19,6 +19,7 @@ import java.util.function.Function; import org.apache.geode.annotations.Experimental; import org.apache.geode.protocol.operations.OperationHandler; +import org.apache.geode.security.ResourcePermission; @Experimental public class OperationContext<OperationRequest, OperationResponse> { @@ -26,14 +27,17 @@ public class OperationContext<OperationRequest, OperationResponse> { private final Function<ClientProtocol.Request, OperationRequest> fromRequest; private final Function<OperationResponse, ClientProtocol.Response.Builder> toResponse; private final Function<BasicTypes.ErrorResponse, ClientProtocol.Response.Builder> toErrorResponse; + private final ResourcePermission accessPermissionRequired; public OperationContext(Function<ClientProtocol.Request, OperationRequest> fromRequest, OperationHandler<OperationRequest, OperationResponse> operationHandler, - Function<OperationResponse, ClientProtocol.Response.Builder> toResponse) { + Function<OperationResponse, ClientProtocol.Response.Builder> toResponse, + ResourcePermission permissionRequired) { this.operationHandler = operationHandler; this.fromRequest = fromRequest; this.toResponse = toResponse; this.toErrorResponse = OperationContext::makeErrorBuilder; + accessPermissionRequired = permissionRequired; } public static ClientProtocol.Response.Builder makeErrorBuilder( @@ -56,4 +60,8 @@ public class OperationContext<OperationRequest, OperationResponse> { public Function<BasicTypes.ErrorResponse, ClientProtocol.Response.Builder> getToErrorResponse() { return toErrorResponse; } + + public ResourcePermission getAccessPermissionRequired() { + return accessPermissionRequired; + } } http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufOpsProcessor.java ---------------------------------------------------------------------- diff --git a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufOpsProcessor.java b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufOpsProcessor.java index 3619e0d..43601d7 100644 --- a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufOpsProcessor.java +++ b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufOpsProcessor.java @@ -19,6 +19,7 @@ import org.apache.geode.internal.cache.tier.sockets.MessageExecutionContext; import org.apache.geode.internal.cache.tier.sockets.InvalidExecutionContextException; import org.apache.geode.protocol.protobuf.registry.OperationContextRegistry; import org.apache.geode.protocol.protobuf.utilities.ProtobufResponseUtilities; +import org.apache.geode.security.StreamAuthorizer; import org.apache.geode.serialization.SerializationService; /** @@ -51,6 +52,15 @@ public class ProtobufOpsProcessor { ProtocolErrorCode.UNSUPPORTED_OPERATION.codeValue, "Invalid execution context found for operation.")); } + Result result; + if (authorizer.authorize(operationContext.getAccessPermissionRequired())) { + result = operationContext.getOperationHandler().process(serializationService, + operationContext.getFromRequest().apply(request), cache); + } else { + result = Failure.of(ProtobufResponseUtilities.makeErrorResponse( + ProtocolErrorCode.AUTHORIZATION_FAILED.codeValue, + "User isn't authorized for this operation.")); + } builder = (ClientProtocol.Response.Builder) result.map(operationContext.getToResponse(), operationContext.getToErrorResponse()); http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufSimpleAuthenticator.java ---------------------------------------------------------------------- diff --git a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufSimpleAuthenticator.java b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufSimpleAuthenticator.java index 00285bb..4b702ea 100644 --- a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufSimpleAuthenticator.java +++ b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufSimpleAuthenticator.java @@ -18,6 +18,7 @@ import org.apache.geode.management.internal.security.ResourceConstants; import org.apache.geode.security.StreamAuthenticator; import org.apache.geode.security.AuthenticationFailedException; import org.apache.geode.security.SecurityManager; +import org.apache.geode.security.StreamAuthorizer; import java.io.EOFException; import java.io.IOException; @@ -27,6 +28,7 @@ import java.util.Properties; public class ProtobufSimpleAuthenticator implements StreamAuthenticator { private boolean authenticated; + private ProtobufSimpleAuthorizer authorizer = null; @Override public void receiveMessage(InputStream inputStream, OutputStream outputStream, @@ -41,20 +43,28 @@ public class ProtobufSimpleAuthenticator implements StreamAuthenticator { properties.setProperty(ResourceConstants.USER_NAME, authenticationRequest.getUsername()); properties.setProperty(ResourceConstants.PASSWORD, authenticationRequest.getPassword()); + authorizer = null; // authenticating a new user clears current authorizer try { Object principal = securityManager.authenticate(properties); - authenticated = principal != null; + if (principal != null) { + authorizer = new ProtobufSimpleAuthorizer(principal, securityManager); + } } catch (AuthenticationFailedException e) { - authenticated = false; + authorizer = null; } - AuthenticationAPI.SimpleAuthenticationResponse.newBuilder().setAuthenticated(authenticated) + AuthenticationAPI.SimpleAuthenticationResponse.newBuilder().setAuthenticated(isAuthenticated()) .build().writeDelimitedTo(outputStream); } @Override public boolean isAuthenticated() { - return authenticated; + return authorizer != null; + } + + @Override + public StreamAuthorizer getAuthorizer() { + return authorizer; } @Override http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufSimpleAuthorizer.java ---------------------------------------------------------------------- diff --git a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufSimpleAuthorizer.java b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufSimpleAuthorizer.java new file mode 100644 index 0000000..872632a --- /dev/null +++ b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufSimpleAuthorizer.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.protocol.protobuf; + +import java.io.EOFException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Properties; + +import org.apache.geode.security.AuthenticationFailedException; +import org.apache.geode.security.ResourcePermission; +import org.apache.geode.security.SecurityManager; +import org.apache.geode.security.StreamAuthenticator; +import org.apache.geode.security.StreamAuthorizer; + +public class ProtobufSimpleAuthorizer implements StreamAuthorizer { + private final Object authenticatedPrincipal; + private final SecurityManager securityManager; + + public ProtobufSimpleAuthorizer(Object authenticatedPrincipal, SecurityManager securityManager) { + this.authenticatedPrincipal = authenticatedPrincipal; + this.securityManager = securityManager; + } + + @Override + public boolean authorize(ResourcePermission permissionRequested) { + return securityManager.authorize(authenticatedPrincipal, permissionRequested); + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufStreamProcessor.java ---------------------------------------------------------------------- diff --git a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufStreamProcessor.java b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufStreamProcessor.java index accb899..8f39cd5 100644 --- a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufStreamProcessor.java +++ b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/ProtobufStreamProcessor.java @@ -26,6 +26,7 @@ import org.apache.geode.protocol.exception.InvalidProtocolMessageException; import org.apache.geode.protocol.protobuf.registry.OperationContextRegistry; import org.apache.geode.protocol.protobuf.serializer.ProtobufProtocolSerializer; import org.apache.geode.protocol.protobuf.utilities.ProtobufUtilities; +import org.apache.geode.security.StreamAuthorizer; import org.apache.geode.serialization.registry.exception.CodecAlreadyRegisteredForTypeException; /** http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/registry/OperationContextRegistry.java ---------------------------------------------------------------------- diff --git a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/registry/OperationContextRegistry.java b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/registry/OperationContextRegistry.java index dfe975c..6b541eb 100644 --- a/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/registry/OperationContextRegistry.java +++ b/geode-protobuf/src/main/java/org/apache/geode/protocol/protobuf/registry/OperationContextRegistry.java @@ -30,6 +30,7 @@ import org.apache.geode.protocol.protobuf.operations.GetRequestOperationHandler; import org.apache.geode.protocol.protobuf.operations.PutAllRequestOperationHandler; import org.apache.geode.protocol.protobuf.operations.PutRequestOperationHandler; import org.apache.geode.protocol.protobuf.operations.RemoveRequestOperationHandler; +import org.apache.geode.security.ResourcePermission; @Experimental public class OperationContextRegistry { @@ -47,41 +48,57 @@ public class OperationContextRegistry { operationContexts.put(RequestAPICase.GETREQUEST, new OperationContext<>(ClientProtocol.Request::getGetRequest, new GetRequestOperationHandler(), - opsResp -> ClientProtocol.Response.newBuilder().setGetResponse(opsResp))); + opsResp -> ClientProtocol.Response.newBuilder().setGetResponse(opsResp), + new ResourcePermission(ResourcePermission.Resource.DATA, + ResourcePermission.Operation.READ))); operationContexts.put(RequestAPICase.GETALLREQUEST, new OperationContext<>(ClientProtocol.Request::getGetAllRequest, new GetAllRequestOperationHandler(), - opsResp -> ClientProtocol.Response.newBuilder().setGetAllResponse(opsResp))); + opsResp -> ClientProtocol.Response.newBuilder().setGetAllResponse(opsResp), + new ResourcePermission(ResourcePermission.Resource.DATA, + ResourcePermission.Operation.READ))); operationContexts.put(RequestAPICase.PUTREQUEST, new OperationContext<>(ClientProtocol.Request::getPutRequest, new PutRequestOperationHandler(), - opsResp -> ClientProtocol.Response.newBuilder().setPutResponse(opsResp))); + opsResp -> ClientProtocol.Response.newBuilder().setPutResponse(opsResp), + new ResourcePermission(ResourcePermission.Resource.DATA, + ResourcePermission.Operation.WRITE))); operationContexts.put(RequestAPICase.PUTALLREQUEST, new OperationContext<>(ClientProtocol.Request::getPutAllRequest, new PutAllRequestOperationHandler(), - opsResp -> ClientProtocol.Response.newBuilder().setPutAllResponse(opsResp))); + opsResp -> ClientProtocol.Response.newBuilder().setPutAllResponse(opsResp), + new ResourcePermission(ResourcePermission.Resource.DATA, + ResourcePermission.Operation.WRITE))); operationContexts.put(RequestAPICase.REMOVEREQUEST, new OperationContext<>(ClientProtocol.Request::getRemoveRequest, new RemoveRequestOperationHandler(), - opsResp -> ClientProtocol.Response.newBuilder().setRemoveResponse(opsResp))); + opsResp -> ClientProtocol.Response.newBuilder().setRemoveResponse(opsResp), + new ResourcePermission(ResourcePermission.Resource.DATA, + ResourcePermission.Operation.WRITE))); operationContexts.put(RequestAPICase.GETREGIONNAMESREQUEST, new OperationContext<>(ClientProtocol.Request::getGetRegionNamesRequest, new GetRegionNamesRequestOperationHandler(), - opsResp -> ClientProtocol.Response.newBuilder().setGetRegionNamesResponse(opsResp))); + opsResp -> ClientProtocol.Response.newBuilder().setGetRegionNamesResponse(opsResp), + new ResourcePermission(ResourcePermission.Resource.DATA, + ResourcePermission.Operation.READ))); operationContexts.put(RequestAPICase.GETREGIONREQUEST, new OperationContext<>(ClientProtocol.Request::getGetRegionRequest, new GetRegionRequestOperationHandler(), - opsResp -> ClientProtocol.Response.newBuilder().setGetRegionResponse(opsResp))); + opsResp -> ClientProtocol.Response.newBuilder().setGetRegionResponse(opsResp), + new ResourcePermission(ResourcePermission.Resource.DATA, + ResourcePermission.Operation.READ))); - operationContexts.put(RequestAPICase.GETAVAILABLESERVERSREQUEST, new OperationContext<>( - ClientProtocol.Request::getGetAvailableServersRequest, - new GetAvailableServersOperationHandler(), - opsResp -> ClientProtocol.Response.newBuilder().setGetAvailableServersResponse(opsResp))); + operationContexts.put(RequestAPICase.GETAVAILABLESERVERSREQUEST, + new OperationContext<>(ClientProtocol.Request::getGetAvailableServersRequest, + new GetAvailableServersOperationHandler(), + opsResp -> ClientProtocol.Response.newBuilder().setGetAvailableServersResponse(opsResp), + new ResourcePermission(ResourcePermission.Resource.CLUSTER, + ResourcePermission.Operation.READ))); } } http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-protobuf/src/test/java/org/apache/geode/protocol/AuthorizationIntegrationTest.java ---------------------------------------------------------------------- diff --git a/geode-protobuf/src/test/java/org/apache/geode/protocol/AuthorizationIntegrationTest.java b/geode-protobuf/src/test/java/org/apache/geode/protocol/AuthorizationIntegrationTest.java new file mode 100644 index 0000000..9df23d6 --- /dev/null +++ b/geode-protobuf/src/test/java/org/apache/geode/protocol/AuthorizationIntegrationTest.java @@ -0,0 +1,205 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more contributor license + * agreements. See the NOTICE file distributed with this work for additional information regarding + * copyright ownership. The ASF licenses this file to You 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 org.apache.geode.protocol; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; +import java.util.Properties; +import java.util.concurrent.TimeUnit; + +import org.awaitility.Awaitility; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.contrib.java.lang.system.RestoreSystemProperties; +import org.junit.experimental.categories.Category; + +import org.apache.geode.cache.Cache; +import org.apache.geode.cache.CacheFactory; +import org.apache.geode.cache.server.CacheServer; +import org.apache.geode.internal.AvailablePortHelper; +import org.apache.geode.protocol.protobuf.AuthenticationAPI; +import org.apache.geode.protocol.protobuf.ClientProtocol; +import org.apache.geode.protocol.protobuf.ProtobufSerializationService; +import org.apache.geode.protocol.protobuf.ProtocolErrorCode; +import org.apache.geode.protocol.protobuf.RegionAPI; +import org.apache.geode.protocol.protobuf.serializer.ProtobufProtocolSerializer; +import org.apache.geode.protocol.protobuf.utilities.ProtobufUtilities; +import org.apache.geode.security.ResourcePermission; +import org.apache.geode.security.SecurityManager; +import org.apache.geode.serialization.registry.exception.CodecAlreadyRegisteredForTypeException; +import org.apache.geode.test.junit.categories.IntegrationTest; + +@Category(IntegrationTest.class) +public class AuthorizationIntegrationTest { + + private static final String TEST_USERNAME = "bob"; + private static final String TEST_PASSWORD = "bobspassword"; + public static final String TEST_REGION = "testRegion"; + + @Rule + public final RestoreSystemProperties restoreSystemProperties = new RestoreSystemProperties(); + + private Cache cache; + private int cacheServerPort; + private CacheServer cacheServer; + private Socket socket; + private OutputStream outputStream; + private ProtobufSerializationService serializationService; + private InputStream inputStream; + private ProtobufProtocolSerializer protobufProtocolSerializer; + private Object securityPrincipal; + private SecurityManager mockSecurityManager; + private String testRegion; + public static final ResourcePermission READ_PERMISSION = + new ResourcePermission(ResourcePermission.Resource.DATA, ResourcePermission.Operation.READ); + public static final ResourcePermission WRITE_PERMISSION = + new ResourcePermission(ResourcePermission.Resource.DATA, ResourcePermission.Operation.WRITE); + + @Before + public void setUp() throws IOException, CodecAlreadyRegisteredForTypeException { + Properties expectedAuthProperties = new Properties(); + expectedAuthProperties.setProperty("username", TEST_USERNAME); + expectedAuthProperties.setProperty("password", TEST_PASSWORD); + + securityPrincipal = new Object(); + mockSecurityManager = mock(SecurityManager.class); + when(mockSecurityManager.authenticate(expectedAuthProperties)).thenReturn(securityPrincipal); + + Properties properties = new Properties(); + CacheFactory cacheFactory = new CacheFactory(properties); + cacheFactory.set("mcast-port", "0"); // sometimes it isn't due to other tests. + + cacheFactory.setSecurityManager(mockSecurityManager); + cache = cacheFactory.create(); + + cacheServer = cache.addCacheServer(); + cacheServerPort = AvailablePortHelper.getRandomAvailableTCPPort(); + cacheServer.setPort(cacheServerPort); + cacheServer.start(); + + cache.createRegionFactory().create(TEST_REGION); + + System.setProperty("geode.feature-protobuf-protocol", "true"); + System.setProperty("geode.protocol-authentication-mode", "SIMPLE"); + socket = new Socket("localhost", cacheServerPort); + + Awaitility.await().atMost(5, TimeUnit.SECONDS).until(socket::isConnected); + outputStream = socket.getOutputStream(); + inputStream = socket.getInputStream(); + outputStream.write(110); + + serializationService = new ProtobufSerializationService(); + protobufProtocolSerializer = new ProtobufProtocolSerializer(); + + when(mockSecurityManager.authorize(same(securityPrincipal), any())).thenReturn(false); + AuthenticationAPI.SimpleAuthenticationRequest authenticationRequest = + AuthenticationAPI.SimpleAuthenticationRequest.newBuilder().setUsername(TEST_USERNAME) + .setPassword(TEST_PASSWORD).build(); + authenticationRequest.writeDelimitedTo(outputStream); + + AuthenticationAPI.SimpleAuthenticationResponse authenticationResponse = + AuthenticationAPI.SimpleAuthenticationResponse.parseDelimitedFrom(inputStream); + assertTrue(authenticationResponse.getAuthenticated()); + } + + @After + public void shutDown() throws IOException { + cache.close(); + socket.close(); + } + + + @Test + public void validateNoPermissions() throws Exception { + when(mockSecurityManager.authorize(securityPrincipal, READ_PERMISSION)).thenReturn(false); + when(mockSecurityManager.authorize(securityPrincipal, WRITE_PERMISSION)).thenReturn(false); + + verifyOperations(false, false); + } + + @Test + public void validateWritePermission() throws Exception { + when(mockSecurityManager.authorize(securityPrincipal, READ_PERMISSION)).thenReturn(false); + when(mockSecurityManager.authorize(securityPrincipal, WRITE_PERMISSION)).thenReturn(true); + + verifyOperations(false, true); + } + + @Test + public void validateReadPermission() throws Exception { + when(mockSecurityManager.authorize(securityPrincipal, READ_PERMISSION)).thenReturn(true); + when(mockSecurityManager.authorize(securityPrincipal, WRITE_PERMISSION)).thenReturn(false); + + verifyOperations(true, false); + } + + @Test + public void validateReadAndWritePermission() throws Exception { + when(mockSecurityManager.authorize(securityPrincipal, READ_PERMISSION)).thenReturn(true); + when(mockSecurityManager.authorize(securityPrincipal, WRITE_PERMISSION)).thenReturn(true); + + verifyOperations(true, true); + } + + private void verifyOperations(boolean readAllowed, boolean writeAllowed) throws Exception { + ClientProtocol.Message getRegionsMessage = + ClientProtocol.Message.newBuilder().setRequest(ClientProtocol.Request.newBuilder() + .setGetRegionNamesRequest(RegionAPI.GetRegionNamesRequest.newBuilder())).build(); + validateOperationAuthorized(getRegionsMessage, inputStream, outputStream, + readAllowed ? ClientProtocol.Response.ResponseAPICase.GETREGIONNAMESRESPONSE + : ClientProtocol.Response.ResponseAPICase.ERRORRESPONSE); + + ClientProtocol.Message putMessage = ClientProtocol.Message.newBuilder() + .setRequest(ClientProtocol.Request.newBuilder() + .setPutRequest(RegionAPI.PutRequest.newBuilder().setRegionName(TEST_REGION).setEntry( + ProtobufUtilities.createEntry(serializationService, "TEST_KEY", "TEST_VALUE")))) + .build(); + validateOperationAuthorized(putMessage, inputStream, outputStream, + writeAllowed ? ClientProtocol.Response.ResponseAPICase.PUTRESPONSE + : ClientProtocol.Response.ResponseAPICase.ERRORRESPONSE); + + ClientProtocol.Message removeMessage = ClientProtocol.Message.newBuilder() + .setRequest(ClientProtocol.Request.newBuilder() + .setRemoveRequest(RegionAPI.RemoveRequest.newBuilder().setRegionName(TEST_REGION) + .setKey(ProtobufUtilities.createEncodedValue(serializationService, "TEST_KEY")))) + .build(); + validateOperationAuthorized(removeMessage, inputStream, outputStream, + writeAllowed ? ClientProtocol.Response.ResponseAPICase.REMOVERESPONSE + : ClientProtocol.Response.ResponseAPICase.ERRORRESPONSE); + } + + private void validateOperationAuthorized(ClientProtocol.Message message, InputStream inputStream, + OutputStream outputStream, ClientProtocol.Response.ResponseAPICase expectedResponseType) + throws Exception { + protobufProtocolSerializer.serialize(message, outputStream); + ClientProtocol.Message response = protobufProtocolSerializer.deserialize(inputStream); + assertEquals(expectedResponseType, response.getResponse().getResponseAPICase()); + if (expectedResponseType == ClientProtocol.Response.ResponseAPICase.ERRORRESPONSE) { + assertEquals(ProtocolErrorCode.AUTHORIZATION_FAILED.codeValue, + response.getResponse().getErrorResponse().getError().getErrorCode()); + } + } +} http://git-wip-us.apache.org/repos/asf/geode/blob/a2b678aa/geode-protobuf/src/test/java/org/apache/geode/protocol/protobuf/ProtobufStreamProcessorTest.java ---------------------------------------------------------------------- diff --git a/geode-protobuf/src/test/java/org/apache/geode/protocol/protobuf/ProtobufStreamProcessorTest.java b/geode-protobuf/src/test/java/org/apache/geode/protocol/protobuf/ProtobufStreamProcessorTest.java index 16eb48b..668d280 100644 --- a/geode-protobuf/src/test/java/org/apache/geode/protocol/protobuf/ProtobufStreamProcessorTest.java +++ b/geode-protobuf/src/test/java/org/apache/geode/protocol/protobuf/ProtobufStreamProcessorTest.java @@ -27,6 +27,7 @@ import org.junit.experimental.categories.Category; import org.apache.geode.internal.cache.InternalCache; import org.apache.geode.internal.cache.tier.sockets.MessageExecutionContext; +import org.apache.geode.security.StreamAuthorizer; import org.apache.geode.test.junit.categories.UnitTest; @Category(UnitTest.class)
