Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 8E010200BAD for ; Tue, 11 Oct 2016 00:08:42 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 8C623160AF1; Mon, 10 Oct 2016 22:08:42 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 56287160AE1 for ; Tue, 11 Oct 2016 00:08:40 +0200 (CEST) Received: (qmail 19765 invoked by uid 500); 10 Oct 2016 22:08:39 -0000 Mailing-List: contact commits-help@geode.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@geode.incubator.apache.org Delivered-To: mailing list commits@geode.incubator.apache.org Received: (qmail 19756 invoked by uid 99); 10 Oct 2016 22:08:39 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd1-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 10 Oct 2016 22:08:39 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd1-us-west.apache.org (ASF Mail Server at spamd1-us-west.apache.org) with ESMTP id 05065C31E9 for ; Mon, 10 Oct 2016 22:08:39 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd1-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -6.219 X-Spam-Level: X-Spam-Status: No, score=-6.219 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, KAM_LAZY_DOMAIN_SECURITY=1, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-2.999] autolearn=disabled Received: from mx1-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd1-us-west.apache.org [10.40.0.7]) (amavisd-new, port 10024) with ESMTP id drKVikCSAxdI for ; Mon, 10 Oct 2016 22:08:33 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-lw-eu.apache.org (ASF Mail Server at mx1-lw-eu.apache.org) with SMTP id 024E45F610 for ; Mon, 10 Oct 2016 22:08:30 +0000 (UTC) Received: (qmail 19485 invoked by uid 99); 10 Oct 2016 22:08:30 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 10 Oct 2016 22:08:30 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 04E0FE04AF; Mon, 10 Oct 2016 22:08:30 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: jinmeiliao@apache.org To: commits@geode.incubator.apache.org Date: Mon, 10 Oct 2016 22:08:30 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [2/6] incubator-geode git commit: GEODE-1570 - developer REST API should be secured archived-at: Mon, 10 Oct 2016 22:08:42 -0000 GEODE-1570 - developer REST API should be secured * Merged with develop after org.apache package rename * Moved classes to internal. * this closes #251 Project: http://git-wip-us.apache.org/repos/asf/incubator-geode/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-geode/commit/5ed443d5 Tree: http://git-wip-us.apache.org/repos/asf/incubator-geode/tree/5ed443d5 Diff: http://git-wip-us.apache.org/repos/asf/incubator-geode/diff/5ed443d5 Branch: refs/heads/develop Commit: 5ed443d5bf8be3943fa76ce457f9592df5eb8317 Parents: a0acc3c Author: Kevin Duling Authored: Wed Sep 21 08:50:46 2016 -0700 Committer: Jinmei Liao Committed: Mon Oct 10 15:07:07 2016 -0700 ---------------------------------------------------------------------- .../internal/web/RestSecurityDUnitTest.java | 247 +++++++++-- .../web/RestSecurityEndpointsDUnitTest.java | 422 +++++++++++++++++++ .../src/main/webapp/WEB-INF/spring-security.xml | 2 +- geode-web-api/build.gradle | 6 +- .../web/controllers/AbstractBaseController.java | 54 ++- .../web/controllers/BaseControllerAdvice.java | 78 +++- .../web/controllers/CommonCrudController.java | 87 ++-- .../controllers/FunctionAccessController.java | 36 +- .../web/controllers/PdxBasedCrudController.java | 44 +- .../web/controllers/QueryAccessController.java | 108 ++--- .../web/security/GeodeAuthentication.java | 37 ++ .../security/GeodeAuthenticationProvider.java | 56 +++ .../internal/web/security/GeodeAuthority.java | 47 +++ .../web/security/RestSecurityConfiguration.java | 76 ++++ .../src/main/webapp/WEB-INF/geode-servlet.xml | 11 +- geode-web-api/src/main/webapp/WEB-INF/web.xml | 20 +- gradle/dependency-versions.properties | 4 +- 17 files changed, 1116 insertions(+), 219 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5ed443d5/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityDUnitTest.java ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityDUnitTest.java b/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityDUnitTest.java index df146a6..a9d90ed 100644 --- a/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityDUnitTest.java +++ b/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityDUnitTest.java @@ -22,75 +22,236 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import org.apache.geode.internal.AvailablePortHelper; +import org.apache.geode.security.AbstractSecureServerDUnitTest; +import org.apache.geode.test.junit.categories.DistributedTest; +import org.apache.geode.test.junit.categories.SecurityTest; import org.apache.http.HttpEntity; +import org.apache.http.HttpHost; +import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.AuthCache; import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.auth.BasicScheme; +import org.apache.http.impl.client.BasicAuthCache; +import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; -import org.json.JSONArray; -import org.json.JSONException; -import org.junit.Test; +import org.json.JSONTokener; import org.junit.experimental.categories.Category; -import org.apache.geode.internal.AvailablePortHelper; -import org.apache.geode.security.AbstractSecureServerDUnitTest; -import org.apache.geode.test.junit.categories.DistributedTest; -import org.apache.geode.test.junit.categories.SecurityTest; -@Category({ DistributedTest.class, SecurityTest.class}) +@Category({ DistributedTest.class, SecurityTest.class }) public class RestSecurityDUnitTest extends AbstractSecureServerDUnitTest { - private String endPoint = null; - public RestSecurityDUnitTest(){ + + public final static String PROTOCOL = "http"; + public final static String HOSTNAME = "localhost"; + public final static String CONTEXT = "/geode/v1"; + + private final String endPoint; + private final URL url; + + public RestSecurityDUnitTest() throws MalformedURLException { int[] ports = AvailablePortHelper.getRandomAvailableTCPPorts(2); this.jmxPort = ports[0]; this.restPort = ports[1]; - endPoint = "http://localhost:"+restPort+"/gemfire-api/v1"; + endPoint = PROTOCOL + "://" + HOSTNAME + ":" + restPort + CONTEXT; + url = new URL(endPoint); + } + + protected HttpResponse doHEAD(String query, String username, String password) throws MalformedURLException { + HttpHost targetHost = new HttpHost(url.getHost(), url.getPort(), url.getProtocol()); + HttpClientContext clientContext = HttpClientContext.create(); + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()), new UsernamePasswordCredentials(username, password)); + CloseableHttpClient httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build(); + AuthCache authCache = new BasicAuthCache(); + BasicScheme basicAuth = new BasicScheme(); + authCache.put(targetHost, basicAuth); + clientContext.setCredentialsProvider(credsProvider); + clientContext.setAuthCache(authCache); + + HttpHead httpHead = new HttpHead(CONTEXT + query); + try { + return httpclient.execute(targetHost, httpHead, clientContext); + } catch (ClientProtocolException e) { + e.printStackTrace(); + fail("Rest HEAD should not have thrown ClientProtocolException!"); + } catch (IOException e) { + e.printStackTrace(); + fail("Rest HEAD Request should not have thrown IOException!"); + } + return null; + } + + protected HttpResponse doGet(String query, String username, String password) throws MalformedURLException { + HttpHost targetHost = new HttpHost(url.getHost(), url.getPort(), url.getProtocol()); + CloseableHttpClient httpclient = HttpClients.custom().build(); + HttpClientContext clientContext = HttpClientContext.create(); +// // if username or password are null or empty, do not put in authentication +// if (!(username == null +// || password == null +// || !username.isEmpty() +// || !password.isEmpty())) { + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()), new UsernamePasswordCredentials(username, password)); + httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build(); + AuthCache authCache = new BasicAuthCache(); + BasicScheme basicAuth = new BasicScheme(); + authCache.put(targetHost, basicAuth); + clientContext.setCredentialsProvider(credsProvider); + clientContext.setAuthCache(authCache); +// } + + HttpGet getRequest = new HttpGet(CONTEXT + query); + try { + return httpclient.execute(targetHost, getRequest, clientContext); + } catch (ClientProtocolException e) { + e.printStackTrace(); + fail("Rest GET should not have thrown ClientProtocolException!"); + } catch (IOException e) { + e.printStackTrace(); + fail("Rest GET Request should not have thrown IOException!"); + } + return null; } - @Test - public void test(){ - client1.invoke(()->{ - JSONArray response = doGet("/servers"); - assertEquals(response.length(), 1); - assertEquals(response.get(0), "http://localhost:"+this.restPort); - }); + + protected HttpResponse doDelete(String query, String username, String password) throws MalformedURLException { + HttpHost targetHost = new HttpHost(url.getHost(), url.getPort(), url.getProtocol()); + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()), new UsernamePasswordCredentials(username, password)); + CloseableHttpClient httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build(); + AuthCache authCache = new BasicAuthCache(); + BasicScheme basicAuth = new BasicScheme(); + authCache.put(targetHost, basicAuth); + + HttpClientContext clientContext = HttpClientContext.create(); + clientContext.setCredentialsProvider(credsProvider); + clientContext.setAuthCache(authCache); + + HttpDelete httpDelete = new HttpDelete(CONTEXT + query); + try { + return httpclient.execute(targetHost, httpDelete, clientContext); + } catch (ClientProtocolException e) { + e.printStackTrace(); + fail("Rest DELETE Request should not have thrown ClientProtocolException!"); + } catch (IOException e) { + e.printStackTrace(); + fail("Rest DELETE Request should not have thrown IOException!"); + } + return null; } + protected HttpResponse doPost(String query, String username, String password, String body) throws MalformedURLException { + HttpHost targetHost = new HttpHost(url.getHost(), url.getPort(), url.getProtocol()); + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()), new UsernamePasswordCredentials(username, password)); + CloseableHttpClient httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build(); + AuthCache authCache = new BasicAuthCache(); + BasicScheme basicAuth = new BasicScheme(); + authCache.put(targetHost, basicAuth); - private JSONArray doGet(String uri) { - HttpGet get = new HttpGet(endPoint + uri); - get.addHeader("Content-Type", "application/json"); - get.addHeader("Accept", "application/json"); - CloseableHttpClient httpclient = HttpClients.createDefault(); - CloseableHttpResponse response; + HttpClientContext clientContext = HttpClientContext.create(); + clientContext.setCredentialsProvider(credsProvider); + clientContext.setAuthCache(authCache); + HttpPost httpPost = new HttpPost(CONTEXT + query); + httpPost.addHeader("content-type", "application/json"); + httpPost.setEntity(new StringEntity(body, StandardCharsets.UTF_8)); try { - response = httpclient.execute(get); - HttpEntity entity = response.getEntity(); - InputStream content = entity.getContent(); - BufferedReader reader = new BufferedReader(new InputStreamReader( - content)); - String line; - StringBuffer str = new StringBuffer(); - while ((line = reader.readLine()) != null) { - str.append(line); - } - - //validate the satus code - assertEquals(response.getStatusLine().getStatusCode(), 200); - return new JSONArray(str.toString()); + return httpclient.execute(targetHost, httpPost, clientContext); } catch (ClientProtocolException e) { e.printStackTrace(); - fail(" Rest Request should not have thrown ClientProtocolException!"); + fail("Rest POST Request should not have thrown ClientProtocolException!"); } catch (IOException e) { e.printStackTrace(); - fail(" Rest Request should not have thrown IOException!"); - } catch (JSONException e) { + fail("Rest POST Request should not have thrown IOException!"); + } + return null; + } + protected HttpResponse doPut(String query, String username, String password, String body) throws MalformedURLException { + HttpHost targetHost = new HttpHost(url.getHost(), url.getPort(), url.getProtocol()); + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()), new UsernamePasswordCredentials(username, password)); + CloseableHttpClient httpclient = HttpClients.custom().setDefaultCredentialsProvider(credsProvider).build(); + AuthCache authCache = new BasicAuthCache(); + BasicScheme basicAuth = new BasicScheme(); + authCache.put(targetHost, basicAuth); + + HttpClientContext clientContext = HttpClientContext.create(); + clientContext.setCredentialsProvider(credsProvider); + clientContext.setAuthCache(authCache); + + HttpPut httpPut = new HttpPut(CONTEXT + query); + httpPut.addHeader("content-type", "application/json"); + httpPut.setEntity(new StringEntity(body, StandardCharsets.UTF_8)); + try { + return httpclient.execute(targetHost, httpPut, clientContext); + } catch (ClientProtocolException e) { e.printStackTrace(); - fail(" Rest Request should not have thrown JSONException!"); + fail("Rest PUT Request should not have thrown ClientProtocolException!"); + } catch (IOException e) { + e.printStackTrace(); + fail("Rest PUT Request should not have thrown IOException!"); } return null; } + /** + * Check the HTTP status of the response and return if it's within the OK range + * @param response The HttpResponse message received from the server + * + * @return true if the status code is a 2XX-type code (200-299), otherwise false + */ + protected boolean isOK(HttpResponse response) { + int returnCode = response.getStatusLine().getStatusCode(); + return (returnCode < 300 && returnCode >= 200); + } + + /** + * Check the HTTP status of the response and return true if a 401 + * @param response The HttpResponse message received from the server + * + * @return true if the status code is 401, otherwise false + */ + protected boolean isUnauthorized(HttpResponse response) { + int returnCode = response.getStatusLine().getStatusCode(); + return returnCode == 401; + } + + /** + * Retrieve the status code of the HttpResponse + * @param response The HttpResponse message received from the server + * + * @return a numeric value + */ + protected int getCode(HttpResponse response) { + return response.getStatusLine().getStatusCode(); + } + + protected JSONTokener getResponseBody(HttpResponse response) throws IOException { + HttpEntity entity = response.getEntity(); + InputStream content = entity.getContent(); + BufferedReader reader = new BufferedReader(new InputStreamReader( + content)); + String line; + StringBuilder str = new StringBuilder(); + while ((line = reader.readLine()) != null) { + str.append(line); + } + return new JSONTokener(str.toString()); + } } http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5ed443d5/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityEndpointsDUnitTest.java ---------------------------------------------------------------------- diff --git a/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityEndpointsDUnitTest.java b/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityEndpointsDUnitTest.java new file mode 100644 index 0000000..149a905 --- /dev/null +++ b/geode-assembly/src/test/java/org/apache/geode/rest/internal/web/RestSecurityEndpointsDUnitTest.java @@ -0,0 +1,422 @@ +/* + * 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.rest.internal.web; + +import static org.junit.Assert.*; + +import java.net.MalformedURLException; + +import org.apache.geode.test.junit.categories.DistributedTest; +import org.apache.geode.test.junit.categories.SecurityTest; +import org.apache.http.HttpResponse; +import org.json.JSONArray; +import org.json.JSONObject; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings("serial") +@Category({ DistributedTest.class, SecurityTest.class }) +public class RestSecurityEndpointsDUnitTest extends RestSecurityDUnitTest { + + Logger logger = LoggerFactory.getLogger(RestSecurityEndpointsDUnitTest.class); + + public RestSecurityEndpointsDUnitTest() throws MalformedURLException { + super(); + } + + @Test + public void testFunctions() { + client1.invoke(() -> { + String json = "{\"@type\":\"double\",\"@value\":210}"; + + HttpResponse response = doGet("/functions", "unknown-user", "1234567"); + assertEquals(401, getCode(response)); + response = doGet("/functions", "stranger", "1234567"); + assertEquals(403, getCode(response)); + response = doGet("/functions", "dataReader", "1234567"); + assertTrue(isOK(response)); + + response = doPost("/functions/AddFreeItemsToOrder", "unknown-user", "1234567", json); + assertEquals(401, getCode(response)); + response = doPost("/functions/AddFreeItemsToOrder", "dataReader", "1234567", json); + assertEquals(403, getCode(response)); + response = doPost("/functions/AddFreeItemsToOrder?onRegion=" + REGION_NAME, "dataWriter", "1234567", json); + // because we're only testing the security of the endpoint, not the endpoint functionality, a 500 is acceptable + assertEquals(500, getCode(response)); + }); + } + + @Test + public void testQueries() { + client1.invoke(() -> { + HttpResponse response = doGet("/queries", "unknown-user", "1234567"); + assertEquals(401, getCode(response)); + response = doGet("/queries", "stranger", "1234567"); + assertEquals(403, getCode(response)); + response = doGet("/queries", "dataReader", "1234567"); + assertEquals(200, getCode(response)); + }); + } + + @Test + public void testAdhocQuery() { + client1.invoke(() -> { + HttpResponse response = doGet("/queries/adhoc?q=", "unknown-user", "1234567"); + assertEquals(401, getCode(response)); + response = doGet("/queries/adhoc?q=", "stranger", "1234567"); + assertEquals(403, getCode(response)); + response = doGet("/queries/adhoc?q=", "dataReader", "1234567"); + // because we're only testing the security of the endpoint, not the endpoint functionality, a 500 is acceptable + assertEquals(500, getCode(response)); + }); + } + + @Test + public void testPostQuery() { + client1.invoke(() -> { + HttpResponse response = doPost("/queries?id=0&q=", "unknown-user", "1234567", ""); + assertEquals(401, getCode(response)); + response = doPost("/queries?id=0&q=", "stranger", "1234567", ""); + assertEquals(403, getCode(response)); + response = doPost("/queries?id=0&q=", "dataWriter", "1234567", ""); + // because we're only testing the security of the endpoint, not the endpoint functionality, a 500 is acceptable + assertEquals(500, getCode(response)); + }); + } + + @Test + public void testPostQuery2() { + client1.invoke(() -> { + HttpResponse response = doPost("/queries/id", "unknown-user", "1234567", "{\"id\" : \"foo\"}"); + assertEquals(401, getCode(response)); + response = doPost("/queries/id", "stranger", "1234567", "{\"id\" : \"foo\"}"); + assertEquals(403, getCode(response)); + response = doPost("/queries/id", "dataWriter", "1234567", "{\"id\" : \"foo\"}"); + // because we're only testing the security of the endpoint, not the endpoint functionality, a 500 is acceptable + assertEquals(500, getCode(response)); + }); + } + + @Test + public void testPutQuery() { + client1.invoke(() -> { + HttpResponse response = doPut("/queries/id", "unknown-user", "1234567", "{\"id\" : \"foo\"}"); + assertEquals(401, getCode(response)); + response = doPut("/queries/id", "stranger", "1234567", "{\"id\" : \"foo\"}"); + assertEquals(403, getCode(response)); + response = doPut("/queries/id", "dataWriter", "1234567", "{\"id\" : \"foo\"}"); + // We should get a 404 because we're trying to update a query that doesn't exist + assertEquals(404, getCode(response)); + }); + } + + @Test + public void testDeleteQuery() { + client1.invoke(() -> { + HttpResponse response = doDelete("/queries/id", "unknown-user", "1234567"); + assertEquals(401, getCode(response)); + response = doDelete("/queries/id", "stranger", "1234567"); + assertEquals(403, getCode(response)); + response = doDelete("/queries/id", "dataWriter", "1234567"); + // We should get a 404 because we're trying to delete a query that doesn't exist + assertEquals(404, getCode(response)); + }); + } + + @Test + public void testServers() { + client1.invoke(() -> { + HttpResponse response = doGet("/servers", "unknown-user", "1234567"); + assertEquals(401, getCode(response)); + response = doGet("/servers", "stranger", "1234567"); + assertEquals(403, getCode(response)); + response = doGet("/servers", "super-user", "1234567"); + assertTrue(isOK(response)); + }); + } + + /** + * This test should always return an OK, whether the user is known or unknown. A phishing script should not be + * able to determine whether a user/password combination is good + */ + @Test + public void testPing() { + client1.invoke(() -> { + HttpResponse response = doHEAD("/ping", "stranger", "1234567"); + assertTrue(isOK(response)); + response = doGet("/ping", "stranger", "1234567"); + assertTrue(isOK(response)); + + response = doHEAD("/ping", "super-user", "1234567"); + assertTrue(isOK(response)); + response = doGet("/ping", "super-user", "1234567"); + assertTrue(isOK(response)); + + // TODO - invalid username/password should still respond, but doesn't + // response = doHEAD("/ping", "unknown-user", "badpassword"); + // assertTrue(isOK(response)); + // response = doGet("/ping", "unknown-user", "badpassword"); + // assertTrue(isOK(response)); + + // TODO - credentials are currently required and shouldn't be for this endpoint + // response = doHEAD("/ping", null, null); + // assertTrue(isOK(response)); + // response = doGet("/ping", null, null); + // assertTrue(isOK(response)); + }); + } + + /** + * Test permissions on retrieving a list of regions. + */ + @Test + public void getRegions() { + client1.invoke(() -> { + HttpResponse response = doGet("", "dataReader", "1234567"); + assertEquals("A '200 - OK' was expected", 200, getCode(response)); + + assertTrue(isOK(response)); + JSONObject jsonObject = new JSONObject(getResponseBody(response)); + JSONArray regions = jsonObject.getJSONArray("regions"); + assertNotNull(regions); + assertTrue(regions.length() > 0); + JSONObject region = regions.getJSONObject(0); + assertEquals("AuthRegion", region.get("name")); + assertEquals("REPLICATE", region.get("type")); + }); + + // List regions with an unknown user - 401 + client1.invoke(() -> { + HttpResponse response = doGet("", "unknown-user", "badpassword"); + assertEquals(401, getCode(response)); + }); + + // list regions with insufficent rights - 403 + client1.invoke(() -> { + HttpResponse response = doGet("", "authRegionReader", "1234567"); + assertEquals(403, getCode(response)); + }); + } + + /** + * Test permissions on getting a region + */ + @Test + public void getRegion() { + // Test an unknown user - 401 error + client1.invoke(() -> { + HttpResponse response = doGet("/" + REGION_NAME, "unknown-user", "1234567"); + assertEquals(401, getCode(response)); + }); + + // Test a user with insufficient rights - 403 + client1.invoke(() -> { + HttpResponse response = doGet("/" + REGION_NAME, "stranger", "1234567"); + assertEquals(403, getCode(response)); + }); + + // Test an authorized user - 200 + client1.invoke(() -> { + HttpResponse response = doGet("/" + REGION_NAME, "super-user", "1234567"); + assertTrue(isOK(response)); + }); + } + + /** + * Test permissions on HEAD region + */ + @Test + public void headRegion() { + // Test an unknown user - 401 error + client1.invoke(() -> { + HttpResponse response = doHEAD("/" + REGION_NAME, "unknown-user", "1234567"); + assertEquals(401, getCode(response)); + }); + + // Test a user with insufficient rights - 403 + client1.invoke(() -> { + HttpResponse response = doHEAD("/" + REGION_NAME, "stranger", "1234567"); + assertEquals(403, getCode(response)); + }); + + // Test an authorized user - 200 + client1.invoke(() -> { + HttpResponse response = doHEAD("/" + REGION_NAME, "super-user", "1234567"); + assertTrue(isOK(response)); + }); + } + + /** + * Test permissions on deleting a region + */ + @Test + public void deleteRegion() { + // Test an unknown user - 401 error + client1.invoke(() -> { + HttpResponse response = doDelete("/" + REGION_NAME, "unknown-user", "1234567"); + assertEquals(401, getCode(response)); + }); + + // Test a user with insufficient rights - 403 + client1.invoke(() -> { + HttpResponse response = doDelete("/" + REGION_NAME, "dataReader", "1234567"); + assertEquals(403, getCode(response)); + }); + + // Test an authorized user - 200 + client1.invoke(() -> { + HttpResponse response = doDelete("/" + REGION_NAME, "super-user", "1234567"); + assertTrue(isOK(response)); + }); + } + + /** + * Test permissions on getting a region's keys + */ + @Test + public void getRegionKeys() { + // Test an authorized user + client1.invoke(() -> { + HttpResponse response = doGet("/" + REGION_NAME + "/keys", "super-user", "1234567"); + assertTrue(isOK(response)); + }); + // Test an unauthorized user + client1.invoke(() -> { + HttpResponse response = doGet("/" + REGION_NAME + "/keys", "dataWriter", "1234567"); + assertEquals(403, getCode(response)); + }); + } + + /** + * Test permissions on retrieving a key from a region + */ + @Test + public void getRegionKey() { + // Test an authorized user + client1.invoke(() -> { + HttpResponse response = doGet("/" + REGION_NAME + "/key1", "key1User", "1234567"); + assertTrue(isOK(response)); + }); + // Test an unauthorized user + client1.invoke(() -> { + HttpResponse response = doGet("/" + REGION_NAME + "/key1", "dataWriter", "1234567"); + assertEquals(403, getCode(response)); + }); + } + + /** + * Test permissions on deleting a region's key(s) + */ + @Test + public void deleteRegionKey() { + // Test an unknown user - 401 error + client1.invoke(() -> { + HttpResponse response = doDelete("/" + REGION_NAME + "/key1", "unknown-user", "1234567"); + assertEquals(401, getCode(response)); + }); + + // Test a user with insufficient rights - 403 + client1.invoke(() -> { + HttpResponse response = doDelete("/" + REGION_NAME + "/key1", "dataReader", "1234567"); + assertEquals(403, getCode(response)); + }); + + // Test an authorized user - 200 + client1.invoke(() -> { + HttpResponse response = doDelete("/" + REGION_NAME + "/key1", "key1User", "1234567"); + assertTrue(isOK(response)); + }); + } + + /** + * Test permissions on deleting a region's key(s) + */ + @Test + public void postRegionKey() { + // Test an unknown user - 401 error + client1.invoke(() -> { + HttpResponse response = doPost("/" + REGION_NAME + "?key9", "unknown", "1234567", "{ \"key9\" : \"foo\" }"); + assertEquals(401, getCode(response)); + }); + + // Test a user with insufficient rights - 403 + client1.invoke(() -> { + HttpResponse response = doPost("/" + REGION_NAME + "?key9", "dataReader", "1234567", "{ \"key9\" : \"foo\" }"); + assertEquals(403, getCode(response)); + }); + + // Test an authorized user - 200 + client1.invoke(() -> { + HttpResponse response = doPost("/" + REGION_NAME + "?key9", "dataWriter", "1234567", "{ \"key9\" : \"foo\" }"); + assertEquals(201, getCode(response)); + assertTrue(isOK(response)); + }); + } + + /** + * Test permissions on deleting a region's key(s) + */ + @Test + public void putRegionKey() { + + String json = "{\"@type\":\"com.gemstone.gemfire.web.rest.domain.Order\",\"purchaseOrderNo\":1121,\"customerId\":1012,\"description\":\"Order for XYZ Corp\",\"orderDate\":\"02/10/2014\",\"deliveryDate\":\"02/20/2014\",\"contact\":\"Jelly Bean\",\"email\":\"jelly.bean@example.com\",\"phone\":\"01-2048096\",\"items\":[{\"itemNo\":1,\"description\":\"Product-100\",\"quantity\":12,\"unitPrice\":5,\"totalPrice\":60}],\"totalPrice\":225}"; + String casJSON = "{\"@old\":{\"@type\":\"com.gemstone.gemfire.web.rest.domain.Order\",\"purchaseOrderNo\":1121,\"customerId\":1012,\"description\":\"Order for XYZ Corp\",\"orderDate\":\"02/10/2014\",\"deliveryDate\":\"02/20/2014\",\"contact\":\"Jelly Bean\",\"email\":\"jelly.bean@example.com\",\"phone\":\"01-2048096\",\"items\":[{\"itemNo\":1,\"description\":\"Product-100\",\"quantity\":12,\"unitPrice\":5,\"totalPrice\":60}],\"totalPrice\":225},\"@new \":{\"@type\":\"com.gemstone.gemfire.web.rest.domain.Order\",\"purchaseOrderNo\":1121,\"customerId\":1013,\"description\":\"Order for New Corp\",\"orderDate\":\"02/10/2014\",\"deliveryDate\":\"02/25/2014\",\"contact\":\"Vanilla Bean\",\"email\":\"vanillabean@example.com\",\"phone\":\"01-2048096\",\"items\":[{\"itemNo\":12345,\"description\":\"part 123\",\"quantity\":12,\"unitPrice\":29.99,\"totalPrice\":149.95}],\"totalPrice\":149.95}}"; + // Test an unknown user - 401 error + client1.invoke(() -> { + HttpResponse response = doPut("/" + REGION_NAME + "/key1?op=PUT", "unknown-user", "1234567", "{ \"key9\" : \"foo\" }"); + assertEquals(401, getCode(response)); + }); + + client1.invoke(() -> { + HttpResponse response = doPut("/" + REGION_NAME + "/key1?op=CAS", "unknown-user", "1234567", "{ \"key9\" : \"foo\" }"); + assertEquals(401, getCode(response)); + }); + + client1.invoke(() -> { + HttpResponse response = doPut("/" + REGION_NAME + "/key1?op=REPLACE", "unknown-user", "1234567", "{ \"@old\" : \"value1\", \"@new\" : \"CASvalue\" }"); + assertEquals(401, getCode(response)); + }); + + // Test a user with insufficient rights - 403 + client1.invoke(() -> { + HttpResponse response = doPut("/" + REGION_NAME + "/key1?op=PUT", "dataReader", "1234567", "{ \"key1\" : \"foo\" }"); + assertEquals(403, getCode(response)); + }); + client1.invoke(() -> { + HttpResponse response = doPut("/" + REGION_NAME + "/key1?op=REPLACE", "dataReader", "1234567", "{ \"key1\" : \"foo\" }"); + assertEquals(403, getCode(response)); + }); + client1.invoke(() -> { + HttpResponse response = doPut("/" + REGION_NAME + "/key1?op=CAS", "dataReader", "1234567", casJSON); + assertEquals(403, getCode(response)); + }); + + // Test an authorized user - 200 + client1.invoke(() -> { + HttpResponse response = doPut("/" + REGION_NAME + "/key1?op=PUT", "key1User", "1234567", "{ \"key1\" : \"foo\" }"); + assertEquals(200, getCode(response)); + assertTrue(isOK(response)); + }); + client1.invoke(() -> { + HttpResponse response = doPut("/" + REGION_NAME + "/key1?op=REPLACE", "key1User", "1234567", json); + assertEquals(200, getCode(response)); + assertTrue(isOK(response)); + }); + } +} http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5ed443d5/geode-pulse/src/main/webapp/WEB-INF/spring-security.xml ---------------------------------------------------------------------- diff --git a/geode-pulse/src/main/webapp/WEB-INF/spring-security.xml b/geode-pulse/src/main/webapp/WEB-INF/spring-security.xml index 3756df7..924dd50 100644 --- a/geode-pulse/src/main/webapp/WEB-INF/spring-security.xml +++ b/geode-pulse/src/main/webapp/WEB-INF/spring-security.xml @@ -22,7 +22,7 @@ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/security - http://www.springframework.org/schema/security/spring-security-3.1.xsd + http://www.springframework.org/schema/security/spring-security-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd"> http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5ed443d5/geode-web-api/build.gradle ---------------------------------------------------------------------- diff --git a/geode-web-api/build.gradle b/geode-web-api/build.gradle index 9e93bb9..3ea652b 100755 --- a/geode-web-api/build.gradle +++ b/geode-web-api/build.gradle @@ -29,7 +29,7 @@ dependencies { compile 'com.fasterxml.jackson.core:jackson-databind:' + project.'jackson.version' compile 'com.fasterxml.jackson.module:jackson-module-scala_2.10:' + project.'jackson-module-scala_2.10.version' compile 'com.google.guava:guava:' + project.'guava.version' - compile ('com.mangofactory:swagger-springmvc:' + project.'swagger-springmvc.version') { + compile ('com.mangofactory:swagger-springmvc:' + project.'swagger-springmvc.version') { exclude module: 'aopalliance' exclude module: 'asm' exclude module: 'cglib' @@ -53,6 +53,10 @@ dependencies { compile 'org.json4s:json4s-ast_2.10:' + project.'json4s.version' compile 'org.scala-lang:scala-library:' + project.'scala.version' compile 'org.scala-lang:scala-reflect:' + project.'scala.version' + compile 'org.springframework:spring-beans:' + project.'springframework.version' + compile 'org.springframework.security:spring-security-core:' + project.'spring-security.version' + compile 'org.springframework.security:spring-security-web:' + project.'spring-security.version' + compile 'org.springframework.security:spring-security-config:' + project.'spring-security.version' compile ('org.springframework.hateoas:spring-hateoas:' + project.'spring-hateoas.version') { exclude module: 'aopalliance' exclude module: 'commons-logging' http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5ed443d5/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/AbstractBaseController.java ---------------------------------------------------------------------- diff --git a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/AbstractBaseController.java b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/AbstractBaseController.java index e94998b..ee5d714 100644 --- a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/AbstractBaseController.java +++ b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/AbstractBaseController.java @@ -35,17 +35,6 @@ import java.util.concurrent.atomic.AtomicLong; import javax.annotation.PostConstruct; -import org.apache.logging.log4j.Logger; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; -import org.springframework.web.servlet.support.ServletUriComponentsBuilder; - import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonMappingException; @@ -60,13 +49,13 @@ import org.apache.geode.cache.Region; import org.apache.geode.cache.TimeoutException; import org.apache.geode.cache.query.QueryService; import org.apache.geode.distributed.DistributedMember; +import org.apache.geode.distributed.LeaseExpiredException; import org.apache.geode.distributed.internal.DistributionManager; -import org.apache.geode.distributed.internal.InternalDistributedSystem; import org.apache.geode.distributed.internal.membership.InternalDistributedMember; -import org.apache.geode.i18n.LogWriterI18n; import org.apache.geode.internal.cache.GemFireCacheImpl; -import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.internal.logging.LogService; +import org.apache.geode.internal.security.IntegratedSecurityService; +import org.apache.geode.internal.security.SecurityService; import org.apache.geode.pdx.JSONFormatter; import org.apache.geode.pdx.JSONFormatterException; import org.apache.geode.pdx.PdxInstance; @@ -82,10 +71,21 @@ import org.apache.geode.rest.internal.web.util.IdentifiableUtils; import org.apache.geode.rest.internal.web.util.JSONUtils; import org.apache.geode.rest.internal.web.util.NumberUtils; import org.apache.geode.rest.internal.web.util.ValidationUtils; +import org.apache.logging.log4j.Logger; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONTokener; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.util.Assert; +import org.springframework.util.ClassUtils; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; + /** * AbstractBaseController class contains common functionalities required for other controllers. @@ -104,6 +104,7 @@ public abstract class AbstractBaseController { protected static final String UTF_8 = "UTF-8"; protected static final String DEFAULT_ENCODING = UTF_8; private static final AtomicLong ID_SEQUENCE = new AtomicLong(0l); + protected SecurityService securityService = IntegratedSecurityService.getSecurityService(); //private Cache cache = GemFireCacheImpl.getExisting(null); @@ -199,7 +200,7 @@ public abstract class AbstractBaseController { final HttpHeaders headers = new HttpHeaders(); headers.setLocation(toUri("queries", queryId)); - return new ResponseEntity<>(queryResultAsJson, headers, HttpStatus.OK); + return new ResponseEntity(queryResultAsJson, headers, HttpStatus.OK); }else { throw new GemfireRestException("Server has encountered error while generating query result into restful format(JSON)!"); } @@ -252,7 +253,7 @@ public abstract class AbstractBaseController { throw new GemfireRestException(String.format("Resource (%1$s) configuration does not allow null keys or values!", regionNamePath), npe); }catch(IllegalArgumentException iae){ throw new GemfireRestException(String.format("Resource (%1$s) configuration prevents specified data from being stored in it!", regionNamePath), iae); - }catch(org.apache.geode.distributed.LeaseExpiredException lee){ + }catch(LeaseExpiredException lee){ throw new GemfireRestException("Server has encountered error while processing this request!", lee); }catch(TimeoutException toe){ throw new GemfireRestException("Server has encountered timeout error while processing this request!", toe); @@ -278,7 +279,7 @@ public abstract class AbstractBaseController { throw new GemfireRestException(String.format("Resource (%1$s) configuration does not allow null keys or values!", regionNamePath), npe); }catch(IllegalArgumentException iae){ throw new GemfireRestException(String.format("Resource (%1$s) configuration prevents specified data from being stored in it!", regionNamePath), iae); - }catch(org.apache.geode.distributed.LeaseExpiredException lee){ + }catch(LeaseExpiredException lee){ throw new GemfireRestException("Server has encountered error while processing this request!", lee); }catch(TimeoutException toe){ throw new GemfireRestException("Server has encountered timeout error while processing this request!", toe); @@ -305,7 +306,7 @@ public abstract class AbstractBaseController { throw new GemfireRestException(String.format("Resource (%1$s) configuration does not allow null keys or values!", regionNamePath), npe); }catch(IllegalArgumentException iae){ throw new GemfireRestException(String.format("Resource (%1$s) configuration prevents specified data from being stored in it!", regionNamePath), iae); - }catch(org.apache.geode.distributed.LeaseExpiredException lee){ + }catch(LeaseExpiredException lee){ throw new GemfireRestException("Server has encountered error while processing this request!", lee); }catch(TimeoutException toe){ throw new GemfireRestException("Server has encountered timeout error while processing this request!", toe); @@ -325,7 +326,7 @@ public abstract class AbstractBaseController { throw new GemfireRestException(String.format("Resource (%1$s) configuration does not allow null keys or values!", regionNamePath), npe); } catch (ClassCastException cce) { throw new GemfireRestException(String.format("Resource (%1$s) configuration does not allow to store specified key or value type in this region!", regionNamePath), cce); - } catch (org.apache.geode.distributed.LeaseExpiredException lee) { + } catch (LeaseExpiredException lee) { throw new GemfireRestException("Server has encountered error while processing this request!", lee); } catch (TimeoutException toe) { throw new GemfireRestException("Server has encountered timeout error while processing this request!", toe); @@ -368,7 +369,7 @@ public abstract class AbstractBaseController { throw new GemfireRestException("NULL query ID or query string is not supported!", npe); } catch(IllegalArgumentException iae) { throw new GemfireRestException("Server has not allowed to perform the requested operation!", iae); - } catch(org.apache.geode.distributed.LeaseExpiredException lee) { + } catch(LeaseExpiredException lee) { throw new GemfireRestException("Server has encountered error while processing this request!", lee); } catch(TimeoutException te) { throw new GemfireRestException("Server has encountered timeout error while processing this request!", te); @@ -382,7 +383,7 @@ public abstract class AbstractBaseController { throw new GemfireRestException("NULL query ID or query string is not supported!", npe); } catch (ClassCastException cce) { throw new GemfireRestException("specified queryId or query string is not supported!", cce); - } catch (org.apache.geode.distributed.LeaseExpiredException lee) { + } catch (LeaseExpiredException lee) { throw new GemfireRestException("Server has encountered error while processing this request!", lee); } catch (TimeoutException toe) { throw new GemfireRestException("Server has encountered timeout error while processing this request!", toe); @@ -403,7 +404,7 @@ public abstract class AbstractBaseController { throw new GemfireRestException("NULL query ID or query string is not supported!", npe); } catch(IllegalArgumentException iae){ throw new GemfireRestException("Configuration does not allow to perform the requested operation!", iae); - } catch(org.apache.geode.distributed.LeaseExpiredException lee){ + } catch(LeaseExpiredException lee){ throw new GemfireRestException("Server has encountered error while processing this request!", lee); } catch(TimeoutException toe){ throw new GemfireRestException("Server has encountered timeout error while processing this request!", toe); @@ -451,7 +452,7 @@ public abstract class AbstractBaseController { throw new GemfireRestException(String.format("Resource (%1$s) configuration does not allow null keys or values!", regionNamePath), npe); }catch(IllegalArgumentException iae){ throw new GemfireRestException(String.format("Resource (%1$s) configuration prevents specified data from being stored in it!", regionNamePath), iae); - }catch(org.apache.geode.distributed.LeaseExpiredException lee){ + }catch(LeaseExpiredException lee){ throw new GemfireRestException("Server has encountered error while processing this request!", lee); }catch(TimeoutException toe){ throw new GemfireRestException("Server has encountered timeout error while processing this request!", toe); @@ -645,10 +646,7 @@ public abstract class AbstractBaseController { protected String convertErrorAsJson(Throwable t) { StringWriter writer = new StringWriter(); t.printStackTrace(new PrintWriter(writer)); - String returnString = writer.toString(); - returnString = returnString.replace("\n"," "); - returnString = returnString.replace("\t"," "); - return String.format("{\"message\" : \"%1$s\", \"stackTrace\" : \"%2$s\"}", t.getMessage(), returnString); + return String.format("{\"message\" : \"%1$s\", \"stackTrace\" : \"%2$s\"}", t.getMessage(), writer.toString()); } protected Map convertJsonToMap(final String jsonString) { @@ -794,7 +792,7 @@ public abstract class AbstractBaseController { throw new GemfireRestException(String.format("Resource (%1$s) configuration does not allow null keys!", regionNamePath), npe); } catch(IllegalArgumentException iae) { throw new GemfireRestException(String.format("Resource (%1$s) configuration does not allow requested operation on specified key!", regionNamePath), iae); - } catch(org.apache.geode.distributed.LeaseExpiredException lee) { + } catch(LeaseExpiredException lee) { throw new GemfireRestException("Server has encountered error while processing this request!", lee); } catch(TimeoutException te) { throw new GemfireRestException("Server has encountered timeout error while processing this request!", te); http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5ed443d5/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/BaseControllerAdvice.java ---------------------------------------------------------------------- diff --git a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/BaseControllerAdvice.java b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/BaseControllerAdvice.java index a4f58b9..54dd9e5 100644 --- a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/BaseControllerAdvice.java +++ b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/BaseControllerAdvice.java @@ -20,20 +20,24 @@ package org.apache.geode.rest.internal.web.controllers; import java.io.PrintWriter; import java.io.StringWriter; +import org.apache.geode.internal.logging.LogService; +import org.apache.geode.rest.internal.web.exception.DataTypeNotSupportedException; +import org.apache.geode.rest.internal.web.exception.GemfireRestException; +import org.apache.geode.rest.internal.web.exception.MalformedJsonException; +import org.apache.geode.rest.internal.web.exception.RegionNotFoundException; +import org.apache.geode.rest.internal.web.exception.ResourceNotFoundException; +import org.apache.geode.security.AuthenticationFailedException; +import org.apache.geode.security.NotAuthorizedException; import org.apache.logging.log4j.Logger; +import org.apache.shiro.authc.AuthenticationException; import org.springframework.http.HttpStatus; +import org.springframework.security.access.AccessDeniedException; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; -import org.apache.geode.internal.logging.LogService; -import org.apache.geode.rest.internal.web.exception.DataTypeNotSupportedException; -import org.apache.geode.rest.internal.web.exception.GemfireRestException; -import org.apache.geode.rest.internal.web.exception.MalformedJsonException; -import org.apache.geode.rest.internal.web.exception.RegionNotFoundException; -import org.apache.geode.rest.internal.web.exception.ResourceNotFoundException; /** * The CrudControllerAdvice class handles exception thrown while serving the REST request @@ -43,7 +47,7 @@ import org.apache.geode.rest.internal.web.exception.ResourceNotFoundException; @ControllerAdvice @SuppressWarnings("unused") -public class BaseControllerAdvice extends AbstractBaseController{ +public class BaseControllerAdvice extends AbstractBaseController { private static final Logger logger = LogService.getLogger(); @@ -121,6 +125,58 @@ public class BaseControllerAdvice extends AbstractBaseController{ } /** + * Handles an AuthenticationFailedException thrown by a REST API web service endpoint, HTTP request handler method. + *

+ * @param cause the Exception causing the error. + * @return a ResponseEntity with an appropriate HTTP status code (403 - Forbidden) + */ + @ExceptionHandler(AuthenticationFailedException.class) + @ResponseBody + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public String handleException(final AuthenticationFailedException cause) { + return convertErrorAsJson(cause.getMessage()); + } + + /** + * Handles an AuthenticationException thrown by a REST API web service endpoint, HTTP request handler method. + *

+ * @param cause the Exception causing the error. + * @return a ResponseEntity with an appropriate HTTP status code (403 - Forbidden) + */ + @ExceptionHandler(AuthenticationException.class) + @ResponseBody + @ResponseStatus(HttpStatus.UNAUTHORIZED) + public String handleException(final AuthenticationException cause) { + return convertErrorAsJson(cause.getMessage()); + } + + /** + * Handles an AccessDenied Exception thrown by a REST API web service endpoint, HTTP request handler method. + *

+ * @param cause the Exception causing the error. + * @return a ResponseEntity with an appropriate HTTP status code (403 - Forbidden) + */ + @ExceptionHandler(AccessDeniedException.class) + @ResponseBody + @ResponseStatus(HttpStatus.FORBIDDEN) + public String handleException(final AccessDeniedException cause) { + return convertErrorAsJson(cause.getMessage()); + } + + /** + * Handles an NotAuthorized Exception thrown by a GeodeSecurityUtil. + *

+ * @param cause the Exception causing the error. + * @return a ResponseEntity with an appropriate HTTP status code (403 - Forbidden) + */ + @ExceptionHandler(NotAuthorizedException.class) + @ResponseBody + @ResponseStatus(HttpStatus.FORBIDDEN) + public String handleException(final NotAuthorizedException cause) { + return convertErrorAsJson(cause.getMessage()); + } + + /** * Handles any Exception thrown by a REST API web service endpoint, HTTP request handler method. *

* @param cause the Exception causing the error. @@ -134,13 +190,13 @@ public class BaseControllerAdvice extends AbstractBaseController{ final StringWriter stackTraceWriter = new StringWriter(); cause.printStackTrace(new PrintWriter(stackTraceWriter)); final String stackTrace = stackTraceWriter.toString(); - + if(logger.isDebugEnabled()){ - logger.debug(stackTrace); + logger.debug(stackTrace); } - + return convertErrorAsJson(cause.getMessage()); } - + } http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5ed443d5/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/CommonCrudController.java ---------------------------------------------------------------------- diff --git a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/CommonCrudController.java b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/CommonCrudController.java index b312884..232f034 100644 --- a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/CommonCrudController.java +++ b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/CommonCrudController.java @@ -21,15 +21,9 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import org.apache.logging.log4j.Logger; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; - +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; import org.apache.geode.cache.LowMemoryException; import org.apache.geode.cache.Region; import org.apache.geode.cache.execute.Execution; @@ -38,14 +32,20 @@ import org.apache.geode.cache.execute.FunctionService; import org.apache.geode.cache.execute.ResultCollector; import org.apache.geode.internal.cache.GemFireCacheImpl; import org.apache.geode.internal.logging.LogService; -import org.apache.geode.internal.util.ArrayUtils; import org.apache.geode.rest.internal.web.controllers.support.RestServersResultCollector; import org.apache.geode.rest.internal.web.exception.GemfireRestException; +import org.apache.geode.rest.internal.web.util.ArrayUtils; import org.apache.geode.rest.internal.web.util.JSONUtils; +import org.apache.logging.log4j.Logger; import org.json.JSONException; -import com.wordnik.swagger.annotations.ApiOperation; -import com.wordnik.swagger.annotations.ApiResponse; -import com.wordnik.swagger.annotations.ApiResponses; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; + /** * The CommonCrudController serves REST Requests related to listing regions, @@ -71,19 +71,20 @@ public abstract class CommonCrudController extends AbstractBaseController { ) @ApiResponses( { @ApiResponse( code = 200, message = "OK." ), - @ApiResponse( code = 500, message = "GemFire throws an error or exception." ) + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), + @ApiResponse( code = 500, message = "GemFire throws an error or exception." ) } ) public ResponseEntity regions() { - + securityService.authorizeDataRead(); if(logger.isDebugEnabled()){ logger.debug("Listing all resources (Regions) in GemFire..."); } - + final HttpHeaders headers = new HttpHeaders(); + headers.setLocation(toUri()); final Set> regions = getCache().rootRegions(); String listRegionsAsJson = JSONUtils.formulateJsonForListRegions(regions, "regions"); - final HttpHeaders headers = new HttpHeaders(); - headers.setLocation(toUri()); - return new ResponseEntity(listRegionsAsJson, headers, HttpStatus.OK); + return new ResponseEntity<>(listRegionsAsJson, headers, HttpStatus.OK); } /** @@ -91,7 +92,7 @@ public abstract class CommonCrudController extends AbstractBaseController { * @param region gemfire region * @return JSON document containing result */ - @RequestMapping(method = RequestMethod.GET, value = "/{region}/keys", + @RequestMapping(method = RequestMethod.GET, value = "/{region}/keys", produces = { MediaType.APPLICATION_JSON_VALUE } ) @ApiOperation( value = "list all keys", @@ -100,11 +101,13 @@ public abstract class CommonCrudController extends AbstractBaseController { ) @ApiResponses( { @ApiResponse( code = 200, message = "OK" ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 404, message = "Region does not exist" ), @ApiResponse( code = 500, message = "GemFire throws an error or exception" ) } ) - public ResponseEntity keys(@PathVariable("region") String region){ - + public ResponseEntity keys(@PathVariable("region") String region){ + securityService.authorizeRegionRead(region); if(logger.isDebugEnabled()){ logger.debug("Reading all Keys in Region ({})...", region); } @@ -116,7 +119,7 @@ public abstract class CommonCrudController extends AbstractBaseController { String listKeysAsJson = JSONUtils.formulateJsonForListKeys(keys, "keys"); final HttpHeaders headers = new HttpHeaders(); headers.setLocation(toUri(region, "keys")); - return new ResponseEntity(listKeysAsJson, headers, HttpStatus.OK); + return new ResponseEntity<>(listKeysAsJson, headers, HttpStatus.OK); } /** @@ -134,11 +137,15 @@ public abstract class CommonCrudController extends AbstractBaseController { ) @ApiResponses( { @ApiResponse( code = 200, message = "OK" ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 404, message = "Region or key(s) does not exist" ), @ApiResponse( code = 500, message = "GemFire throws an error or exception" ) } ) public ResponseEntity delete(@PathVariable("region") String region, - @PathVariable("keys") final String[] keys){ + @PathVariable("keys") final String[] keys){ + for (String key : keys) + securityService.authorizeRegionWrite(region, key); if(logger.isDebugEnabled()){ logger.debug("Delete data for key {} on region {}", ArrayUtils.toString((Object[])keys), region); } @@ -146,7 +153,7 @@ public abstract class CommonCrudController extends AbstractBaseController { region = decode(region); deleteValues(region, (Object[])keys); - return new ResponseEntity(HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.OK); } /** @@ -162,21 +169,27 @@ public abstract class CommonCrudController extends AbstractBaseController { ) @ApiResponses( { @ApiResponse( code = 200, message = "OK" ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 404, message = "Region does not exist" ), @ApiResponse( code = 500, message = "if GemFire throws an error or exception" ) } ) public ResponseEntity delete(@PathVariable("region") String region) { - + securityService.authorizeRegionWrite(region); if(logger.isDebugEnabled()){ logger.debug("Deleting all data in Region ({})...", region); } - + region = decode(region); deleteValues(region); - return new ResponseEntity(HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.OK); } - + + /** + * Ping is not secured so that it may not be used to determine a valid username/password + * @return + */ @RequestMapping(method = { RequestMethod.GET, RequestMethod.HEAD }, value = "/ping") @ApiOperation( value = "Check Rest service status ", @@ -188,7 +201,7 @@ public abstract class CommonCrudController extends AbstractBaseController { @ApiResponse( code = 500, message = "if GemFire throws an error or exception" ) } ) public ResponseEntity ping() { - return new ResponseEntity(HttpStatus.OK); + return new ResponseEntity<>(HttpStatus.OK); } @RequestMapping(method = { RequestMethod.GET }, value = "/servers") @@ -199,15 +212,17 @@ public abstract class CommonCrudController extends AbstractBaseController { ) @ApiResponses( { @ApiResponse( code = 200, message = "OK" ), - @ApiResponse( code = 500, message = "if GemFire throws an error or exception" ) + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), + @ApiResponse( code = 500, message = "if GemFire throws an error or exception" ) } ) public ResponseEntity servers() { - Execution function = null; - + securityService.authorizeClusterRead(); if(logger.isDebugEnabled()){ logger.debug("Executing function to get REST enabled gemfire nodes in the DS!"); } - + + Execution function; try { function = FunctionService.onMembers(getAllMembersInDS()); } catch(FunctionException fe) { @@ -223,7 +238,7 @@ public abstract class CommonCrudController extends AbstractBaseController { headers.setLocation(toUri("servers")); try { String functionResultAsJson = JSONUtils.convertCollectionToJson((ArrayList)functionResult); - return new ResponseEntity(functionResultAsJson, headers, HttpStatus.OK); + return new ResponseEntity<>(functionResultAsJson, headers, HttpStatus.OK); } catch (JSONException e) { throw new GemfireRestException("Could not convert function results into Restful (JSON) format!", e); } @@ -242,7 +257,5 @@ public abstract class CommonCrudController extends AbstractBaseController { }catch (FunctionException fe){ throw new GemfireRestException("Server has encountered error while executing the function!", fe); } - } - } http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5ed443d5/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/FunctionAccessController.java ---------------------------------------------------------------------- diff --git a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/FunctionAccessController.java b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/FunctionAccessController.java index c31292b..8cec110 100644 --- a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/FunctionAccessController.java +++ b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/FunctionAccessController.java @@ -17,16 +17,25 @@ package org.apache.geode.rest.internal.web.controllers; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; import org.apache.geode.cache.LowMemoryException; -import org.apache.geode.cache.execute.*; +import org.apache.geode.cache.execute.Execution; +import org.apache.geode.cache.execute.Function; +import org.apache.geode.cache.execute.FunctionException; +import org.apache.geode.cache.execute.FunctionService; +import org.apache.geode.cache.execute.ResultCollector; import org.apache.geode.internal.logging.LogService; import org.apache.geode.rest.internal.web.exception.GemfireRestException; import org.apache.geode.rest.internal.web.util.ArrayUtils; import org.apache.geode.rest.internal.web.util.JSONUtils; -import com.wordnik.swagger.annotations.Api; -import com.wordnik.swagger.annotations.ApiOperation; -import com.wordnik.swagger.annotations.ApiResponse; -import com.wordnik.swagger.annotations.ApiResponses; import org.apache.logging.log4j.Logger; import org.json.JSONException; import org.springframework.http.HttpHeaders; @@ -35,9 +44,13 @@ import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; -import org.springframework.web.bind.annotation.*; - -import java.util.*; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; /** * The FunctionsController class serving REST Requests related to the function execution @@ -79,12 +92,14 @@ public class FunctionAccessController extends AbstractBaseController { ) @ApiResponses({ @ApiResponse(code = 200, message = "OK."), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse(code = 500, message = "GemFire throws an error or exception.") }) @ResponseBody @ResponseStatus(HttpStatus.OK) public ResponseEntity list() { - + securityService.authorizeDataRead(); if (logger.isDebugEnabled()) { logger.debug("Listing all registered Functions in GemFire..."); } @@ -116,6 +131,8 @@ public class FunctionAccessController extends AbstractBaseController { ) @ApiResponses({ @ApiResponse(code = 200, message = "OK."), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse(code = 500, message = "if GemFire throws an error or exception"), @ApiResponse(code = 400, message = "if Function arguments specified as JSON document in the request body is invalid") }) @@ -128,6 +145,7 @@ public class FunctionAccessController extends AbstractBaseController { @RequestParam(value = "filter", required = false) final String[] filter, @RequestBody(required = false) final String argsInBody ) { + securityService.authorizeDataWrite(); Execution function = null; functionId = decode(functionId); http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5ed443d5/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/PdxBasedCrudController.java ---------------------------------------------------------------------- diff --git a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/PdxBasedCrudController.java b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/PdxBasedCrudController.java index dd77316..c05845a 100644 --- a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/PdxBasedCrudController.java +++ b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/PdxBasedCrudController.java @@ -20,6 +20,16 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; +import org.apache.geode.internal.logging.LogService; +import org.apache.geode.rest.internal.web.controllers.support.JSONTypes; +import org.apache.geode.rest.internal.web.controllers.support.RegionData; +import org.apache.geode.rest.internal.web.controllers.support.RegionEntryData; +import org.apache.geode.rest.internal.web.exception.ResourceNotFoundException; +import org.apache.geode.rest.internal.web.util.ArrayUtils; import org.apache.logging.log4j.Logger; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; @@ -33,17 +43,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; -import org.apache.geode.internal.logging.LogService; -import org.apache.geode.internal.util.ArrayUtils; -import org.apache.geode.rest.internal.web.controllers.support.JSONTypes; -import org.apache.geode.rest.internal.web.controllers.support.RegionData; -import org.apache.geode.rest.internal.web.controllers.support.RegionEntryData; -import org.apache.geode.rest.internal.web.exception.ResourceNotFoundException; -import com.wordnik.swagger.annotations.Api; -import com.wordnik.swagger.annotations.ApiOperation; -import com.wordnik.swagger.annotations.ApiResponse; -import com.wordnik.swagger.annotations.ApiResponses; - /** * The PdxBasedCrudController class serving REST Requests related to the REST CRUD operation on region *

@@ -88,6 +87,8 @@ public class PdxBasedCrudController extends CommonCrudController { @ApiResponses( { @ApiResponse( code = 201, message = "Created."), @ApiResponse( code = 400, message = "Data specified (JSON doc) in the request body is invalid." ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 404, message = "Region does not exist." ), @ApiResponse( code = 409, message = "Key already exist in region."), @ApiResponse( code = 500, message = "GemFire throws an error or exception.") @@ -95,7 +96,7 @@ public class PdxBasedCrudController extends CommonCrudController { public ResponseEntity create(@PathVariable("region") String region, @RequestParam(value = "key", required = false) String key, @RequestBody final String json) { - + securityService.authorizeRegionWrite(region); key = generateKey(key); if(logger.isDebugEnabled()){ @@ -141,12 +142,14 @@ public class PdxBasedCrudController extends CommonCrudController { @ApiResponses( { @ApiResponse( code = 200, message = "OK."), @ApiResponse( code = 400, message = "Bad request." ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 404, message = "Region does not exist." ), @ApiResponse( code = 500, message = "GemFire throws an error or exception.") } ) public ResponseEntity read(@PathVariable("region") String region, @RequestParam(value = "limit", defaultValue = DEFAULT_GETALL_RESULT_LIMIT) final String limit) { - + securityService.authorizeRegionRead(region); if(logger.isDebugEnabled()){ logger.debug("Reading all data in Region ({})...", region); } @@ -219,6 +222,8 @@ public class PdxBasedCrudController extends CommonCrudController { @ApiResponses( { @ApiResponse( code = 200, message = "OK."), @ApiResponse( code = 400, message = "Bad Request."), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 404, message = "Region does not exist." ), @ApiResponse( code = 500, message = "GemFire throws an error or exception.") } ) @@ -226,7 +231,9 @@ public class PdxBasedCrudController extends CommonCrudController { @PathVariable("region") String region, @PathVariable("keys") final String[] keys, @RequestParam(value = "ignoreMissingKey", required = false ) final String ignoreMissingKey) { - + + for (String key : keys) + securityService.authorizeRegionRead(region, key); if(logger.isDebugEnabled()){ logger.debug("Reading data for keys ({}) in Region ({})", ArrayUtils.toString(keys), region); @@ -302,6 +309,8 @@ public class PdxBasedCrudController extends CommonCrudController { @ApiResponses( { @ApiResponse( code = 200, message = "OK."), @ApiResponse( code = 400, message = "Bad Request."), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 404, message = "Region does not exist or if key is not mapped to some value for REPLACE or CAS."), @ApiResponse( code = 409, message = "For CAS, @old value does not match to the current value in region" ), @ApiResponse( code = 500, message = "GemFire throws an error or exception.") @@ -310,7 +319,8 @@ public class PdxBasedCrudController extends CommonCrudController { @PathVariable("keys") final String[] keys, @RequestParam(value = "op", defaultValue = "PUT") final String opValue, @RequestBody final String json) { - + for (String key : keys) + securityService.authorizeRegionWrite(region, key); if(logger.isDebugEnabled()){ logger.debug("updating key(s) for region ({}) ", region); } @@ -334,11 +344,13 @@ public class PdxBasedCrudController extends CommonCrudController { @ApiResponses( { @ApiResponse( code = 200, message = "OK."), @ApiResponse( code = 400, message = "Bad request." ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 404, message = "Region does not exist." ), @ApiResponse( code = 500, message = "GemFire throws an error or exception.") } ) public ResponseEntity size(@PathVariable("region") String region) { - + securityService.authorizeRegionRead(region); if(logger.isDebugEnabled()){ logger.debug("Determining the number of entries in Region ({})...", region); } http://git-wip-us.apache.org/repos/asf/incubator-geode/blob/5ed443d5/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/QueryAccessController.java ---------------------------------------------------------------------- diff --git a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/QueryAccessController.java b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/QueryAccessController.java index 4f059a4..2df95aa 100644 --- a/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/QueryAccessController.java +++ b/geode-web-api/src/main/java/org/apache/geode/rest/internal/web/controllers/QueryAccessController.java @@ -19,20 +19,10 @@ package org.apache.geode.rest.internal.web.controllers; import java.util.concurrent.ConcurrentHashMap; -import org.apache.logging.log4j.Logger; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; - +import com.wordnik.swagger.annotations.Api; +import com.wordnik.swagger.annotations.ApiOperation; +import com.wordnik.swagger.annotations.ApiResponse; +import com.wordnik.swagger.annotations.ApiResponses; import org.apache.geode.cache.Region; import org.apache.geode.cache.query.FunctionDomainException; import org.apache.geode.cache.query.NameResolutionException; @@ -48,10 +38,20 @@ import org.apache.geode.rest.internal.web.exception.GemfireRestException; import org.apache.geode.rest.internal.web.exception.ResourceNotFoundException; import org.apache.geode.rest.internal.web.util.JSONUtils; import org.apache.geode.rest.internal.web.util.ValidationUtils; -import com.wordnik.swagger.annotations.Api; -import com.wordnik.swagger.annotations.ApiOperation; -import com.wordnik.swagger.annotations.ApiResponse; -import com.wordnik.swagger.annotations.ApiResponses; +import org.apache.logging.log4j.Logger; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; + /** * The QueryingController class serves all HTTP REST requests related to the gemfire querying @@ -86,37 +86,39 @@ public class QueryAccessController extends AbstractBaseController { } /** - * list all parameterized Queries created in a Gemfire data node + * list all parametrized Queries created in a Gemfire data node * @return result as a JSON document. */ @RequestMapping(method = RequestMethod.GET, produces = { MediaType.APPLICATION_JSON_VALUE }) @ApiOperation( - value = "list all parameterized queries", - notes = "List all parameterized queries by id/name", + value = "list all parametrized queries", + notes = "List all parametrized queries by id/name", response = void.class ) @ApiResponses( { - @ApiResponse( code = 200, message = "OK." ), - @ApiResponse( code = 500, message = "if GemFire throws an error or exception" ) + @ApiResponse( code = 200, message = "OK." ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), + @ApiResponse( code = 500, message = "if GemFire throws an error or exception" ) } ) @ResponseBody @ResponseStatus(HttpStatus.OK) public ResponseEntity list() { - + securityService.authorizeDataRead(); if (logger.isDebugEnabled()) { - logger.debug("Listing all parameterized Queries in GemFire..."); + logger.debug("Listing all parametrized Queries in GemFire..."); } - final Region parameterizedQueryRegion = getQueryStore(PARAMETERIZED_QUERIES_REGION); + final Region parametrizedQueryRegion = getQueryStore(PARAMETERIZED_QUERIES_REGION); - String queryListAsJson = JSONUtils.formulateJsonForListQueriesCall(parameterizedQueryRegion); + String queryListAsJson = JSONUtils.formulateJsonForListQueriesCall(parametrizedQueryRegion); final HttpHeaders headers = new HttpHeaders(); headers.setLocation(toUri("queries")); return new ResponseEntity(queryListAsJson, headers, HttpStatus.OK); } /** - * Create a named, parameterized Query + * Create a named, parametrized Query * @param queryId uniquely identify the query * @param oqlInUrl OQL query string specified in a request URL * @param oqlInBody OQL query string specified in a request body @@ -124,23 +126,26 @@ public class QueryAccessController extends AbstractBaseController { */ @RequestMapping(method = RequestMethod.POST) @ApiOperation( - value = "create a parameterized Query", - notes = "Prepare the specified parameterized query and assign the corresponding ID for lookup", + value = "create a parametrized Query", + notes = "Prepare the specified parametrized query and assign the corresponding ID for lookup", response = void.class ) @ApiResponses( { @ApiResponse( code = 201, message = "Successfully created." ), - @ApiResponse( code = 409, message = "QueryId already assigned to other query." ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), + @ApiResponse( code = 409, message = "QueryId already assigned to other query." ), @ApiResponse( code = 500, message = "GemFire throws an error or exception." ) } ) public ResponseEntity create(@RequestParam("id") final String queryId, @RequestParam(value = "q", required = false) String oqlInUrl, @RequestBody(required = false) final String oqlInBody) { + securityService.authorizeDataWrite(); final String oqlStatement = validateQuery(oqlInUrl, oqlInBody); if (logger.isDebugEnabled()) { - logger.debug("Creating a named, parameterized Query ({}) with ID ({})...", oqlStatement, queryId); + logger.debug("Creating a named, parametrized Query ({}) with ID ({})...", oqlStatement, queryId); } // store the compiled OQL statement with 'queryId' as the Key into the hidden, ParameterizedQueries Region... @@ -170,12 +175,14 @@ public class QueryAccessController extends AbstractBaseController { ) @ApiResponses( { @ApiResponse( code = 200, message = "OK." ), - @ApiResponse( code = 500, message = "GemFire throws an error or exception" ) + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), + @ApiResponse( code = 500, message = "GemFire throws an error or exception" ) } ) @ResponseBody @ResponseStatus(HttpStatus.OK) public ResponseEntity runAdhocQuery(@RequestParam("q") String oql) { - + securityService.authorizeDataRead(); if (logger.isDebugEnabled()) { logger.debug("Running an adhoc Query ({})...", oql); } @@ -210,19 +217,21 @@ public class QueryAccessController extends AbstractBaseController { } /** - * Run named parameterized Query with ID + * Run named parametrized Query with ID * @param queryId id of the OQL string * @param arguments query bind params required while executing query * @return query result as a JSON document */ @RequestMapping(method = RequestMethod.POST, value = "/{query}", produces = {MediaType.APPLICATION_JSON_VALUE}) @ApiOperation( - value = "run parameterized query", + value = "run parametrized query", notes = "run the specified named query passing in scalar values for query parameters in the GemFire cluster", response = void.class ) @ApiResponses( { @ApiResponse( code = 200, message = "Query successfully executed." ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 400, message = "Query bind params specified as JSON document in the request body is invalid" ), @ApiResponse( code = 500, message = "GemFire throws an error or exception" ) } ) @@ -231,6 +240,7 @@ public class QueryAccessController extends AbstractBaseController { public ResponseEntity runNamedQuery(@PathVariable("query") String queryId, @RequestBody String arguments) { + securityService.authorizeDataWrite(); if (logger.isDebugEnabled()) { logger.debug("Running named Query with ID ({})...", queryId); } @@ -286,30 +296,32 @@ public class QueryAccessController extends AbstractBaseController { } /** - * Update named, parameterized Query + * Update named, parametrized Query * @param queryId uniquely identify the query * @param oqlInUrl OQL query string specified in a request URL * @param oqlInBody OQL query string specified in a request body */ @RequestMapping(method = RequestMethod.PUT, value = "/{query}") @ApiOperation( - value = "update parameterized query", - notes = "Update named, parameterized query by ID", + value = "update parametrized query", + notes = "Update named, parametrized query by ID", response = void.class ) @ApiResponses( { @ApiResponse( code = 200, message = "Updated successfully." ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 404, message = "queryId does not exist." ), @ApiResponse( code = 500, message = "GemFire throws an error or exception." ) } ) public ResponseEntity update( @PathVariable("query") final String queryId, @RequestParam(value = "q", required = false) String oqlInUrl, @RequestBody(required = false) final String oqlInBody) { - + securityService.authorizeDataWrite(); final String oqlStatement = validateQuery(oqlInUrl, oqlInBody); if (logger.isDebugEnabled()) { - logger.debug("Updating a named, parameterized Query ({}) with ID ({})...", oqlStatement, queryId); + logger.debug("Updating a named, parametrized Query ({}) with ID ({})...", oqlStatement, queryId); } // update the OQL statement with 'queryId' as the Key into the hidden, ParameterizedQueries Region... @@ -320,26 +332,28 @@ public class QueryAccessController extends AbstractBaseController { return new ResponseEntity(HttpStatus.OK); } - //delete named, parameterized query + //delete named, parametrized query /** - * Delete named, parameterized Query + * Delete named, parametrized Query * @param queryId uniquely identify the query to be deleted */ @RequestMapping(method = RequestMethod.DELETE, value = "/{query}") @ApiOperation( - value = "delete parameterized query", - notes = "delete named, parameterized query by ID", + value = "delete parametrized query", + notes = "delete named, parametrized query by ID", response = void.class ) @ApiResponses( { @ApiResponse( code = 200, message = "Deleted successfully." ), + @ApiResponse( code = 401, message = "Invalid Username or Password." ), + @ApiResponse( code = 403, message = "Insufficient privileges for operation." ), @ApiResponse( code = 404, message = "queryId does not exist." ), @ApiResponse( code = 500, message = "GemFire throws an error or exception" ) } ) public ResponseEntity delete(@PathVariable("query") final String queryId) { - + securityService.authorizeDataWrite(); if (logger.isDebugEnabled()) { - logger.debug("Deleting a named, parameterized Query with ID ({}).", queryId); + logger.debug("Deleting a named, parametrized Query with ID ({}).", queryId); } //delete the OQL statement with 'queryId' as the Key into the hidden,