/* * Copyright (C) 2005-2016 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ package org.alfresco.rest.api.tests; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.People; import org.alfresco.rest.api.model.LoginTicket; import org.alfresco.rest.api.model.LoginTicketResponse; import org.alfresco.rest.api.sites.SiteEntityResource; import org.alfresco.rest.api.tests.client.HttpResponse; import org.alfresco.rest.api.tests.client.PublicApiClient.Paging; import org.alfresco.rest.api.tests.client.data.Document; import org.alfresco.rest.api.tests.client.data.Folder; import org.alfresco.rest.api.tests.util.RestApiUtil; import org.alfresco.service.cmr.security.MutableAuthenticationService; import org.alfresco.service.cmr.security.PersonService; import org.apache.commons.codec.binary.Base64; import org.junit.After; import org.junit.Before; import org.junit.Test; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * @author Jamal Kaabi-Mofrad */ public class AuthenticationsTest extends AbstractBaseApiTest { private String user1; private String user2; private List users = new ArrayList<>(); protected MutableAuthenticationService authenticationService; protected PersonService personService; @Before public void setup() throws Exception { authenticationService = applicationContext.getBean("authenticationService", MutableAuthenticationService.class); personService = applicationContext.getBean("personService", PersonService.class); user1 = createUser("user1" + System.currentTimeMillis(), "user1Password"); user2 = createUser("user2" + System.currentTimeMillis(), "user2Password"); users.add(user1); users.add(user2); AuthenticationUtil.clearCurrentSecurityContext(); } @After public void tearDown() throws Exception { AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); for (final String user : users) { transactionHelper.doInTransaction(new RetryingTransactionCallback() { @Override public Void execute() throws Throwable { if (personService.personExists(user)) { authenticationService.deleteAuthentication(user); personService.deletePerson(user); } return null; } }); } users.clear(); AuthenticationUtil.clearCurrentSecurityContext(); } /** * Tests login (create ticket), logout (delete ticket), and validate (get ticket). * *

POST:

* {@literal :/alfresco/api//public/alfresco/versions/1/tickets} * *

GET:

* {@literal :/alfresco/api//public/alfresco/versions/1/tickets/-me-} * *

DELETE:

* {@literal :/alfresco/api//public/alfresco/versions/1/tickets/-me-} */ @Test public void testCreateValidateDeleteTicket() throws Exception { Paging paging = getPaging(0, 100); // Unauthorized call getAll(SiteEntityResource.class, null, paging, null, 401); /* * user1 login - via alf_ticket parameter */ // User1 login request LoginTicket loginRequest = new LoginTicket(); // Invalid login details post("tickets", null, RestApiUtil.toJsonAsString(loginRequest), 400); loginRequest.setUserId(null); loginRequest.setPassword("user1Password"); // Invalid login details post("tickets", null, RestApiUtil.toJsonAsString(loginRequest), 400); loginRequest.setUserId(user1); loginRequest.setPassword(null); // Invalid login details post("tickets", null, RestApiUtil.toJsonAsString(loginRequest), 400); loginRequest.setUserId(user1); loginRequest.setPassword("user1Password"); // Authenticate and create a ticket HttpResponse response = post("tickets", null, RestApiUtil.toJsonAsString(loginRequest), 201); LoginTicketResponse loginResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), LoginTicketResponse.class); assertNotNull(loginResponse.getId()); assertNotNull(loginResponse.getUserId()); // Get list of sites by appending the alf_ticket to the URL // e.g. .../alfresco/versions/1/sites/?alf_ticket=TICKET_57866258ea56c28491bb3e75d8355ebf6fbaaa23 Map ticket = Collections.singletonMap("alf_ticket", loginResponse.getId()); getAll(SiteEntityResource.class, null, paging, ticket, 200); // Unauthorized - Invalid ticket getAll(SiteEntityResource.class, null, paging, Collections.singletonMap("alf_ticket", "TICKET_" + System.currentTimeMillis()), 401); // Validate ticket - Invalid parameter. Only '-me-' is supported getSingle("tickets", null, loginResponse.getId(), ticket, 400); // Validate ticket response = getSingle("tickets", null, People.DEFAULT_USER, ticket, 200); LoginTicketResponse validatedTicket = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), LoginTicketResponse.class); assertEquals(loginResponse.getId(), validatedTicket.getId()); // Validate ticket - Invalid parameter. Only '-me-' is supported getSingle("tickets", null, loginResponse.getId(), ticket, 400); // Delete the ticket - Logout delete("tickets", null, People.DEFAULT_USER, ticket, 204); // Validate ticket - 401 as ticket has been invalidated so the API call is unauthorized getSingle("tickets", null, People.DEFAULT_USER, ticket, 401); // Check the ticket has been invalidated - the difference with the above is that the API call is authorized getSingle("tickets", user1, People.DEFAULT_USER, ticket, 404); // Ticket has already been invalidated delete("tickets", user1, People.DEFAULT_USER, ticket, 404); // Get list of site by appending the invalidated ticket getAll(SiteEntityResource.class, null, paging, ticket, 401); /* * user2 login - Via Authorization header */ // User2 create a folder within his home folder (-my-) Folder folderResp = createFolder(user2, Nodes.PATH_MY, "F2", null); assertNotNull(folderResp.getId()); getAll(getNodeChildrenUrl(Nodes.PATH_MY), null, paging, 401); // User2 login request loginRequest = new LoginTicket(); loginRequest.setUserId(user2); loginRequest.setPassword("wrongPassword"); // Authentication failed - wrong password post("tickets", null, RestApiUtil.toJsonAsString(loginRequest), 403); loginRequest.setUserId(user1); loginRequest.setPassword("user2Password"); // Authentication failed - userId/password mismatch post("tickets", null, RestApiUtil.toJsonAsString(loginRequest), 403); // Set the correct details loginRequest.setUserId(user2); loginRequest.setPassword("user2Password"); // Authenticate and create a ticket response = post("tickets", null, RestApiUtil.toJsonAsString(loginRequest), 201); loginResponse = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), LoginTicketResponse.class); assertNotNull(loginResponse.getId()); assertNotNull(loginResponse.getUserId()); String encodedTicket = encodeB64(loginResponse.getId()); // Set the authorization (encoded ticket only) header rather than appending the ticket to the URL Map header = Collections.singletonMap("Authorization", "Basic " + encodedTicket); // Get children of user2 home folder response = getAll(getNodeChildrenUrl(Nodes.PATH_MY), null, paging, null, header, 200); List nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Document.class); assertEquals(1, nodes.size()); // Validate ticket - Invalid parameter. Only '-me-' is supported getSingle("tickets", null, loginResponse.getId(), null, header, 400); // Validate ticket - user2 response = getSingle("tickets", null, People.DEFAULT_USER, null, header, 200); validatedTicket = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), LoginTicketResponse.class); assertEquals(loginResponse.getId(), validatedTicket.getId()); // Try list children for user2 again. // Encode Alfresco predefined userId for ticket authentication, ROLE_TICKET, and the ticket String encodedUserIdAndTicket = encodeB64("ROLE_TICKET:" + loginResponse.getId()); // Set the authorization (encoded userId:ticket) header rather than appending the ticket to the URL header = Collections.singletonMap("Authorization", "Basic " + encodedUserIdAndTicket); // Get children of user2 home folder response = getAll(getNodeChildrenUrl(Nodes.PATH_MY), null, paging, null, header, 200); nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Document.class); assertEquals(1, nodes.size()); // Try list children for user2 again - appending ticket ticket = Collections.singletonMap("alf_ticket", loginResponse.getId()); response = getAll(getNodeChildrenUrl(Nodes.PATH_MY), null, paging, ticket, 200); nodes = RestApiUtil.parseRestApiEntries(response.getJsonResponse(), Document.class); assertEquals(1, nodes.size()); // Try to validate the ticket without supplying the Authorization header or the alf_ticket param getSingle("tickets", user2, People.DEFAULT_USER, null, null, 400); // Delete the ticket - Invalid parameter. Only '-me-' is supported header = Collections.singletonMap("Authorization", "Basic " + encodedUserIdAndTicket); delete("tickets", null, loginResponse.getId(), null, header, 400); // Delete the ticket - Logout delete("tickets", null, People.DEFAULT_USER, null, header, 204); // Get children of user2 home folder - invalidated ticket getAll(getNodeChildrenUrl(Nodes.PATH_MY), null, paging, null, header, 401); } private String encodeB64(String str) { return Base64.encodeBase64String(str.getBytes()); } @Override public String getScope() { return "public"; } }