diff --git a/pom.xml b/pom.xml index 21f666ea03..7062bd24de 100644 --- a/pom.xml +++ b/pom.xml @@ -36,7 +36,7 @@ ${project.build.directory}/alf_data convert - 6.4 + 6.6 1.0 6.13 diff --git a/src/main/java/org/alfresco/rest/api/PublicApiAuthenticatorFactory.java b/src/main/java/org/alfresco/rest/api/PublicApiAuthenticatorFactory.java index 0b18efa24a..2eef57809f 100644 --- a/src/main/java/org/alfresco/rest/api/PublicApiAuthenticatorFactory.java +++ b/src/main/java/org/alfresco/rest/api/PublicApiAuthenticatorFactory.java @@ -1,28 +1,28 @@ -/* - * #%L - * Alfresco Remote API - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * 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 . - * #L% - */ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ package org.alfresco.rest.api; import java.util.Collections; @@ -67,6 +67,7 @@ public class PublicApiAuthenticatorFactory extends RemoteUserAuthenticatorFactor private TenantAuthentication tenantAuthentication; private Set validAuthenticatorKeys = Collections.emptySet(); private Set outboundHeaderNames; + private boolean useBasicAuth = true; public void setAuthenticatorKeyHeader(String authenticatorKeyHeader) { @@ -92,6 +93,23 @@ public class PublicApiAuthenticatorFactory extends RemoteUserAuthenticatorFactor this.outboundHeaderNames = outboundHeaders; } + /** + * Whether to suggest that users use Basic auth. If set to true, then a + * 401 (unauthorized) response will contain a WWW-Authentication header + * specifying the scheme "Basic". If this is set to false, then + * the scheme "AlfTicket" will be used. + *

+ * Set this to false to avoid getting Basic auth dialogue popups in browsers + * when using the public API directly, for example. + * + * @see REPO-2575 + * @param useBasicAuth + */ + public void setUseBasicAuth(boolean useBasicAuth) + { + this.useBasicAuth = useBasicAuth; + } + public void setTenantAuthentication(TenantAuthentication service) { this.tenantAuthentication = service; @@ -232,7 +250,9 @@ public class PublicApiAuthenticatorFactory extends RemoteUserAuthenticatorFactor if (!authorized) { servletRes.setStatus(401); - servletRes.setHeader("WWW-Authenticate", "Basic realm=\"Alfresco " + servletReq.getTenant() + " tenant\""); + String scheme = useBasicAuth ? "Basic" : "AlfTicket"; + String challenge = scheme + " realm=\"Alfresco " + servletReq.getTenant() + " tenant\""; + servletRes.setHeader("WWW-Authenticate", challenge); } } } diff --git a/src/main/resources/alfresco/project-remote-api.properties b/src/main/resources/alfresco/project-remote-api.properties new file mode 100644 index 0000000000..90aa30f32e --- /dev/null +++ b/src/main/resources/alfresco/project-remote-api.properties @@ -0,0 +1,24 @@ +################################################################################ +# Remote API property defaults +# 9th October 2017 +################################################################################ + + +# Whether to send a "basic auth" challenge along with a 401 response (not authorized) +# +# If set to true, then a header will be sent similar to: +# +# WWW-Authenticate: Basic realm="..." +# +# If set to false, then a header will be sent with an AlfTicket challenge: +# +# WWW-Authenticate: AlfTicket realm="..." +# +# This latter case is particularly useful when building a web-browser based client +# that communicates directly with the Alfresco Public API - using the AlfTicket +# challenge allows the client to completely control the login behaviour, whereas +# allowing a Basic auth challenge to be sent results in the Basic Authentication +# browser dialogue being popped-up without the client app being involved. +# +# See issue REPO-2575 for details. +alfresco.restApi.basicAuthScheme=true diff --git a/src/main/resources/alfresco/public-rest-context.xml b/src/main/resources/alfresco/public-rest-context.xml index 997a45b348..641894a2a1 100644 --- a/src/main/resources/alfresco/public-rest-context.xml +++ b/src/main/resources/alfresco/public-rest-context.xml @@ -95,6 +95,7 @@ + diff --git a/src/test/java/org/alfresco/rest/api/tests/AuthenticationsTest.java b/src/test/java/org/alfresco/rest/api/tests/AuthenticationsTest.java index 61ce4829d0..5b50d7ea1f 100644 --- a/src/test/java/org/alfresco/rest/api/tests/AuthenticationsTest.java +++ b/src/test/java/org/alfresco/rest/api/tests/AuthenticationsTest.java @@ -31,6 +31,7 @@ import static org.junit.Assert.assertNotNull; import org.alfresco.rest.AbstractSingleNetworkSiteTest; import org.alfresco.rest.api.Nodes; import org.alfresco.rest.api.People; +import org.alfresco.rest.api.PublicApiAuthenticatorFactory; import org.alfresco.rest.api.model.LoginTicket; import org.alfresco.rest.api.model.LoginTicketResponse; import org.alfresco.rest.api.sites.SiteEntityResource; @@ -41,6 +42,7 @@ 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.apache.commons.codec.binary.Base64; +import org.junit.Before; import org.junit.Test; import java.util.Collections; @@ -56,7 +58,43 @@ public class AuthenticationsTest extends AbstractSingleNetworkSiteTest { private static final String TICKETS_URL = "tickets"; private static final String TICKETS_API_NAME = "authentication"; + private PublicApiAuthenticatorFactory authFactory; + @Before + public void setUpAuthTest() + { + authFactory = (PublicApiAuthenticatorFactory) applicationContext.getBean("publicapi.authenticator"); + } + + @Test + public void canDisableBasicAuthChallenge() throws Exception + { + authFactory.setUseBasicAuth(false); + + // Expect to be challenged for an AlfTicket (REPO-2575) + testAuthChallenge("AlfTicket"); + } + + @Test + public void canEnableBasicAuthChallenge() throws Exception + { + authFactory.setUseBasicAuth(true); + + // Expect to be challenged for Basic auth. + testAuthChallenge("Basic"); + } + + private void testAuthChallenge(String expectedScheme) throws Exception + { + // Unauthorized call + setRequestContext(null); + + HttpResponse response = getAll(SiteEntityResource.class, getPaging(0, 100), null, 401); + String authenticateHeader = response.getHeaders().get("WWW-Authenticate"); + assertNotNull("Expected an authentication challenge", authenticateHeader); + String authScheme = authenticateHeader.split(" ")[0]; // Other parts may contain, e.g. realm="..." + assertEquals(expectedScheme, authScheme); + } /** * Tests login (create ticket), logout (delete ticket), and validate (get ticket).