You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@wookie.apache.org by sc...@apache.org on 2011/05/13 16:56:12 UTC

svn commit: r1102772 - in /incubator/wookie/trunk: WebContent/WEB-INF/web.xml src-tests/org/apache/wookie/tests/functional/ApiKeyControllerTest.java src/org/apache/wookie/controller/ApiKeyController.java src/org/apache/wookie/helpers/ApiKeyHelper.java

Author: scottbw
Date: Fri May 13 14:56:12 2011
New Revision: 1102772

URL: http://svn.apache.org/viewvc?rev=1102772&view=rev
Log:
Added an admin web API for managing API keys (see WOOKIE-208). However, there are some issues to resolve around API key migration and so PUT support is not currently enabled.

Added:
    incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ApiKeyControllerTest.java
    incubator/wookie/trunk/src/org/apache/wookie/controller/ApiKeyController.java
    incubator/wookie/trunk/src/org/apache/wookie/helpers/ApiKeyHelper.java
Modified:
    incubator/wookie/trunk/WebContent/WEB-INF/web.xml

Modified: incubator/wookie/trunk/WebContent/WEB-INF/web.xml
URL: http://svn.apache.org/viewvc/incubator/wookie/trunk/WebContent/WEB-INF/web.xml?rev=1102772&r1=1102771&r2=1102772&view=diff
==============================================================================
--- incubator/wookie/trunk/WebContent/WEB-INF/web.xml (original)
+++ incubator/wookie/trunk/WebContent/WEB-INF/web.xml Fri May 13 14:56:12 2011
@@ -209,6 +209,20 @@
 		<servlet-name>WhitelistController</servlet-name>
 		<url-pattern>/whitelist/*</url-pattern>
 	</servlet-mapping>
+	
+	<servlet>
+		<description></description>
+		<display-name>ApiKeys</display-name>
+		<servlet-name>ApiKeyController</servlet-name>
+		<servlet-class>
+			org.apache.wookie.controller.ApiKeyController
+		</servlet-class>
+		<load-on-startup>2</load-on-startup>
+	</servlet>	
+	<servlet-mapping>
+		<servlet-name>ApiKeyController</servlet-name>
+		<url-pattern>/keys/*</url-pattern>
+	</servlet-mapping>
 
 	<servlet>
 		<description></description>
@@ -364,7 +378,20 @@
 				<role-name>widgetadmin</role-name>
 			</auth-constraint>
 		</security-constraint>
-				<security-constraint>		
+		<security-constraint>		
+			<web-resource-collection>
+				<web-resource-name>ApiKeyController</web-resource-name>
+				<url-pattern>/keys/*</url-pattern>
+				<http-method>GET</http-method>
+				<http-method>DELETE</http-method>
+				<http-method>PUT</http-method>
+				<http-method>POST</http-method>
+			</web-resource-collection>		
+			<auth-constraint>
+				<role-name>widgetadmin</role-name>
+			</auth-constraint>
+		</security-constraint>
+		<security-constraint>		
 			<web-resource-collection>
 				<web-resource-name>UpdatesController</web-resource-name>
 				<url-pattern>/updates/*</url-pattern>

Added: incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ApiKeyControllerTest.java
URL: http://svn.apache.org/viewvc/incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ApiKeyControllerTest.java?rev=1102772&view=auto
==============================================================================
--- incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ApiKeyControllerTest.java (added)
+++ incubator/wookie/trunk/src-tests/org/apache/wookie/tests/functional/ApiKeyControllerTest.java Fri May 13 14:56:12 2011
@@ -0,0 +1,328 @@
+/*
+ * 
+ * Licensed 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.wookie.tests.functional;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.apache.commons.httpclient.HttpClient;
+import org.apache.commons.httpclient.methods.DeleteMethod;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
+import org.apache.commons.httpclient.methods.PutMethod;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.input.SAXBuilder;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Functional tests for API key management
+ */
+public class ApiKeyControllerTest extends AbstractControllerTest {
+
+  private static final String APIKEY_SERVICE_LOCATION_VALID = TEST_SERVER_LOCATION+"keys";
+
+  @Test
+  public void getEntriesUnauthorized(){
+    try {
+      HttpClient client = new HttpClient();
+      GetMethod get = new GetMethod(APIKEY_SERVICE_LOCATION_VALID);
+      client.executeMethod(get);
+      int code = get.getStatusCode();
+      assertEquals(401, code);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("get failed");
+    }
+  }
+
+  @Test
+  public void getKeys(){
+    try {
+      HttpClient client = new HttpClient();
+      GetMethod get = new GetMethod(APIKEY_SERVICE_LOCATION_VALID);
+      setAuthenticationCredentials(client);
+      client.executeMethod(get);
+      int code = get.getStatusCode();
+      assertEquals(200, code);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("get failed");
+    }
+  }
+
+  @Test
+  public void addKey(){
+    try {
+      HttpClient client = new HttpClient();
+      PostMethod post = new PostMethod(APIKEY_SERVICE_LOCATION_VALID);
+      setAuthenticationCredentials(client);
+      post.setParameter("apikey", "TEST_KEY");
+      post.setParameter("email", "test@incubator.apache.org");
+      client.executeMethod(post);
+      int code = post.getStatusCode();
+      assertEquals(201, code);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("post failed");
+    } 
+    try {
+      HttpClient client = new HttpClient();
+      GetMethod get = new GetMethod(APIKEY_SERVICE_LOCATION_VALID);
+      setAuthenticationCredentials(client);
+      client.executeMethod(get);
+      int code = get.getStatusCode();
+      assertEquals(200, code);
+      assertTrue(get.getResponseBodyAsString().contains("TEST_KEY"));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("get failed");
+    }
+  }
+
+  @Test
+  public void removeKey(){
+    String id = null;
+    try {
+      HttpClient client = new HttpClient();
+      GetMethod get = new GetMethod(APIKEY_SERVICE_LOCATION_VALID);
+      setAuthenticationCredentials(client);
+      client.executeMethod(get);
+      int code = get.getStatusCode();
+      assertEquals(200, code);
+      assertTrue(get.getResponseBodyAsString().contains("TEST_KEY"));
+      // Get the ID
+      Document doc = new SAXBuilder().build(get.getResponseBodyAsStream());
+      for (Object key: doc.getRootElement().getChildren()){
+        Element keyElement = (Element)key;
+        if (keyElement.getAttributeValue("value").equals("TEST_KEY")){
+          id = keyElement.getAttributeValue("id");
+        }
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("get failed");
+    }
+    try {
+      HttpClient client = new HttpClient();
+      DeleteMethod del = new DeleteMethod(APIKEY_SERVICE_LOCATION_VALID+"/"+id);
+      setAuthenticationCredentials(client);
+      client.executeMethod(del);
+      int code = del.getStatusCode();
+      assertEquals(200, code);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("delete failed");
+    }
+    try {
+      HttpClient client = new HttpClient();
+      GetMethod get = new GetMethod(APIKEY_SERVICE_LOCATION_VALID);
+      get.setRequestHeader("Content-type", "application/json");
+      setAuthenticationCredentials(client);
+      client.executeMethod(get);
+      int code = get.getStatusCode();
+      assertEquals(200, code);
+      System.out.println(get.getResponseBodyAsString());
+      assertFalse(get.getResponseBodyAsString().contains("TEST_KEY"));
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("get failed");
+    }
+  }
+
+  @Test
+  public void removeNonExistantEntry(){
+    try {
+      HttpClient client = new HttpClient();
+      DeleteMethod del = new DeleteMethod(APIKEY_SERVICE_LOCATION_VALID+"/99999999");
+      setAuthenticationCredentials(client);
+      client.executeMethod(del);
+      int code = del.getStatusCode();
+      assertEquals(404, code);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("delete failed");
+    } 
+  }
+
+  @Test
+  public void addEntryNoEmailOrValue(){
+    try {
+      HttpClient client = new HttpClient();
+      PostMethod post = new PostMethod(APIKEY_SERVICE_LOCATION_VALID);
+      setAuthenticationCredentials(client);
+      client.executeMethod(post);
+      int code = post.getStatusCode();
+      assertEquals(400, code);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("post failed");
+    } 
+  }
+
+  @Test
+  public void addDuplicateEntry(){
+    try {
+      HttpClient client = new HttpClient();
+      PostMethod post = new PostMethod(APIKEY_SERVICE_LOCATION_VALID);
+      setAuthenticationCredentials(client);
+      post.setParameter("apikey", "DUPLICATION_TEST");
+      post.setParameter("email", "test@127.0.0.1");
+      client.executeMethod(post);
+      int code = post.getStatusCode();
+      assertEquals(201, code);
+      client.executeMethod(post);
+      code = post.getStatusCode();
+      assertEquals(409, code);
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("post failed");
+    }
+  }
+
+  @Test
+  @Ignore
+  public void migrateAPIKey(){
+    
+    String keyId = null;
+    // Create a new key
+    try {
+      HttpClient client = new HttpClient();
+      PostMethod post = new PostMethod(APIKEY_SERVICE_LOCATION_VALID);
+      setAuthenticationCredentials(client);
+      post.setParameter("apikey", "MIGRATION_TEST_KEY_1");
+      post.setParameter("email", "test@incubator.apache.org");
+      client.executeMethod(post);
+      int code = post.getStatusCode();
+      assertEquals(201, code);
+
+      GetMethod get = new GetMethod(APIKEY_SERVICE_LOCATION_VALID);
+      setAuthenticationCredentials(client);
+      client.executeMethod(get);
+
+      // Get the ID
+      Document doc = new SAXBuilder().build(get.getResponseBodyAsStream());
+      for (Object key: doc.getRootElement().getChildren()){
+        Element keyElement = (Element)key;
+        if (keyElement.getAttributeValue("value").equals("MIGRATION_TEST_KEY_1")){
+          keyId = keyElement.getAttributeValue("id");
+        }
+      }
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("get failed");
+    }
+
+    // Create a widget instance
+    String instance_id_key =  null;
+    try {
+      HttpClient client = new HttpClient();
+      PostMethod post = new PostMethod(TEST_INSTANCES_SERVICE_URL_VALID);
+      post.setQueryString("api_key=MIGRATION_TEST_KEY_1&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=migration_test");
+      client.executeMethod(post);
+      int code = post.getStatusCode();
+      String response = post.getResponseBodyAsString();
+      instance_id_key = post.getResponseBodyAsString().substring(response.indexOf("<identifier>")+12, response.indexOf("</identifier>"));
+      assertEquals(201,code);
+      post.releaseConnection();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("post failed");
+    }
+
+    // Set participant
+    try {
+      HttpClient client = new HttpClient();
+      PostMethod post = new PostMethod(TEST_PARTICIPANTS_SERVICE_URL_VALID);
+      post.setQueryString("api_key=MIGRATION_TEST_KEY_1&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=migration_test&participant_id=1&participant_display_name=bob&participant_thumbnail_url=http://www.test.org");
+      client.executeMethod(post);
+      int code = post.getStatusCode();
+      assertEquals(201,code);
+      post.releaseConnection();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("post failed");
+    }
+
+    // Migrate key
+    try {
+      HttpClient client = new HttpClient();
+      PutMethod put = new PutMethod(APIKEY_SERVICE_LOCATION_VALID+"/"+keyId);
+      put.setQueryString("apikey=MIGRATION_TEST_KEY_2&email=test@127.0.0.1");
+      setAuthenticationCredentials(client);
+      client.executeMethod(put);
+      int code = put.getStatusCode();
+      assertEquals(200,code);
+      put.releaseConnection();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("post failed");
+    }
+
+    // Get instance again using the new key - should be 200 not 201
+    try {
+      HttpClient client = new HttpClient();
+      PostMethod post = new PostMethod(TEST_INSTANCES_SERVICE_URL_VALID);
+      post.setQueryString("api_key=MIGRATION_TEST_KEY_2&widgetid="+WIDGET_ID_VALID+"&userid=test&shareddatakey=migration_test");
+      client.executeMethod(post);
+      int code = post.getStatusCode();
+      assertEquals(200,code);
+      post.releaseConnection();
+    }
+    catch (Exception e) {
+      e.printStackTrace();
+      fail("post failed");
+    }
+
+    // Get participant
+    try {
+      HttpClient client = new HttpClient();
+      GetMethod get = new GetMethod(TEST_PARTICIPANTS_SERVICE_URL_VALID);
+      get.setQueryString("api_key=MIGRATION_TEST_KEY_2&id_key="+instance_id_key);
+      client.executeMethod(get);
+      int code = get.getStatusCode();
+      assertEquals(200,code);
+      String response = get.getResponseBodyAsString();
+      assertTrue(response.contains("<participant id=\"1\" display_name=\"bob\" thumbnail_url=\"http://www.test.org\" />"));
+      get.releaseConnection();
+  }
+  catch (Exception e) {
+    e.printStackTrace();
+    fail("get failed");
+  }
+
+
+  }
+}

Added: incubator/wookie/trunk/src/org/apache/wookie/controller/ApiKeyController.java
URL: http://svn.apache.org/viewvc/incubator/wookie/trunk/src/org/apache/wookie/controller/ApiKeyController.java?rev=1102772&view=auto
==============================================================================
--- incubator/wookie/trunk/src/org/apache/wookie/controller/ApiKeyController.java (added)
+++ incubator/wookie/trunk/src/org/apache/wookie/controller/ApiKeyController.java Fri May 13 14:56:12 2011
@@ -0,0 +1,155 @@
+/*
+ * 
+ * Licensed 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.wookie.controller;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.log4j.Logger;
+import org.apache.wookie.beans.IApiKey;
+import org.apache.wookie.beans.util.IPersistenceManager;
+import org.apache.wookie.beans.util.PersistenceManagerFactory;
+import org.apache.wookie.exceptions.InvalidParametersException;
+import org.apache.wookie.exceptions.ResourceDuplicationException;
+import org.apache.wookie.exceptions.ResourceNotFoundException;
+import org.apache.wookie.exceptions.UnauthorizedAccessException;
+import org.apache.wookie.helpers.ApiKeyHelper;
+
+/**
+ * Admin controller for creating, updating and listing API keys
+ * 
+ * <ul>
+ * <li>GET /keys - index <em>requires authentication</em></li>
+ * <li>POST /keys {apikey, email} - create <em>requires authentication</em></li>
+ * <li>PUT /keys/{id} {apikey, email} - update <em>requires authentication</em></li>
+ * <li>DELETE /keys/{id} - remove <em>requires authentication</em></li>
+ * </ul>
+ * 
+ * Note that PUT support is disabled until a solution is available for migrating
+ * widget instances, shared data and participants
+ */
+
+public class ApiKeyController extends Controller {
+
+  private static final long serialVersionUID = -2985087125119757793L;
+  static Logger _logger = Logger.getLogger(ApiKeyController.class.getName()); 
+
+  /* (non-Javadoc)
+   * @see org.apache.wookie.controller.Controller#index(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
+   */
+  @Override
+  protected void index(HttpServletRequest request, HttpServletResponse response)
+      throws UnauthorizedAccessException, IOException {
+    IPersistenceManager persistenceManager = PersistenceManagerFactory.getPersistenceManager();
+    IApiKey[] apiKeys = persistenceManager.findAll(IApiKey.class);
+    returnXml(ApiKeyHelper.createXML(apiKeys),response);
+  }
+
+  /* (non-Javadoc)
+   * @see org.apache.wookie.controller.Controller#create(java.lang.String, javax.servlet.http.HttpServletRequest)
+   */
+  @Override
+  protected boolean create(String resourceId, HttpServletRequest request)
+      throws ResourceDuplicationException, InvalidParametersException,
+      UnauthorizedAccessException {
+    String value = request.getParameter("apikey");
+    String email = request.getParameter("email");
+    if (value == null || email == null || value.trim().length() ==0 || email.trim().length() == 0) throw new InvalidParametersException();
+    IPersistenceManager persistenceManager = PersistenceManagerFactory.getPersistenceManager();
+    Map<String, Object> values = new HashMap<String, Object>();
+    values.put("value", value);
+    values.put("email", email);
+    if (persistenceManager.findByValues(IApiKey.class, values).length > 0){
+      throw new ResourceDuplicationException();
+    }
+    
+    IApiKey apiKey = persistenceManager.newInstance(IApiKey.class);
+    apiKey.setValue(value);
+    apiKey.setEmail(email);
+    persistenceManager.save(apiKey);
+    _logger.info("New API key registered for "+email);
+    return true;
+  }
+
+  
+/*
+  @Override
+  protected void update(String resourceId, HttpServletRequest request)
+      throws ResourceNotFoundException, InvalidParametersException,
+      UnauthorizedAccessException {
+
+    String value = request.getParameter("apikey");
+    String email = request.getParameter("email");
+    if (value == null || email == null || value.trim().length() ==0 || email.trim().length() == 0) throw new InvalidParametersException();
+    
+    IPersistenceManager persistenceManager = PersistenceManagerFactory.getPersistenceManager();
+    IApiKey apiKey = persistenceManager.findById(IApiKey.class, resourceId);
+    if (apiKey == null) throw new ResourceNotFoundException();
+    String oldValue = apiKey.getValue();
+    String oldEmail = apiKey.getEmail();
+    apiKey.setEmail(email);
+    apiKey.setValue(value);
+    persistenceManager.save(apiKey);
+    migrateWidgetInstances(apiKey, oldValue);
+    _logger.info("API key updated from "+oldEmail+" : "+oldValue + " to "+email + " : "+value);
+
+  }
+*/
+
+  
+  /**
+   * Migrates any widget instances using the previous key to the new key.
+   * @param key
+   * @param oldValue
+   */
+ /*
+  private void migrateWidgetInstances(IApiKey apiKey, String oldValue){
+    IPersistenceManager persistenceManager = PersistenceManagerFactory.getPersistenceManager();
+    // 
+    IWidgetInstance[] instances = persistenceManager.findByValue(IWidgetInstance.class, "apiKey", oldValue);
+    for (IWidgetInstance instance: instances){
+      //FIXME this doesn't really work right now because we can't migrate the shared data key. To do
+      //      this we would need to store both the original shared data key and the internal version in the WidgetInstance or
+      //      somewhere. (We then ought to rename one of them to make it clear which it is). We could then transparently
+      //      update all the sharedDataKeys for instances, participants and shared data
+      instance.setApiKey(apiKey.getValue());
+      persistenceManager.save(instance);
+    }
+  }
+ */
+
+  /* (non-Javadoc)
+   * @see org.apache.wookie.controller.Controller#remove(java.lang.String, javax.servlet.http.HttpServletRequest)
+   */
+  @Override
+  protected boolean remove(String resourceId, HttpServletRequest request)
+      throws ResourceNotFoundException, UnauthorizedAccessException,
+      InvalidParametersException {
+    IPersistenceManager persistenceManager = PersistenceManagerFactory.getPersistenceManager();
+    IApiKey apiKey = persistenceManager.findById(IApiKey.class, resourceId);
+    if (apiKey == null) throw new ResourceNotFoundException();
+    persistenceManager.delete(apiKey);
+    _logger.info("API key deleted for "+apiKey.getEmail());
+    return true;
+  }
+  
+}

Added: incubator/wookie/trunk/src/org/apache/wookie/helpers/ApiKeyHelper.java
URL: http://svn.apache.org/viewvc/incubator/wookie/trunk/src/org/apache/wookie/helpers/ApiKeyHelper.java?rev=1102772&view=auto
==============================================================================
--- incubator/wookie/trunk/src/org/apache/wookie/helpers/ApiKeyHelper.java (added)
+++ incubator/wookie/trunk/src/org/apache/wookie/helpers/ApiKeyHelper.java Fri May 13 14:56:12 2011
@@ -0,0 +1,50 @@
+/*
+ * 
+ * Licensed 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.wookie.helpers;
+
+import org.apache.wookie.beans.IApiKey;
+import org.jdom.Document;
+import org.jdom.Element;
+import org.jdom.output.XMLOutputter;
+
+/**
+ * View helper for API keys
+ */
+public class ApiKeyHelper {
+
+  /**
+   * Create an XML representation of a set of API keys
+   * @param keys the keys to serialize
+   * @return a String containing the XML serialization of the API keys
+   */
+  public static String createXML(IApiKey[] keys){
+    Document document = new Document();
+    Element keysElement = new Element("keys");
+
+    for(IApiKey key: keys){
+      Element keyElement = new Element("key");
+      keyElement.setAttribute("id", String.valueOf(key.getId()));
+      keyElement.setAttribute("value", key.getValue());
+      keyElement.setAttribute("email", key.getEmail());
+      keysElement.addContent(keyElement);
+    }
+    document.setRootElement(keysElement);
+    XMLOutputter outputter = new XMLOutputter();
+    return outputter.outputString(document);
+  }
+}