From 8e224cb5fe23db314b2c4b37f864ea86e8087cc1 Mon Sep 17 00:00:00 2001 From: Mark Rogers Date: Wed, 7 Jan 2009 16:38:17 +0000 Subject: [PATCH] Beefing up public_api Authentication rest scripts. Add unit test. Add JSON. Add post for login. Fixed NPE in validate ticket. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@12606 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../alfresco/repository/login.get.json.ftl | 6 + .../alfresco/repository/login.post.desc.xml | 24 ++ .../alfresco/repository/login.post.json.ftl | 6 + .../alfresco/repository/login.post.xml.ftl | 2 + .../repository/loginticket.get.desc.xml | 11 +- .../tagging/node.tags.post.desc.xml | 3 +- .../repository/upload/upload.post.desc.xml | 4 +- .../web-scripts-application-context.xml | 5 + .../alfresco/repo/web/scripts/LoginTest.java | 253 ++++++++++++++++++ .../web/scripts/bean/AbstractLoginBean.java | 88 ++++++ .../alfresco/repo/web/scripts/bean/Login.java | 36 +-- .../repo/web/scripts/bean/LoginPost.java | 93 +++++++ .../repo/web/scripts/bean/LoginTicket.java | 7 +- 13 files changed, 496 insertions(+), 42 deletions(-) create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/login.get.json.ftl create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/login.post.desc.xml create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/login.post.json.ftl create mode 100644 config/alfresco/templates/webscripts/org/alfresco/repository/login.post.xml.ftl create mode 100644 source/java/org/alfresco/repo/web/scripts/LoginTest.java create mode 100644 source/java/org/alfresco/repo/web/scripts/bean/AbstractLoginBean.java create mode 100644 source/java/org/alfresco/repo/web/scripts/bean/LoginPost.java diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/login.get.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/login.get.json.ftl new file mode 100644 index 0000000000..d023f4aec5 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/login.get.json.ftl @@ -0,0 +1,6 @@ +{ + data: + { + ticket:${ticket} + } +} diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/login.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/login.post.desc.xml new file mode 100644 index 0000000000..b8a043b985 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/login.post.desc.xml @@ -0,0 +1,24 @@ + + Login (POST) + + + Input +
+ JSON Data Object. +
+
username
cleartext username
+
password
cleartext password
+
+
+ Returns the new authentication ticket. + ]]> +
+ /api/login + + none + required + public_api + Authentication +
\ No newline at end of file diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/login.post.json.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/login.post.json.ftl new file mode 100644 index 0000000000..d023f4aec5 --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/login.post.json.ftl @@ -0,0 +1,6 @@ +{ + data: + { + ticket:${ticket} + } +} diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/login.post.xml.ftl b/config/alfresco/templates/webscripts/org/alfresco/repository/login.post.xml.ftl new file mode 100644 index 0000000000..d311e170fd --- /dev/null +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/login.post.xml.ftl @@ -0,0 +1,2 @@ + +${ticket} diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/loginticket.get.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/loginticket.get.desc.xml index bf949010e5..86e47b1be8 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/loginticket.get.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/loginticket.get.desc.xml @@ -1,13 +1,16 @@ Validate Login Ticket - The ticket may be invalid, or expired, or the user may have been locked out. + The ticket may be invalid, or expired, or the user may have been locked out. +
+ For security reasons this script will not validate the ticket of another user.
    -
  • If the ticket is valid retuns 200, STATUS_SUCCESS
  • -
  • If the ticket is not valid returns a 404, STATUS_NOT_FOUND
  • +
  • If the ticket is valid retuns, STATUS_SUCCESS (200)
  • +
  • If the ticket is not valid return, STATUS_NOT_FOUND (404)
  • +
  • If the ticket does not belong to the current user, STATUS_NOT_FOUND (404)
]]>
diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/tagging/node.tags.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/tagging/node.tags.post.desc.xml index 315c4f02bf..884e6f06ce 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/tagging/node.tags.post.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/tagging/node.tags.post.desc.xml @@ -2,7 +2,8 @@ Add tag Input: array of String +
Input: +
(mandatory) array of String
Returns the array of tags diff --git a/config/alfresco/templates/webscripts/org/alfresco/repository/upload/upload.post.desc.xml b/config/alfresco/templates/webscripts/org/alfresco/repository/upload/upload.post.desc.xml index cca092796a..a7cb44b82e 100644 --- a/config/alfresco/templates/webscripts/org/alfresco/repository/upload/upload.post.desc.xml +++ b/config/alfresco/templates/webscripts/org/alfresco/repository/upload/upload.post.desc.xml @@ -1,12 +1,12 @@ File Upload HTML form data
    -
  • filedata
  • +
  • filedata, (mandatory) HTML type file
  • siteid
  • containerid
  • uploaddirectory
  • diff --git a/config/alfresco/web-scripts-application-context.xml b/config/alfresco/web-scripts-application-context.xml index b21140a780..f9fa0b832d 100644 --- a/config/alfresco/web-scripts-application-context.xml +++ b/config/alfresco/web-scripts-application-context.xml @@ -190,6 +190,11 @@ + + + + + diff --git a/source/java/org/alfresco/repo/web/scripts/LoginTest.java b/source/java/org/alfresco/repo/web/scripts/LoginTest.java new file mode 100644 index 0000000000..6ac7a2616c --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/LoginTest.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.scripts; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.PropertyMap; +import org.alfresco.web.scripts.Status; +import org.alfresco.web.scripts.TestWebScriptServer.GetRequest; +import org.alfresco.web.scripts.TestWebScriptServer.DeleteRequest; +import org.alfresco.web.scripts.TestWebScriptServer.PostRequest; +import org.alfresco.web.scripts.TestWebScriptServer.Response; +import org.json.JSONObject; + +/** + * Junit test for login / logout and validate web scripts + * + * testing uri /api/login + */ +public class LoginTest extends BaseWebScriptTest +{ + private AuthenticationService authenticationService; + private AuthenticationComponent authenticationComponent; + private PersonService personService; + + private static final String USER_ONE = "AuthenticationTestOne"; + + protected void setUp() throws Exception + { + super.setUp(); + this.authenticationService = (AuthenticationService)getServer().getApplicationContext().getBean("AuthenticationService"); + this.authenticationComponent = (AuthenticationComponent)getServer().getApplicationContext().getBean("authenticationComponent"); + this.personService = (PersonService)getServer().getApplicationContext().getBean("PersonService"); + + this.authenticationComponent.setSystemUserAsCurrentUser(); + createUser(USER_ONE, USER_ONE); + } + + protected void tearDown() throws Exception + { + super.tearDown(); + } + + private void createUser(String userName, String password) + { + if (this.authenticationService.authenticationExists(userName) == false) + { + this.authenticationService.createAuthentication(userName, password.toCharArray()); + + PropertyMap ppOne = new PropertyMap(4); + ppOne.put(ContentModel.PROP_USERNAME, userName); + ppOne.put(ContentModel.PROP_FIRSTNAME, "firstName"); + ppOne.put(ContentModel.PROP_LASTNAME, "lastName"); + ppOne.put(ContentModel.PROP_EMAIL, "email@email.com"); + ppOne.put(ContentModel.PROP_JOBTITLE, "jobTitle"); + + this.personService.createPerson(ppOne); + } + } + + private String parseTicket(String ticketResult) + { + int startTag = ticketResult.indexOf(""); + int endTag = ticketResult.indexOf(""); + if ((startTag != -1) && (endTag != -1)) + { + return ticketResult.substring(startTag+("".length()), endTag); + } + return ""; + } + + /** + * Positive test - login and retrieve a ticket via get - return xml, + * - via get method + * validate ticket + * logout + * fail to validate ticket + * fail to get ticket + */ + public void testAuthentication() throws Exception + { + /** + * Login via get method to return xml + */ + String loginURL = "/api/login?u=" + USER_ONE + "&pw=" + USER_ONE; + Response resp = sendRequest(new GetRequest(loginURL), Status.STATUS_OK); + String xmlFragment = resp.getContentAsString(); + + assertNotNull("xmlFragment"); + assertTrue("xmlFragment contains ticket", xmlFragment.contains("")); + String ticket = parseTicket(xmlFragment); + + String ticketURL = "/api/login/ticket/"+ticket; + + /** + * Negative test - validate as "admin" - should fail with a 404 + */ + setDefaultRunAs("admin"); + sendRequest(new GetRequest(ticketURL), Status.STATUS_NOT_FOUND); + + /** + * Validate the ticket - should succeed + */ + setDefaultRunAs(USER_ONE); + + sendRequest(new GetRequest(ticketURL), Status.STATUS_OK); + + /** + * Logout + */ + sendRequest(new DeleteRequest(ticketURL), Status.STATUS_OK); + + /** + * Validate the ticket - should fail now + */ + sendRequest(new GetRequest(ticketURL), Status.STATUS_NOT_FOUND); + + } + + /** + * Positive test - login and retrieve a ticket, + * - via json method + */ + public void testAuthenticationGetJSON() throws Exception + { + /** + * Login via get method to return json + */ + String loginURL = "/api/login.json?u=" + USER_ONE + "&pw=" + USER_ONE ; + Response resp = sendRequest(new GetRequest(loginURL), Status.STATUS_OK); + JSONObject result = new JSONObject(resp.getContentAsString()); + JSONObject data = result.getJSONObject("data"); + String ticket = data.getString("ticket"); + assertNotNull("ticket is null", ticket); + + /** + * This is now testing the framework ... With a different format. + */ + String login2URL = "/api/login?u=" + USER_ONE + "&pw=" + USER_ONE + "&format=json"; + Response resp2 = sendRequest(new GetRequest(login2URL), Status.STATUS_OK); + JSONObject result2 = new JSONObject(resp2.getContentAsString()); + JSONObject data2 = result2.getJSONObject("data"); + String ticket2 = data2.getString("ticket"); + assertNotNull("ticket is null", ticket2); + + } + + /** + * Authenticate via a POST + * @throws Exception + */ + public void testPostLogin() throws Exception + { + String loginURL = "/api/login"; + /** + * logon via POST and JSON + */ + { + JSONObject req = new JSONObject(); + req.put("username", USER_ONE); + req.put("password", USER_ONE); + Response response = sendRequest(new PostRequest(loginURL, req.toString(), "application/json"), Status.STATUS_OK); + + JSONObject result = new JSONObject(response.getContentAsString()); + JSONObject data = result.getJSONObject("data"); + String ticket = data.getString("ticket"); + assertNotNull("ticket null", ticket); + } + + /** + * Negative test - wrong password + */ + { + JSONObject req = new JSONObject(); + req.put("username", USER_ONE); + req.put("password", "blurb"); + sendRequest(new PostRequest(loginURL, req.toString(), "application/json"), Status.STATUS_FORBIDDEN); + } + /** + * Negative test - missing username + */ + { + JSONObject req = new JSONObject(); + req.put("password", USER_ONE); + sendRequest(new PostRequest(loginURL, req.toString(), "application/json"), Status.STATUS_BAD_REQUEST); + } + + /** + * Negative test - missing password + */ + { + JSONObject req = new JSONObject(); + req.put("username", USER_ONE); + sendRequest(new PostRequest(loginURL, req.toString(), "application/json"), Status.STATUS_BAD_REQUEST); + } + } + + + /** + * Negative tests - wrong password + */ + public void testWrongPassword() throws Exception + { + /** + * Login via get method and wrong password, should get FORBIDDEN + */ + String loginURL = "/api/login?u=" + USER_ONE + "&pw=" + "crap"; + sendRequest(new GetRequest(loginURL), Status.STATUS_FORBIDDEN); + } + + /** + * Negative test - missing parameters + */ + public void testMissingParameters() throws Exception + { + /** + * Login via get method missing pw + */ + String loginURL = "/api/login?u=" + USER_ONE; + sendRequest(new GetRequest(loginURL), Status.STATUS_BAD_REQUEST); + + /** + * Login via get method missing u + */ + String login2URL = "/api/login?&pw=" + USER_ONE; + sendRequest(new GetRequest(login2URL), Status.STATUS_BAD_REQUEST); + + } +} diff --git a/source/java/org/alfresco/repo/web/scripts/bean/AbstractLoginBean.java b/source/java/org/alfresco/repo/web/scripts/bean/AbstractLoginBean.java new file mode 100644 index 0000000000..11d063c069 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/bean/AbstractLoginBean.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2005-2007 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.scripts.bean; + +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.service.cmr.security.AuthenticationService; +import org.alfresco.web.scripts.DeclarativeWebScript; +import org.alfresco.web.scripts.Status; +import org.alfresco.web.scripts.WebScriptException; +import org.alfresco.web.scripts.WebScriptRequest; + + +/** + * common code between Get based login and POST based login + */ +/* package scope */ abstract class AbstractLoginBean extends DeclarativeWebScript +{ + // dependencies + private AuthenticationService authenticationService; + + /** + * @param authenticationService + */ + public void setAuthenticationService(AuthenticationService authenticationService) + { + this.authenticationService = authenticationService; + } + + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) + */ + @Override + protected Map executeImpl(WebScriptRequest req, Status status) + { + return null; + } + + protected Map login(String username, String password) + { + + try + { + // get ticket + authenticationService.authenticate(username, password.toCharArray()); + + // add ticket to model for javascript and template access + Map model = new HashMap(7, 1.0f); + model.put("ticket", authenticationService.getCurrentTicket()); + return model; + } + catch(AuthenticationException e) + { + throw new WebScriptException(HttpServletResponse.SC_FORBIDDEN, "Login failed"); + } + finally + { + authenticationService.clearCurrentSecurityContext(); + } + } +} diff --git a/source/java/org/alfresco/repo/web/scripts/bean/Login.java b/source/java/org/alfresco/repo/web/scripts/bean/Login.java index 3bf64e03ca..e4ab427ee4 100644 --- a/source/java/org/alfresco/repo/web/scripts/bean/Login.java +++ b/source/java/org/alfresco/repo/web/scripts/bean/Login.java @@ -42,24 +42,11 @@ import org.alfresco.web.scripts.WebScriptRequest; * * @author davidc */ -public class Login extends DeclarativeWebScript -{ - // dependencies - private AuthenticationService authenticationService; - - /** - * @param authenticationService - */ - public void setAuthenticationService(AuthenticationService authenticationService) - { - this.authenticationService = authenticationService; - } - - +public class Login extends AbstractLoginBean +{ /* (non-Javadoc) * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) */ - @Override protected Map executeImpl(WebScriptRequest req, Status status) { // extract username and password @@ -74,24 +61,7 @@ public class Login extends DeclarativeWebScript throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Password not specified"); } - try - { - // get ticket - authenticationService.authenticate(username, password.toCharArray()); - - // add ticket to model for javascript and template access - Map model = new HashMap(7, 1.0f); - model.put("ticket", authenticationService.getCurrentTicket()); - return model; - } - catch(AuthenticationException e) - { - throw new WebScriptException(HttpServletResponse.SC_FORBIDDEN, "Login failed"); - } - finally - { - authenticationService.clearCurrentSecurityContext(); - } + return login(username, password); } } diff --git a/source/java/org/alfresco/repo/web/scripts/bean/LoginPost.java b/source/java/org/alfresco/repo/web/scripts/bean/LoginPost.java new file mode 100644 index 0000000000..f6fe863061 --- /dev/null +++ b/source/java/org/alfresco/repo/web/scripts/bean/LoginPost.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2005-2009 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.repo.web.scripts.bean; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.http.HttpServletResponse; + +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.util.Content; +import org.alfresco.web.scripts.Status; +import org.alfresco.web.scripts.WebScriptException; +import org.alfresco.web.scripts.WebScriptRequest; +import org.json.JSONException; +import org.json.JSONObject; + + +/** + * Post based login script + * + */ +public class LoginPost extends AbstractLoginBean +{ + + /* (non-Javadoc) + * @see org.alfresco.web.scripts.DeclarativeWebScript#executeImpl(org.alfresco.web.scripts.WebScriptRequest, org.alfresco.web.scripts.WebScriptResponse) + */ + protected Map executeImpl(WebScriptRequest req, Status status) + { + // Extract user and password from JSON POST + Content c = req.getContent(); + if (c == null) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, + "Missing POST body."); + } + // TODO accept xml type. + + // extract username and password from JSON object + JSONObject json; + try { + json = new JSONObject(c.getContent()); + String username = json.getString("username"); + String password = json.getString("password"); + + if (username == null || username.length() == 0) + { + throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Username not specified"); + } + + if (password == null) + { + throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Password not specified"); + } + + return login(username, password); + } + catch (JSONException jErr) + { + throw new WebScriptException(Status.STATUS_BAD_REQUEST, + "Unable to parse JSON POST body: " + jErr.getMessage()); + } + catch (IOException ioErr) + { + throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, + "Unable to retrieve POST body: " + ioErr.getMessage()); + } + } +} diff --git a/source/java/org/alfresco/repo/web/scripts/bean/LoginTicket.java b/source/java/org/alfresco/repo/web/scripts/bean/LoginTicket.java index 1f3ca789ae..458d62d289 100644 --- a/source/java/org/alfresco/repo/web/scripts/bean/LoginTicket.java +++ b/source/java/org/alfresco/repo/web/scripts/bean/LoginTicket.java @@ -77,9 +77,12 @@ public class LoginTicket extends DeclarativeWebScript try { String ticketUser = ticketComponent.validateTicket(ticket); + + String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); - // do not go any further if tickets are different - if (!AuthenticationUtil.getFullyAuthenticatedUser().equals(ticketUser)) + // do not go any further if tickets are different + // or the user is not fully authenticated + if (currentUser == null || !currentUser.equals(ticketUser)) { status.setRedirect(true); status.setCode(HttpServletResponse.SC_NOT_FOUND);