Repository: nutch
Updated Branches:
  refs/heads/2.x 5012c742c -> 9ecdc9be3


NUTCH-2301 Tests for Security Layer of NutchServer Are Created


Project: http://git-wip-us.apache.org/repos/asf/nutch/repo
Commit: http://git-wip-us.apache.org/repos/asf/nutch/commit/3bc3d81e
Tree: http://git-wip-us.apache.org/repos/asf/nutch/tree/3bc3d81e
Diff: http://git-wip-us.apache.org/repos/asf/nutch/diff/3bc3d81e

Branch: refs/heads/2.x
Commit: 3bc3d81e964aac59f61951740e848bd429a15b3c
Parents: 22683a1
Author: Furkan KAMACI <[email protected]>
Authored: Tue Aug 23 23:41:43 2016 +0300
Committer: Furkan KAMACI <[email protected]>
Committed: Tue Aug 23 23:41:43 2016 +0300

----------------------------------------------------------------------
 src/test/nutch-site.xml                         |  48 ++++
 src/test/nutch-ssl.keystore.jks                 | Bin 0 -> 2300 bytes
 .../nutch/api/AbstractNutchAPITestBase.java     | 186 +++++++++++++++
 src/test/org/apache/nutch/api/TestAPI.java      | 225 -------------------
 src/test/org/apache/nutch/api/TestNutchAPI.java | 100 +++++++++
 5 files changed, 334 insertions(+), 225 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/nutch/blob/3bc3d81e/src/test/nutch-site.xml
----------------------------------------------------------------------
diff --git a/src/test/nutch-site.xml b/src/test/nutch-site.xml
index e599547..4dbce0c 100644
--- a/src/test/nutch-site.xml
+++ b/src/test/nutch-site.xml
@@ -29,4 +29,52 @@
   </description>
 </property>
 
+<property>
+  <name>restapi.auth</name>
+  <value>NONE</value>
+  <description>
+    Configures authentication type for communicating with RESTAPI. Valid 
values are BASIC, DIGEST, SSL and NONE.
+    When no authentication type is defined NONE will be used as default which 
does not provide security.
+    Use the restapi.auth.username and restapi.auth.password properties to 
configure
+    your credentials if security is used.
+  </description>
+</property>
+
+<property>
+  <name>restapi.auth.users</name>
+  <value>admin|admin|admin,user|user|user</value>
+  <description>
+    Username, password and role combination for REST API 
authentication/authorization. restapi.auth property should be set to either 
BASIC or DIGEST to use this property.
+    Username, password and role should be delimited by pipe character (|) 
Every user should be separated with comma character (,). i.e. 
admin|admin|admin,user|user|user.
+    Default is admin|admin|admin,user|user|user
+  </description>
+</property>
+
+<property>
+  <name>restapi.auth.ssl.storepath</name>
+  <value>nutch-ssl.keystore.jks</value>
+  <description>
+    Key store path for jks file. restapi.auth property should be set to SSL to 
use this property.
+    etc/nutch-ssl.keystore.jks is used for restapi.auth.ssl.storepath as 
default.
+  </description>
+</property>
+
+<property>
+  <name>restapi.auth.ssl.storepass</name>
+  <value>password</value>
+  <description>
+    Key store path for jks file. restapi.auth property should be set to SSL to 
use this property.
+    "password" is used for restapi.auth.ssl.storepass as default.
+  </description>
+</property>
+
+<property>
+  <name>restapi.auth.ssl.keypass</name>
+  <value>password</value>
+  <description>
+    Key store path for jks file. restapi.auth property should be set to SSL to 
use this property.
+    "password" is used for restapi.auth.ssl.keypass as default.
+  </description>
+</property>
+
 </configuration>

http://git-wip-us.apache.org/repos/asf/nutch/blob/3bc3d81e/src/test/nutch-ssl.keystore.jks
----------------------------------------------------------------------
diff --git a/src/test/nutch-ssl.keystore.jks b/src/test/nutch-ssl.keystore.jks
new file mode 100644
index 0000000..9d0bd01
Binary files /dev/null and b/src/test/nutch-ssl.keystore.jks differ

http://git-wip-us.apache.org/repos/asf/nutch/blob/3bc3d81e/src/test/org/apache/nutch/api/AbstractNutchAPITestBase.java
----------------------------------------------------------------------
diff --git a/src/test/org/apache/nutch/api/AbstractNutchAPITestBase.java 
b/src/test/org/apache/nutch/api/AbstractNutchAPITestBase.java
new file mode 100644
index 0000000..3cdff23
--- /dev/null
+++ b/src/test/org/apache/nutch/api/AbstractNutchAPITestBase.java
@@ -0,0 +1,186 @@
+/*******************************************************************************
+ * 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.nutch.api;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.nutch.api.impl.RAMConfManager;
+import org.apache.nutch.api.security.AuthenticationTypeEnum;
+import org.apache.nutch.util.NutchConfiguration;
+import org.junit.BeforeClass;
+import org.restlet.data.ChallengeRequest;
+import org.restlet.data.ChallengeResponse;
+import org.restlet.data.ChallengeScheme;
+import org.restlet.resource.ClientResource;
+import org.restlet.resource.ResourceException;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Base class for Nutc REST API tests.
+ * Default path is /admin and default port is 8081.
+ */
+public abstract class AbstractNutchAPITestBase {
+  protected static Configuration conf;
+  protected static final Integer DEFAULT_PORT = 8081;
+  protected static final String DEFAULT_PATH = "/admin";
+  public NutchServer nutchServer;
+
+  /**
+   * Creates Nutch configuration
+   *
+   * @throws Exception
+   */
+  @BeforeClass
+  public static void before() throws Exception {
+    conf = NutchConfiguration.create();
+  }
+
+  /**
+   * Tests a request for given expected status code for {@link NutchServer} 
from which port it runs
+   *
+   * @param expectedStatusCode expected status code
+   * @param port port that {@link NutchServer} runs at
+   */
+  public void testRequest(int expectedStatusCode, int port) {
+    testRequest(expectedStatusCode, port, null, null, DEFAULT_PATH, null);
+  }
+
+  /**
+   * Tests a request for given expected status code for {@link NutchServer} 
from which port it runs
+   * with given username and password credentials
+   *
+   * @param expectedStatusCode expected status code
+   * @param port port that {@link NutchServer} runs at
+   * @param username username
+   * @param password password
+   */
+  public void testRequest(int expectedStatusCode, int port, String username, 
String password) {
+    testRequest(expectedStatusCode, port, username, password, DEFAULT_PATH, 
null);
+  }
+
+  /**
+   * Tests a request for given expected status code for {@link NutchServer} 
from which port it runs
+   * with given username and password credentials for a {@link ChallengeScheme}
+   *
+   * @param expectedStatusCode expected status code
+   * @param port port that {@link NutchServer} runs at
+   * @param username username
+   * @param password password
+   * @param challengeScheme challenge scheme
+   */
+  public void testRequest(int expectedStatusCode, int port, String username, 
String password, ChallengeScheme challengeScheme) {
+    testRequest(expectedStatusCode, port, username, password, DEFAULT_PATH, 
challengeScheme);
+  }
+
+  /**
+   * Tests a request for given expected status code for {@link NutchServer} 
from which port it runs
+   * with given username and password credentials for a {@link ChallengeScheme}
+   *
+   * @param expectedStatusCode expected status code
+   * @param port port that {@link NutchServer} runs at
+   * @param username username
+   * @param password password
+   * @param path path
+   * @param challengeScheme challenge scheme
+   */
+  public void testRequest(int expectedStatusCode, int port, String username, 
String password, String path,
+                          ChallengeScheme challengeScheme) {
+    if (port <= 0) {
+      port = DEFAULT_PORT;
+    }
+
+    if (!path.startsWith("/")) {
+      path += "/";
+    }
+    String protocol = 
AuthenticationTypeEnum.SSL.toString().equals(conf.get("restapi.auth")) ? 
"https" : "http";
+    ClientResource resource = new ClientResource(protocol + "://localhost:" + 
port + path);
+    if (challengeScheme != null) {
+      resource.setChallengeResponse(challengeScheme, username, password);
+    }
+
+    try {
+      resource.get();
+    } catch (ResourceException rex) {
+      //Don't use it. Use the status code to check afterwards
+    }
+
+    /*
+    If request was a HTTP Digest request, previous request was 401. Client 
needs some data sent by the server so
+    we should complete the ChallengeResponse.
+    */
+
+    if (ChallengeScheme.HTTP_DIGEST.equals(challengeScheme)) {
+      // User server's data to complete the challengeResponse object
+      ChallengeRequest digestChallengeRequest = 
retrieveDigestChallengeRequest(resource);
+      ChallengeResponse challengeResponse = new 
ChallengeResponse(digestChallengeRequest, resource.getResponse(),
+          username, password.toCharArray());
+      testDigestRequest(expectedStatusCode, resource, challengeResponse);
+    }
+    assertEquals(expectedStatusCode, resource.getStatus().getCode());
+  }
+
+  private void testDigestRequest(int expectedStatusCode, ClientResource 
resource, ChallengeResponse challengeResponse){
+    resource.setChallengeResponse(challengeResponse);
+    try {
+      resource.get();
+    } catch (ResourceException rex) {
+      //Don't use it. Use the status code to check afterwards
+    }
+    assertEquals(expectedStatusCode, resource.getStatus().getCode());
+  }
+
+  private ChallengeRequest retrieveDigestChallengeRequest (ClientResource 
resource) {
+    ChallengeRequest digestChallengeRequest = null;
+    for (ChallengeRequest cr : resource.getChallengeRequests()) {
+      if (ChallengeScheme.HTTP_DIGEST.equals(cr.getScheme())) {
+        digestChallengeRequest = cr;
+        break;
+      }
+    }
+    return digestChallengeRequest;
+  }
+
+  private void initializeSSLProperties() {
+    conf.set("restapi.auth.ssl.storepath", "src/test/nutch-ssl.keystore.jks");
+    conf.set("restapi.auth.ssl.storepass", "password");
+    conf.set("restapi.auth.ssl.keypass", "password");
+  }
+
+  /**
+   * Starts the server with given authentication type
+   *
+   * @param authenticationType authentication type
+   */
+  public void startServer(AuthenticationTypeEnum authenticationType) {
+    conf.set("restapi.auth", authenticationType.toString());
+    if(AuthenticationTypeEnum.SSL.equals(authenticationType)) {
+      initializeSSLProperties();
+    }
+
+    RAMConfManager ramConfManager = new 
RAMConfManager(NutchConfiguration.getUUID(conf), conf);
+    nutchServer = new NutchServer(ramConfManager, 
NutchConfiguration.getUUID(conf));
+    nutchServer.start();
+  }
+
+  /**
+   * Stops server
+   */
+  public void stopServer() {
+    nutchServer.stop(true);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/nutch/blob/3bc3d81e/src/test/org/apache/nutch/api/TestAPI.java
----------------------------------------------------------------------
diff --git a/src/test/org/apache/nutch/api/TestAPI.java 
b/src/test/org/apache/nutch/api/TestAPI.java
deleted file mode 100644
index 28e999a..0000000
--- a/src/test/org/apache/nutch/api/TestAPI.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*******************************************************************************
- * 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.
- 
******************************************************************************/
-
-// CURRENTLY DISABLED. TESTS ARE FLAPPING FOR NO APPARENT REASON.
-// SHALL BE FIXED OR REPLACES BY NEW API IMPLEMENTATION
-
-package org.apache.nutch.api;
-
-//import static org.junit.Assert.*;
-//
-//import java.util.HashMap;
-//import java.util.Map;
-//
-//import org.apache.nutch.api.JobManager.JobType;
-//import org.apache.nutch.metadata.Nutch;
-//import org.apache.nutch.util.NutchTool;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Test;
-//import org.restlet.ext.jackson.JacksonRepresentation;
-//import org.restlet.representation.Representation;
-//import org.restlet.resource.ClientResource;
-
-public class TestAPI {
-  @Test
-  public void test() throws Exception {
-  }
-  //
-  // private static NutchServer server;
-  // ClientResource cli;
-  //
-  // private static String baseUrl = "http://localhost:8192/nutch/";;
-  //
-  // @BeforeClass
-  // public static void before() throws Exception {
-  // server = new NutchServer(8192);
-  // server.start();
-  // }
-  //
-  // @AfterClass
-  // public static void after() throws Exception {
-  // if (!server.stop(false)) {
-  // for (int i = 1; i < 11; i++) {
-  // System.err.println("Waiting for jobs to complete - " + i + "s");
-  // try {
-  // Thread.sleep(1000);
-  // } catch (Exception e) {};
-  // server.stop(false);
-  // if (!server.isRunning()) {
-  // break;
-  // }
-  // }
-  // }
-  // if (server.isRunning()) {
-  // System.err.println("Forcibly stopping server...");
-  // server.stop(true);
-  // }
-  // }
-  //
-  // @Test
-  // public void testInfoAPI() throws Exception {
-  // ClientResource cli = new ClientResource(baseUrl);
-  // String expected =
-  // "[[\"admin\",\"Service admin actions\"],[\"confs\",\"Configuration 
manager\"],[\"db\",\"DB data streaming\"],[\"jobs\",\"Job manager\"]]";
-  // String got = cli.get().getText();
-  // assertEquals(expected, got);
-  // }
-  //
-  // @SuppressWarnings("rawtypes")
-  // @Test
-  // public void testConfsAPI() throws Exception {
-  // ClientResource cli = new ClientResource(baseUrl + ConfResource.PATH);
-  // assertEquals("[\"default\"]", cli.get().getText());
-  // // create
-  // Map<String,Object> map = new HashMap<String,Object>();
-  // map.put(Params.CONF_ID, "test");
-  // HashMap<String,String> props = new HashMap<String,String>();
-  // props.put("testProp", "blurfl");
-  // map.put(Params.PROPS, props);
-  // JacksonRepresentation<Map<String,Object>> jr =
-  // new JacksonRepresentation<Map<String,Object>>(map);
-  // System.out.println(cli.put(jr).getText());
-  // assertEquals("[\"default\",\"test\"]", cli.get().getText());
-  // cli = new ClientResource(baseUrl + ConfResource.PATH + "/test");
-  // Map res = cli.get(Map.class);
-  // assertEquals("blurfl", res.get("testProp"));
-  // // delete
-  // cli.delete();
-  // cli = new ClientResource(baseUrl + ConfResource.PATH);
-  // assertEquals("[\"default\"]", cli.get().getText());
-  // }
-  //
-  // @SuppressWarnings("rawtypes")
-  // @Test
-  // public void testJobsAPI() throws Exception {
-  // ClientResource cli = new ClientResource(baseUrl + JobResource.PATH);
-  // assertEquals("[]", cli.get().getText());
-  // // create
-  // Map<String,Object> map = new HashMap<String,Object>();
-  // map.put(Params.JOB_TYPE, JobType.READDB.toString());
-  // map.put(Params.CONF_ID, "default");
-  // Representation r = cli.put(map);
-  // String jobId = r.getText();
-  // assertNotNull(jobId);
-  // assertTrue(jobId.startsWith("default-READDB-"));
-  // // list
-  // Map[] list = cli.get(Map[].class);
-  // assertEquals(1, list.length);
-  // String id = (String)list[0].get("id");
-  // String state = (String)list[0].get("state");
-  // assertEquals(jobId, id);
-  // assertEquals(state, "RUNNING");
-  // int cnt = 10;
-  // do {
-  // try {
-  // Thread.sleep(2000);
-  // } catch (Exception e) {};
-  // list = cli.get(Map[].class);
-  // state = (String)list[0].get("state");
-  // if (!state.equals("RUNNING")) {
-  // break;
-  // }
-  // } while (--cnt > 0);
-  // assertTrue(cnt > 0);
-  // if (list == null) return;
-  // for (Map m : list) {
-  // System.out.println(m);
-  // }
-  // }
-  //
-  // @SuppressWarnings("unchecked")
-  // @Test
-  // public void testStopKill() throws Exception {
-  // ClientResource cli = new ClientResource(baseUrl + JobResource.PATH);
-  // // create
-  // Map<String,Object> map = new HashMap<String,Object>();
-  // map.put(Params.JOB_TYPE, JobType.CLASS.toString());
-  // Map<String,Object> args = new HashMap<String,Object>();
-  // map.put(Params.ARGS, args);
-  // args.put(Nutch.ARG_CLASS, SpinningJob.class.getName());
-  // map.put(Params.CONF_ID, "default");
-  // Representation r = cli.put(map);
-  // String jobId = r.getText();
-  // cli.release();
-  // assertNotNull(jobId);
-  // System.out.println(jobId);
-  // assertTrue(jobId.startsWith("default-CLASS-"));
-  // ClientResource stopCli = new ClientResource(baseUrl + JobResource.PATH +
-  // "?job=" + jobId + "&cmd=stop");
-  // r = stopCli.get();
-  // assertEquals("true", r.getText());
-  // stopCli.release();
-  // Thread.sleep(2000); // wait for the job to finish
-  // ClientResource jobCli = new ClientResource(baseUrl + JobResource.PATH + 
"/"
-  // + jobId);
-  // Map<String,Object> res = jobCli.get(Map.class);
-  // res = (Map<String,Object>)res.get("result");
-  // assertEquals("stopped", res.get("res"));
-  // jobCli.release();
-  // // restart and kill
-  // r = cli.put(map);
-  // jobId = r.getText();
-  // cli.release();
-  // assertNotNull(jobId);
-  // System.out.println(jobId);
-  // assertTrue(jobId.startsWith("default-CLASS-"));
-  // ClientResource killCli = new ClientResource(baseUrl + JobResource.PATH +
-  // "?job=" + jobId + "&cmd=abort");
-  // r = killCli.get();
-  // assertEquals("true", r.getText());
-  // killCli.release();
-  // Thread.sleep(2000); // wait for the job to finish
-  // jobCli = new ClientResource(baseUrl + JobResource.PATH + "/" + jobId);
-  // res = jobCli.get(Map.class);
-  // res = (Map<String,Object>)res.get("result");
-  // assertEquals("killed", res.get("res"));
-  // jobCli.release();
-  // }
-  //
-  // public static class SpinningJob extends NutchTool {
-  // volatile boolean shouldStop = false;
-  //
-  // @Override
-  // public Map<String, Object> run(Map<String, Object> args) throws Exception 
{
-  // status.put(Nutch.STAT_MESSAGE, "running");
-  // int cnt = 60;
-  // while (!shouldStop && cnt-- > 0) {
-  // Thread.sleep(1000);
-  // }
-  // if (cnt == 0) {
-  // results.put("res", "failed");
-  // }
-  // return results;
-  // }
-  //
-  // @Override
-  // public boolean stopJob() throws Exception {
-  // results.put("res", "stopped");
-  // shouldStop = true;
-  // return true;
-  // }
-  //
-  // @Override
-  // public boolean killJob() throws Exception {
-  // results.put("res", "killed");
-  // shouldStop = true;
-  // return true;
-  // }
-  //
-  // }
-}

http://git-wip-us.apache.org/repos/asf/nutch/blob/3bc3d81e/src/test/org/apache/nutch/api/TestNutchAPI.java
----------------------------------------------------------------------
diff --git a/src/test/org/apache/nutch/api/TestNutchAPI.java 
b/src/test/org/apache/nutch/api/TestNutchAPI.java
new file mode 100644
index 0000000..56aed92
--- /dev/null
+++ b/src/test/org/apache/nutch/api/TestNutchAPI.java
@@ -0,0 +1,100 @@
+/*******************************************************************************
+ * 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.nutch.api;
+
+import org.apache.nutch.api.security.AuthenticationTypeEnum;
+import org.junit.After;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.restlet.data.ChallengeScheme;
+
+/**
+ * Test class for {@link org.apache.nutch.api.NutchServer}
+ */
+public class TestNutchAPI extends AbstractNutchAPITestBase {
+
+  /**
+   * Test insecure connection
+   */
+  @Test
+  public void testInsecure() {
+    startServer(AuthenticationTypeEnum.NONE);
+    testRequest(200, 8081);
+  }
+
+  /**
+   * Test Basic Authentication for invalid username/password pair,
+   * authorized username/password pair and insufficient privileged 
username/password pair
+   */
+  @Test
+  public void testBasicAuth() {
+    startServer(AuthenticationTypeEnum.BASIC);
+    //Check for an invalid username/password pair
+    testRequest(401, 8081, "xxx", "xxx", ChallengeScheme.HTTP_BASIC);
+
+    //Check for an authorized username/password pair
+    testRequest(200, 8081, "admin", "admin", ChallengeScheme.HTTP_BASIC);
+
+    //Check for an insufficient privileged username/password pair
+    testRequest(403, 8081, "user", "user", ChallengeScheme.HTTP_BASIC);
+  }
+
+  /**
+   * Test Digest Authentication for invalid username/password pair,
+   * authorized username/password pair and insufficient privileged 
username/password pair
+   */
+  @Test
+  public void testDigestAuth() {
+    startServer(AuthenticationTypeEnum.DIGEST);
+    //Check for an invalid username/password pair
+    testRequest(401, 8081, "xxx", "xxx", ChallengeScheme.HTTP_DIGEST);
+
+    //Check for an authorized username/password pair
+    testRequest(200, 8081, "admin", "admin", ChallengeScheme.HTTP_DIGEST);
+
+    //Check for an insufficient privileged username/password pair
+    testRequest(403, 8081, "user", "user", ChallengeScheme.HTTP_DIGEST);
+  }
+
+  /**
+   * Test SSL for invalid username/password pair,
+   * authorized username/password pair and insufficient privileged 
username/password pair
+   */
+  @Ignore
+  @Test
+  public void testSSL() {
+    startServer(AuthenticationTypeEnum.SSL);
+    //Check for an invalid username/password pair
+    testRequest(401, 8081, "xxx", "xxx");
+
+    //Check for an authorized username/password pair
+    testRequest(200, 8081, "admin", "admin");
+
+    //Check for an insufficient privileged username/password pair
+    testRequest(403, 8081, "user", "user");
+  }
+
+  /**
+   * Stops the {@link NutchServer}
+   */
+  @After
+  public  void tearDown() {
+    stopServer();
+  }
+
+}
+

Reply via email to