mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
[MNT-24859] Basic Auth still possible with Keycloak enabled (#3361)
Signed-off-by: cezary-witkowski <cezary.witkowski@hyland.com> Co-authored-by: Sathish Kumar <ST28@ford.com> Co-authored-by: pmm <purusothaman.mm@hyland.com> Co-authored-by: purusothaman-mm <purusothman.mm@hyland.com>
This commit is contained in:
@@ -1273,7 +1273,7 @@
|
|||||||
"filename": "repository/src/main/resources/alfresco/repository.properties",
|
"filename": "repository/src/main/resources/alfresco/repository.properties",
|
||||||
"hashed_secret": "84551ae5442affc9f1a2d3b4c86ae8b24860149d",
|
"hashed_secret": "84551ae5442affc9f1a2d3b4c86ae8b24860149d",
|
||||||
"is_verified": false,
|
"is_verified": false,
|
||||||
"line_number": 770,
|
"line_number": 771,
|
||||||
"is_secret": false
|
"is_secret": false
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -1868,5 +1868,5 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"generated_at": "2025-03-27T23:45:41Z"
|
"generated_at": "2025-04-22T06:32:47Z"
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* #%L
|
* #%L
|
||||||
* Alfresco Remote API
|
* Alfresco Remote API
|
||||||
* %%
|
* %%
|
||||||
* Copyright (C) 2005 - 2023 Alfresco Software Limited
|
* Copyright (C) 2005 - 2025 Alfresco Software Limited
|
||||||
* %%
|
* %%
|
||||||
* This file is part of the Alfresco software.
|
* This file is part of the Alfresco software.
|
||||||
* If the software was purchased under a paid Alfresco license, the terms of
|
* If the software was purchased under a paid Alfresco license, the terms of
|
||||||
@@ -46,7 +46,7 @@ import org.alfresco.repo.management.subsystems.ActivateableBean;
|
|||||||
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
import org.alfresco.repo.security.authentication.AuthenticationComponent;
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
import org.alfresco.repo.security.authentication.AuthenticationUtil;
|
||||||
import org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator;
|
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
|
||||||
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||||
import org.alfresco.repo.web.auth.AuthenticationListener;
|
import org.alfresco.repo.web.auth.AuthenticationListener;
|
||||||
import org.alfresco.repo.web.auth.TicketCredentials;
|
import org.alfresco.repo.web.auth.TicketCredentials;
|
||||||
@@ -71,9 +71,11 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
|||||||
|
|
||||||
protected RemoteUserMapper remoteUserMapper;
|
protected RemoteUserMapper remoteUserMapper;
|
||||||
protected AuthenticationComponent authenticationComponent;
|
protected AuthenticationComponent authenticationComponent;
|
||||||
protected AdminConsoleAuthenticator adminConsoleAuthenticator;
|
protected ExternalUserAuthenticator adminConsoleAuthenticator;
|
||||||
|
protected ExternalUserAuthenticator webScriptsHomeAuthenticator;
|
||||||
|
|
||||||
private boolean alwaysAllowBasicAuthForAdminConsole = true;
|
private boolean alwaysAllowBasicAuthForAdminConsole = true;
|
||||||
|
private boolean alwaysAllowBasicAuthForWebScriptsHome = true;
|
||||||
List<String> adminConsoleScriptFamilies;
|
List<String> adminConsoleScriptFamilies;
|
||||||
long getRemoteUserTimeoutMilliseconds = GET_REMOTE_USER_TIMEOUT_MILLISECONDS_DEFAULT;
|
long getRemoteUserTimeoutMilliseconds = GET_REMOTE_USER_TIMEOUT_MILLISECONDS_DEFAULT;
|
||||||
|
|
||||||
@@ -97,6 +99,16 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
|||||||
this.alwaysAllowBasicAuthForAdminConsole = alwaysAllowBasicAuthForAdminConsole;
|
this.alwaysAllowBasicAuthForAdminConsole = alwaysAllowBasicAuthForAdminConsole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAlwaysAllowBasicAuthForWebScriptsHome()
|
||||||
|
{
|
||||||
|
return alwaysAllowBasicAuthForWebScriptsHome;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlwaysAllowBasicAuthForWebScriptsHome(boolean alwaysAllowBasicAuthForWebScriptsHome)
|
||||||
|
{
|
||||||
|
this.alwaysAllowBasicAuthForWebScriptsHome = alwaysAllowBasicAuthForWebScriptsHome;
|
||||||
|
}
|
||||||
|
|
||||||
public List<String> getAdminConsoleScriptFamilies()
|
public List<String> getAdminConsoleScriptFamilies()
|
||||||
{
|
{
|
||||||
return adminConsoleScriptFamilies;
|
return adminConsoleScriptFamilies;
|
||||||
@@ -118,11 +130,17 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setAdminConsoleAuthenticator(
|
public void setAdminConsoleAuthenticator(
|
||||||
AdminConsoleAuthenticator adminConsoleAuthenticator)
|
ExternalUserAuthenticator adminConsoleAuthenticator)
|
||||||
{
|
{
|
||||||
this.adminConsoleAuthenticator = adminConsoleAuthenticator;
|
this.adminConsoleAuthenticator = adminConsoleAuthenticator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setWebScriptsHomeAuthenticator(
|
||||||
|
ExternalUserAuthenticator webScriptsHomeAuthenticator)
|
||||||
|
{
|
||||||
|
this.webScriptsHomeAuthenticator = webScriptsHomeAuthenticator;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Authenticator create(WebScriptServletRequest req, WebScriptServletResponse res)
|
public Authenticator create(WebScriptServletRequest req, WebScriptServletResponse res)
|
||||||
{
|
{
|
||||||
@@ -136,6 +154,8 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
|||||||
*/
|
*/
|
||||||
public class RemoteUserAuthenticator extends BasicHttpAuthenticator
|
public class RemoteUserAuthenticator extends BasicHttpAuthenticator
|
||||||
{
|
{
|
||||||
|
private static final String WEB_SCRIPTS_BASE_PATH = "org/springframework/extensions/webscripts";
|
||||||
|
|
||||||
public RemoteUserAuthenticator(WebScriptServletRequest req, WebScriptServletResponse res, AuthenticationListener listener)
|
public RemoteUserAuthenticator(WebScriptServletRequest req, WebScriptServletResponse res, AuthenticationListener listener)
|
||||||
{
|
{
|
||||||
super(req, res, listener);
|
super(req, res, listener);
|
||||||
@@ -156,24 +176,47 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
|||||||
{
|
{
|
||||||
|
|
||||||
if (servletReq.getServiceMatch() != null &&
|
if (servletReq.getServiceMatch() != null &&
|
||||||
isAdminConsoleWebScript(servletReq.getServiceMatch().getWebScript()) && isAdminConsoleAuthenticatorActive())
|
isAdminConsole(servletReq.getServiceMatch().getWebScript()) && isAdminConsoleAuthenticatorActive())
|
||||||
{
|
{
|
||||||
userId = getAdminConsoleUser();
|
userId = getAdminConsoleUser();
|
||||||
}
|
}
|
||||||
|
else if (servletReq.getServiceMatch() != null &&
|
||||||
|
isWebScriptsHome(servletReq.getServiceMatch().getWebScript()) && isWebScriptsHomeAuthenticatorActive())
|
||||||
|
{
|
||||||
|
userId = getWebScriptsHomeUser();
|
||||||
|
}
|
||||||
|
|
||||||
if (userId == null)
|
if (userId == null)
|
||||||
{
|
{
|
||||||
if (isAlwaysAllowBasicAuthForAdminConsole())
|
if (isAlwaysAllowBasicAuthForAdminConsole())
|
||||||
{
|
{
|
||||||
final boolean useTimeoutForAdminAccessingAdminConsole = shouldUseTimeoutForAdminAccessingAdminConsole(required, isGuest);
|
boolean shouldUseTimeout = shouldUseTimeoutForAdminAccessingAdminConsole(required, isGuest);
|
||||||
|
|
||||||
if (useTimeoutForAdminAccessingAdminConsole && isBasicAuthHeaderPresentForAdmin())
|
if (shouldUseTimeout && isBasicAuthHeaderPresentForAdmin())
|
||||||
{
|
{
|
||||||
return callBasicAuthForAdminConsoleAccess(required, isGuest);
|
return callBasicAuthForAdminConsoleOrWebScriptsHomeAccess(required, isGuest);
|
||||||
}
|
}
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
userId = getRemoteUserWithTimeout(useTimeoutForAdminAccessingAdminConsole);
|
userId = getRemoteUserWithTimeout(shouldUseTimeout);
|
||||||
|
}
|
||||||
|
catch (AuthenticationTimeoutException e)
|
||||||
|
{
|
||||||
|
// return basic auth challenge
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (isAlwaysAllowBasicAuthForWebScriptsHome())
|
||||||
|
{
|
||||||
|
boolean shouldUseTimeout = shouldUseTimeoutForAdminAccessingWebScriptsHome(required, isGuest);
|
||||||
|
|
||||||
|
if (shouldUseTimeout && isBasicAuthHeaderPresentForAdmin())
|
||||||
|
{
|
||||||
|
return callBasicAuthForAdminConsoleOrWebScriptsHomeAccess(required, isGuest);
|
||||||
|
}
|
||||||
|
try
|
||||||
|
{
|
||||||
|
userId = getRemoteUserWithTimeout(shouldUseTimeout);
|
||||||
}
|
}
|
||||||
catch (AuthenticationTimeoutException e)
|
catch (AuthenticationTimeoutException e)
|
||||||
{
|
{
|
||||||
@@ -252,38 +295,63 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
|||||||
authenticated = super.authenticate(required, isGuest);
|
authenticated = super.authenticate(required, isGuest);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!authenticated && servletReq.getServiceMatch() != null &&
|
if (!authenticated && servletReq.getServiceMatch() != null)
|
||||||
isAdminConsoleWebScript(servletReq.getServiceMatch().getWebScript()) && isAdminConsoleAuthenticatorActive())
|
|
||||||
{
|
{
|
||||||
adminConsoleAuthenticator.requestAuthentication(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
|
WebScript webScript = servletReq.getServiceMatch().getWebScript();
|
||||||
|
|
||||||
|
if (isAdminConsole(webScript) && isAdminConsoleAuthenticatorActive())
|
||||||
|
{
|
||||||
|
adminConsoleAuthenticator.requestAuthentication(
|
||||||
|
this.servletReq.getHttpServletRequest(),
|
||||||
|
this.servletRes.getHttpServletResponse());
|
||||||
|
}
|
||||||
|
else if (isWebScriptsHome(webScript)
|
||||||
|
&& isWebScriptsHomeAuthenticatorActive())
|
||||||
|
{
|
||||||
|
webScriptsHomeAuthenticator.requestAuthentication(
|
||||||
|
this.servletReq.getHttpServletRequest(),
|
||||||
|
this.servletRes.getHttpServletResponse());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return authenticated;
|
return authenticated;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean callBasicAuthForAdminConsoleAccess(RequiredAuthentication required, boolean isGuest)
|
private boolean callBasicAuthForAdminConsoleOrWebScriptsHomeAccess(RequiredAuthentication required, boolean isGuest)
|
||||||
{
|
{
|
||||||
// return REST call, after a timeout/basic auth challenge
|
// return REST call, after a timeout/basic auth challenge
|
||||||
if (LOGGER.isTraceEnabled())
|
if (LOGGER.isTraceEnabled())
|
||||||
{
|
{
|
||||||
LOGGER.trace("An Admin Console request has come in with Basic Auth headers present for an admin user.");
|
LOGGER.trace("An Admin Console or WebScripts Home request has come in with Basic Auth headers present for an admin user.");
|
||||||
}
|
}
|
||||||
// In order to prompt for another password, in case it was not entered correctly,
|
// In order to prompt for another password, in case it was not entered correctly,
|
||||||
// the output of this method should be returned by the calling "authenticate" method;
|
// the output of this method should be returned by the calling "authenticate" method;
|
||||||
// This would also mean, that once the admin basic auth header is present,
|
// This would also mean, that once the admin basic auth header is present,
|
||||||
// the authentication chain will not be used for the admin console access
|
// the authentication chain will not be used for access
|
||||||
return super.authenticate(required, isGuest);
|
return super.authenticate(required, isGuest);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean shouldUseTimeoutForAdminAccessingAdminConsole(RequiredAuthentication required, boolean isGuest)
|
private boolean shouldUseTimeoutForAdminAccessingAdminConsole(RequiredAuthentication required, boolean isGuest)
|
||||||
{
|
{
|
||||||
boolean useTimeoutForAdminAccessingAdminConsole = RequiredAuthentication.admin.equals(required) && !isGuest &&
|
boolean adminConsoleTimeout = RequiredAuthentication.admin.equals(required) && !isGuest &&
|
||||||
servletReq.getServiceMatch() != null && isAdminConsoleWebScript(servletReq.getServiceMatch().getWebScript());
|
servletReq.getServiceMatch() != null && isAdminConsole(servletReq.getServiceMatch().getWebScript());
|
||||||
|
|
||||||
if (LOGGER.isTraceEnabled())
|
if (LOGGER.isTraceEnabled())
|
||||||
{
|
{
|
||||||
LOGGER.trace("Should ensure that the admins can login with basic auth: " + useTimeoutForAdminAccessingAdminConsole);
|
LOGGER.trace("Should ensure that the admins can login with basic auth: " + adminConsoleTimeout);
|
||||||
}
|
}
|
||||||
return useTimeoutForAdminAccessingAdminConsole;
|
return adminConsoleTimeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean shouldUseTimeoutForAdminAccessingWebScriptsHome(RequiredAuthentication required, boolean isGuest)
|
||||||
|
{
|
||||||
|
boolean adminWebScriptsHomeTimeout = RequiredAuthentication.admin.equals(required) && !isGuest &&
|
||||||
|
servletReq.getServiceMatch() != null && isWebScriptsHome(servletReq.getServiceMatch().getWebScript());
|
||||||
|
|
||||||
|
if (LOGGER.isTraceEnabled())
|
||||||
|
{
|
||||||
|
LOGGER.trace("Should ensure that the admins can login with basic auth: " + adminWebScriptsHomeTimeout);
|
||||||
|
}
|
||||||
|
return adminWebScriptsHomeTimeout;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isRemoteUserMapperActive()
|
private boolean isRemoteUserMapperActive()
|
||||||
@@ -296,7 +364,12 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
|||||||
return adminConsoleAuthenticator != null && (!(adminConsoleAuthenticator instanceof ActivateableBean) || ((ActivateableBean) adminConsoleAuthenticator).isActive());
|
return adminConsoleAuthenticator != null && (!(adminConsoleAuthenticator instanceof ActivateableBean) || ((ActivateableBean) adminConsoleAuthenticator).isActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isAdminConsoleWebScript(WebScript webScript)
|
private boolean isWebScriptsHomeAuthenticatorActive()
|
||||||
|
{
|
||||||
|
return webScriptsHomeAuthenticator != null && (!(webScriptsHomeAuthenticator instanceof ActivateableBean) || ((ActivateableBean) webScriptsHomeAuthenticator).isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isAdminConsole(WebScript webScript)
|
||||||
{
|
{
|
||||||
if (webScript == null || adminConsoleScriptFamilies == null || webScript.getDescription() == null
|
if (webScript == null || adminConsoleScriptFamilies == null || webScript.getDescription() == null
|
||||||
|| webScript.getDescription().getFamilys() == null)
|
|| webScript.getDescription().getFamilys() == null)
|
||||||
@@ -310,7 +383,7 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
|||||||
}
|
}
|
||||||
|
|
||||||
// intersect the "family" sets defined
|
// intersect the "family" sets defined
|
||||||
Set<String> families = new HashSet<String>(webScript.getDescription().getFamilys());
|
Set<String> families = new HashSet<>(webScript.getDescription().getFamilys());
|
||||||
families.retainAll(adminConsoleScriptFamilies);
|
families.retainAll(adminConsoleScriptFamilies);
|
||||||
final boolean isAdminConsole = !families.isEmpty();
|
final boolean isAdminConsole = !families.isEmpty();
|
||||||
|
|
||||||
@@ -322,6 +395,23 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
|||||||
return isAdminConsole;
|
return isAdminConsole;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected boolean isWebScriptsHome(WebScript webScript)
|
||||||
|
{
|
||||||
|
if (webScript == null || webScript.toString() == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isWebScriptsHome = webScript.toString().startsWith(WEB_SCRIPTS_BASE_PATH);
|
||||||
|
|
||||||
|
if (LOGGER.isTraceEnabled() && isWebScriptsHome)
|
||||||
|
{
|
||||||
|
LOGGER.trace("Detected a WebScripts Home webscript: " + webScript);
|
||||||
|
}
|
||||||
|
|
||||||
|
return isWebScriptsHome;
|
||||||
|
}
|
||||||
|
|
||||||
protected String getRemoteUserWithTimeout(boolean useTimeout) throws AuthenticationTimeoutException
|
protected String getRemoteUserWithTimeout(boolean useTimeout) throws AuthenticationTimeoutException
|
||||||
{
|
{
|
||||||
if (!useTimeout)
|
if (!useTimeout)
|
||||||
@@ -417,7 +507,21 @@ public class RemoteUserAuthenticatorFactory extends BasicHttpAuthenticatorFactor
|
|||||||
|
|
||||||
if (isRemoteUserMapperActive())
|
if (isRemoteUserMapperActive())
|
||||||
{
|
{
|
||||||
userId = adminConsoleAuthenticator.getAdminConsoleUser(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
|
userId = adminConsoleAuthenticator.getUserId(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
|
||||||
|
}
|
||||||
|
|
||||||
|
logRemoteUserID(userId);
|
||||||
|
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String getWebScriptsHomeUser()
|
||||||
|
{
|
||||||
|
String userId = null;
|
||||||
|
|
||||||
|
if (isRemoteUserMapperActive())
|
||||||
|
{
|
||||||
|
userId = webScriptsHomeAuthenticator.getUserId(this.servletReq.getHttpServletRequest(), this.servletRes.getHttpServletResponse());
|
||||||
}
|
}
|
||||||
|
|
||||||
logRemoteUserID(userId);
|
logRemoteUserID(userId);
|
||||||
|
@@ -214,9 +214,13 @@
|
|||||||
<property name="authenticationListener" ref="webScriptAuthenticationListener"/>
|
<property name="authenticationListener" ref="webScriptAuthenticationListener"/>
|
||||||
<property name="remoteUserMapper" ref="RemoteUserMapper" />
|
<property name="remoteUserMapper" ref="RemoteUserMapper" />
|
||||||
<property name="adminConsoleAuthenticator" ref="AdminConsoleAuthenticator" />
|
<property name="adminConsoleAuthenticator" ref="AdminConsoleAuthenticator" />
|
||||||
|
<property name="webScriptsHomeAuthenticator" ref="WebScriptsHomeAuthenticator" />
|
||||||
<property name="alwaysAllowBasicAuthForAdminConsole">
|
<property name="alwaysAllowBasicAuthForAdminConsole">
|
||||||
<value>${authentication.alwaysAllowBasicAuthForAdminConsole.enabled}</value>
|
<value>${authentication.alwaysAllowBasicAuthForAdminConsole.enabled}</value>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="alwaysAllowBasicAuthForWebScriptsHome">
|
||||||
|
<value>${authentication.alwaysAllowBasicAuthForWebScriptsHome.enabled}</value>
|
||||||
|
</property>
|
||||||
<property name="getRemoteUserTimeoutMilliseconds">
|
<property name="getRemoteUserTimeoutMilliseconds">
|
||||||
<value>${authentication.getRemoteUserTimeoutMilliseconds}</value>
|
<value>${authentication.getRemoteUserTimeoutMilliseconds}</value>
|
||||||
</property>
|
</property>
|
||||||
|
@@ -31,12 +31,12 @@ import jakarta.servlet.http.HttpServletResponse;
|
|||||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A default {@link AdminConsoleAuthenticator} implementation. Returns null to request a basic auth challenge.
|
* A default {@link ExternalUserAuthenticator} implementation. Returns null to request a basic auth challenge.
|
||||||
*/
|
*/
|
||||||
public class DefaultAdminConsoleAuthenticator implements AdminConsoleAuthenticator, ActivateableBean
|
public class DefaultAdminConsoleAuthenticator implements ExternalUserAuthenticator, ActivateableBean
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public String getAdminConsoleUser(HttpServletRequest request, HttpServletResponse response)
|
public String getUserId(HttpServletRequest request, HttpServletResponse response)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2025 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 <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.security.authentication.external;
|
||||||
|
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A default {@link ExternalUserAuthenticator} implementation. Returns null to request a basic auth challenge.
|
||||||
|
*/
|
||||||
|
public class DefaultWebScriptsHomeAuthenticator implements ExternalUserAuthenticator, ActivateableBean
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public String getUserId(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void requestAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
{
|
||||||
|
// No implementation
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -29,28 +29,17 @@ import jakarta.servlet.http.HttpServletRequest;
|
|||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface for objects capable of extracting an externally authenticated user ID from the HTTP Admin Console webscript request.
|
* An interface for objects capable of extracting an externally authenticated user ID from the HTTP request.
|
||||||
*/
|
*/
|
||||||
public interface AdminConsoleAuthenticator
|
public interface ExternalUserAuthenticator
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Gets an externally authenticated user ID from the HTTP Admin Console webscript request.
|
* Gets an externally authenticated user ID from the HTTP request.
|
||||||
*
|
*
|
||||||
* @param request
|
|
||||||
* the request
|
|
||||||
* @param response
|
|
||||||
* the response
|
|
||||||
* @return the user ID or <code>null</code> if the user is unauthenticated
|
* @return the user ID or <code>null</code> if the user is unauthenticated
|
||||||
*/
|
*/
|
||||||
String getAdminConsoleUser(HttpServletRequest request, HttpServletResponse response);
|
String getUserId(HttpServletRequest request, HttpServletResponse response);
|
||||||
|
|
||||||
/**
|
/* Sends redirect to external site to initiate the OIDC authorization code flow. */
|
||||||
* Requests an authentication.
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* the request
|
|
||||||
* @param response
|
|
||||||
* the response
|
|
||||||
*/
|
|
||||||
void requestAuthentication(HttpServletRequest request, HttpServletResponse response);
|
void requestAuthentication(HttpServletRequest request, HttpServletResponse response);
|
||||||
}
|
}
|
@@ -76,6 +76,18 @@ public class IdentityServiceConfig
|
|||||||
private String lastNameAttribute;
|
private String lastNameAttribute;
|
||||||
private String emailAttribute;
|
private String emailAttribute;
|
||||||
private long jwtClockSkewMs;
|
private long jwtClockSkewMs;
|
||||||
|
private String webScriptsHomeRedirectPath;
|
||||||
|
private String webScriptsHomeScopes;
|
||||||
|
|
||||||
|
public String getWebScriptsHomeRedirectPath()
|
||||||
|
{
|
||||||
|
return webScriptsHomeRedirectPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWebScriptsHomeRedirectPath(String webScriptsHomeRedirectPath)
|
||||||
|
{
|
||||||
|
this.webScriptsHomeRedirectPath = webScriptsHomeRedirectPath;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -359,6 +371,18 @@ public class IdentityServiceConfig
|
|||||||
this.adminConsoleScopes = adminConsoleScopes;
|
this.adminConsoleScopes = adminConsoleScopes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<String> getWebScriptsHomeScopes()
|
||||||
|
{
|
||||||
|
return Stream.of(webScriptsHomeScopes.split(","))
|
||||||
|
.map(String::trim)
|
||||||
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWebScriptsHomeScopes(String webScriptsHomeScopes)
|
||||||
|
{
|
||||||
|
this.webScriptsHomeScopes = webScriptsHomeScopes;
|
||||||
|
}
|
||||||
|
|
||||||
public Set<String> getPasswordGrantScopes()
|
public Set<String> getPasswordGrantScopes()
|
||||||
{
|
{
|
||||||
return Stream.of(passwordGrantScopes.split(","))
|
return Stream.of(passwordGrantScopes.split(","))
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.repo.security.authentication.identityservice.admin;
|
package org.alfresco.repo.security.authentication.identityservice.authentication;
|
||||||
|
|
||||||
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant.authorizationCode;
|
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant.authorizationCode;
|
||||||
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceMetadataKey.SCOPES_SUPPORTED;
|
import static org.alfresco.repo.security.authentication.identityservice.IdentityServiceMetadataKey.SCOPES_SUPPORTED;
|
||||||
@@ -32,7 +32,6 @@ import java.io.IOException;
|
|||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -50,9 +49,8 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
|
|||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
|
||||||
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
|
||||||
import org.alfresco.repo.security.authentication.AuthenticationException;
|
import org.alfresco.repo.security.authentication.AuthenticationException;
|
||||||
import org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator;
|
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
|
||||||
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
|
||||||
@@ -60,27 +58,26 @@ import org.alfresco.repo.security.authentication.identityservice.IdentityService
|
|||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
||||||
|
|
||||||
/**
|
public abstract class AbstractIdentityServiceAuthenticator implements ExternalUserAuthenticator
|
||||||
* An {@link AdminConsoleAuthenticator} implementation to extract an externally authenticated user ID or to initiate the OIDC authorization code flow.
|
|
||||||
*/
|
|
||||||
public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAuthenticator, ActivateableBean
|
|
||||||
{
|
{
|
||||||
private static final Logger LOGGER = LoggerFactory.getLogger(IdentityServiceAdminConsoleAuthenticator.class);
|
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractIdentityServiceAuthenticator.class);
|
||||||
|
|
||||||
private static final String ALFRESCO_ACCESS_TOKEN = "ALFRESCO_ACCESS_TOKEN";
|
private static final String ALFRESCO_ACCESS_TOKEN = "ALFRESCO_ACCESS_TOKEN";
|
||||||
private static final String ALFRESCO_REFRESH_TOKEN = "ALFRESCO_REFRESH_TOKEN";
|
private static final String ALFRESCO_REFRESH_TOKEN = "ALFRESCO_REFRESH_TOKEN";
|
||||||
private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION";
|
private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION";
|
||||||
|
|
||||||
private IdentityServiceConfig identityServiceConfig;
|
protected IdentityServiceConfig identityServiceConfig;
|
||||||
private IdentityServiceFacade identityServiceFacade;
|
protected IdentityServiceFacade identityServiceFacade;
|
||||||
private AdminConsoleAuthenticationCookiesService cookiesService;
|
protected AdminAuthenticationCookiesService cookiesService;
|
||||||
private RemoteUserMapper remoteUserMapper;
|
protected RemoteUserMapper remoteUserMapper;
|
||||||
private boolean isEnabled;
|
|
||||||
|
protected abstract String getConfiguredRedirectPath();
|
||||||
|
|
||||||
|
protected abstract Set<String> getConfiguredScopes();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getAdminConsoleUser(HttpServletRequest request, HttpServletResponse response)
|
public String getUserId(HttpServletRequest request, HttpServletResponse response)
|
||||||
{
|
{
|
||||||
// Try to extract username from the authorization header
|
|
||||||
String username = remoteUserMapper.getRemoteUser(request);
|
String username = remoteUserMapper.getRemoteUser(request);
|
||||||
if (username != null)
|
if (username != null)
|
||||||
{
|
{
|
||||||
@@ -107,16 +104,12 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return remoteUserMapper.getRemoteUser(decorateBearerHeader(bearerToken, request));
|
HttpServletRequest wrappedRequest = newRequestWrapper(Map.of("Authorization", "Bearer " + bearerToken), request);
|
||||||
|
return remoteUserMapper.getRemoteUser(wrappedRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void requestAuthentication(HttpServletRequest request, HttpServletResponse response)
|
public void requestAuthentication(HttpServletRequest request, HttpServletResponse response)
|
||||||
{
|
|
||||||
respondWithAuthChallenge(request, response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void respondWithAuthChallenge(HttpServletRequest request, HttpServletResponse response)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -124,7 +117,8 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
|||||||
{
|
{
|
||||||
LOGGER.debug("Responding with the authentication challenge");
|
LOGGER.debug("Responding with the authentication challenge");
|
||||||
}
|
}
|
||||||
response.sendRedirect(getAuthenticationRequest(request));
|
String authenticationRequest = buildAuthRequestUrl(request);
|
||||||
|
response.sendRedirect(authenticationRequest);
|
||||||
}
|
}
|
||||||
catch (IOException e)
|
catch (IOException e)
|
||||||
{
|
{
|
||||||
@@ -133,84 +127,34 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String retrieveTokenUsingAuthCode(HttpServletRequest request, HttpServletResponse response, String code)
|
protected String getRedirectUri(String requestURL)
|
||||||
{
|
{
|
||||||
String bearerToken = null;
|
return buildRedirectUri(requestURL, getConfiguredRedirectPath());
|
||||||
if (LOGGER.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOGGER.debug("Retrieving a response using the Authorization Code at the Token Endpoint");
|
|
||||||
}
|
|
||||||
try
|
|
||||||
{
|
|
||||||
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
|
|
||||||
authorizationCode(code, request.getRequestURL().toString()));
|
|
||||||
addCookies(response, accessTokenAuthorization);
|
|
||||||
bearerToken = accessTokenAuthorization.getAccessToken().getTokenValue();
|
|
||||||
}
|
|
||||||
catch (AuthorizationException exception)
|
|
||||||
{
|
|
||||||
if (LOGGER.isWarnEnabled())
|
|
||||||
{
|
|
||||||
LOGGER.warn(
|
|
||||||
"Error while trying to retrieve a response using the Authorization Code at the Token Endpoint: {}",
|
|
||||||
exception.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bearerToken;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String refreshTokenIfNeeded(HttpServletRequest request, HttpServletResponse response, String bearerToken)
|
public String buildAuthRequestUrl(HttpServletRequest request)
|
||||||
{
|
|
||||||
String refreshToken = cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request);
|
|
||||||
String authTokenExpiration = cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (isAuthTokenExpired(authTokenExpiration))
|
|
||||||
{
|
|
||||||
bearerToken = refreshAuthToken(refreshToken, response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
if (LOGGER.isDebugEnabled())
|
|
||||||
{
|
|
||||||
LOGGER.debug("Error while trying to refresh Auth Token: {}", e.getMessage());
|
|
||||||
}
|
|
||||||
bearerToken = null;
|
|
||||||
resetCookies(response);
|
|
||||||
}
|
|
||||||
return bearerToken;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addCookies(HttpServletResponse response, AccessTokenAuthorization accessTokenAuthorization)
|
|
||||||
{
|
|
||||||
cookiesService.addCookie(ALFRESCO_ACCESS_TOKEN, accessTokenAuthorization.getAccessToken().getTokenValue(), response);
|
|
||||||
cookiesService.addCookie(ALFRESCO_TOKEN_EXPIRATION, String.valueOf(
|
|
||||||
accessTokenAuthorization.getAccessToken().getExpiresAt().toEpochMilli()), response);
|
|
||||||
cookiesService.addCookie(ALFRESCO_REFRESH_TOKEN, accessTokenAuthorization.getRefreshTokenValue(), response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getAuthenticationRequest(HttpServletRequest request)
|
|
||||||
{
|
{
|
||||||
ClientRegistration clientRegistration = identityServiceFacade.getClientRegistration();
|
ClientRegistration clientRegistration = identityServiceFacade.getClientRegistration();
|
||||||
State state = new State();
|
State state = new State();
|
||||||
|
|
||||||
UriComponentsBuilder authRequestBuilder = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getAuthorizationUri())
|
UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails()
|
||||||
|
.getAuthorizationUri())
|
||||||
.queryParam("client_id", clientRegistration.getClientId())
|
.queryParam("client_id", clientRegistration.getClientId())
|
||||||
.queryParam("redirect_uri", getRedirectUri(request.getRequestURL().toString()))
|
.queryParam("redirect_uri", getRedirectUri(request.getRequestURL().toString()))
|
||||||
.queryParam("response_type", "code")
|
.queryParam("response_type", "code")
|
||||||
.queryParam("scope", String.join("+", getScopes(clientRegistration)))
|
.queryParam("scope", String.join("+", getConfiguredScopes(clientRegistration)))
|
||||||
.queryParam("state", state.toString());
|
.queryParam("state", state.toString());
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(identityServiceConfig.getAudience()))
|
if (StringUtils.isNotBlank(identityServiceConfig.getAudience()))
|
||||||
{
|
{
|
||||||
authRequestBuilder.queryParam("audience", identityServiceConfig.getAudience());
|
builder.queryParam("audience", identityServiceConfig.getAudience());
|
||||||
}
|
}
|
||||||
|
|
||||||
return authRequestBuilder.build().toUriString();
|
return builder.build()
|
||||||
|
.toUriString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> getScopes(ClientRegistration clientRegistration)
|
private Set<String> getConfiguredScopes(ClientRegistration clientRegistration)
|
||||||
{
|
{
|
||||||
return Optional.ofNullable(clientRegistration.getProviderDetails())
|
return Optional.ofNullable(clientRegistration.getProviderDetails())
|
||||||
.map(ProviderDetails::getConfigurationMetadata)
|
.map(ProviderDetails::getConfigurationMetadata)
|
||||||
@@ -223,100 +167,149 @@ public class IdentityServiceAdminConsoleAuthenticator implements AdminConsoleAut
|
|||||||
|
|
||||||
private Set<String> getSupportedScopes(Scope scopes)
|
private Set<String> getSupportedScopes(Scope scopes)
|
||||||
{
|
{
|
||||||
|
Set<String> configuredScopes = getConfiguredScopes();
|
||||||
return scopes.stream()
|
return scopes.stream()
|
||||||
.filter(this::hasAdminConsoleScope)
|
|
||||||
.map(Identifier::getValue)
|
.map(Identifier::getValue)
|
||||||
|
.filter(configuredScopes::contains)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasAdminConsoleScope(Scope.Value scope)
|
protected String buildRedirectUri(String requestURL, String overridePath)
|
||||||
{
|
|
||||||
return identityServiceConfig.getAdminConsoleScopes().contains(scope.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String getRedirectUri(String requestURL)
|
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
URI originalUri = new URI(requestURL);
|
URI originalUri = new URI(requestURL);
|
||||||
URI redirectUri = new URI(originalUri.getScheme(), originalUri.getAuthority(), identityServiceConfig.getAdminConsoleRedirectPath(), originalUri.getQuery(), originalUri.getFragment());
|
String path = overridePath != null ? overridePath : originalUri.getPath();
|
||||||
|
|
||||||
|
URI redirectUri = new URI(
|
||||||
|
originalUri.getScheme(),
|
||||||
|
originalUri.getAuthority(),
|
||||||
|
path,
|
||||||
|
originalUri.getQuery(),
|
||||||
|
originalUri.getFragment());
|
||||||
|
|
||||||
return redirectUri.toASCIIString();
|
return redirectUri.toASCIIString();
|
||||||
}
|
}
|
||||||
catch (URISyntaxException e)
|
catch (URISyntaxException e)
|
||||||
{
|
{
|
||||||
LOGGER.error("Error while trying to get the redirect URI and respond with the authentication challenge: {}", e.getMessage(), e);
|
LOGGER.error("Redirect URI construction failed: {}", e.getMessage(), e);
|
||||||
throw new AuthenticationException(e.getMessage(), e);
|
throw new AuthenticationException(e.getMessage(), e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetCookies(HttpServletResponse response)
|
public void challenge(HttpServletRequest request, HttpServletResponse response)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
response.sendRedirect(buildAuthRequestUrl(request));
|
||||||
|
}
|
||||||
|
catch (IOException e)
|
||||||
|
{
|
||||||
|
throw new AuthenticationException("Auth redirect failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String retrieveTokenUsingAuthCode(HttpServletRequest request, HttpServletResponse response, String code)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(authorizationCode(code, getRedirectUri(request.getRequestURL()
|
||||||
|
.toString())));
|
||||||
|
addCookies(response, accessTokenAuthorization);
|
||||||
|
return accessTokenAuthorization.getAccessToken()
|
||||||
|
.getTokenValue();
|
||||||
|
}
|
||||||
|
catch (AuthorizationException exception)
|
||||||
|
{
|
||||||
|
LOGGER.warn("Error while trying to retrieve token using Authorization Code: {}", exception.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String refreshTokenIfNeeded(HttpServletRequest request, HttpServletResponse response, String bearerToken)
|
||||||
|
{
|
||||||
|
String refreshToken = cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request);
|
||||||
|
String authTokenExpiration = cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (isAuthTokenExpired(authTokenExpiration))
|
||||||
|
{
|
||||||
|
bearerToken = refreshAuthToken(refreshToken, response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
if (LOGGER.isDebugEnabled())
|
||||||
|
{
|
||||||
|
LOGGER.debug("Token refresh failed: {}", e.getMessage());
|
||||||
|
}
|
||||||
|
bearerToken = null;
|
||||||
|
resetCookies(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bearerToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isAuthTokenExpired(String authTokenExpiration)
|
||||||
|
{
|
||||||
|
return authTokenExpiration == null || Instant.now()
|
||||||
|
.compareTo(Instant.ofEpochMilli(Long.parseLong(authTokenExpiration))) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String refreshAuthToken(String refreshToken, HttpServletResponse response)
|
||||||
|
{
|
||||||
|
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(AuthorizationGrant.refreshToken(refreshToken));
|
||||||
|
if (accessTokenAuthorization == null || accessTokenAuthorization.getAccessToken() == null)
|
||||||
|
{
|
||||||
|
throw new AuthenticationException("Refresh token response is invalid.");
|
||||||
|
}
|
||||||
|
addCookies(response, accessTokenAuthorization);
|
||||||
|
return accessTokenAuthorization.getAccessToken()
|
||||||
|
.getTokenValue();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addCookies(HttpServletResponse response, AccessTokenAuthorization accessTokenAuthorization)
|
||||||
|
{
|
||||||
|
cookiesService.addCookie(ALFRESCO_ACCESS_TOKEN, accessTokenAuthorization.getAccessToken()
|
||||||
|
.getTokenValue(), response);
|
||||||
|
cookiesService.addCookie(ALFRESCO_TOKEN_EXPIRATION, String.valueOf(accessTokenAuthorization.getAccessToken()
|
||||||
|
.getExpiresAt()
|
||||||
|
.toEpochMilli()), response);
|
||||||
|
cookiesService.addCookie(ALFRESCO_REFRESH_TOKEN, accessTokenAuthorization.getRefreshTokenValue(), response);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void resetCookies(HttpServletResponse response)
|
||||||
{
|
{
|
||||||
cookiesService.resetCookie(ALFRESCO_TOKEN_EXPIRATION, response);
|
cookiesService.resetCookie(ALFRESCO_TOKEN_EXPIRATION, response);
|
||||||
cookiesService.resetCookie(ALFRESCO_ACCESS_TOKEN, response);
|
cookiesService.resetCookie(ALFRESCO_ACCESS_TOKEN, response);
|
||||||
cookiesService.resetCookie(ALFRESCO_REFRESH_TOKEN, response);
|
cookiesService.resetCookie(ALFRESCO_REFRESH_TOKEN, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String refreshAuthToken(String refreshToken, HttpServletResponse response)
|
protected HttpServletRequest newRequestWrapper(Map<String, String> headers, HttpServletRequest request)
|
||||||
{
|
{
|
||||||
AccessTokenAuthorization accessTokenAuthorization = doRefreshAuthToken(refreshToken);
|
return new AdditionalHeadersHttpServletRequestWrapper(headers, request);
|
||||||
addCookies(response, accessTokenAuthorization);
|
|
||||||
return accessTokenAuthorization.getAccessToken().getTokenValue();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private AccessTokenAuthorization doRefreshAuthToken(String refreshToken)
|
// Setters
|
||||||
|
public void setIdentityServiceConfig(IdentityServiceConfig config)
|
||||||
{
|
{
|
||||||
AccessTokenAuthorization accessTokenAuthorization = identityServiceFacade.authorize(
|
this.identityServiceConfig = config;
|
||||||
AuthorizationGrant.refreshToken(refreshToken));
|
|
||||||
if (accessTokenAuthorization == null || accessTokenAuthorization.getAccessToken() == null)
|
|
||||||
{
|
|
||||||
throw new AuthenticationException("AccessTokenResponse is null or empty");
|
|
||||||
}
|
|
||||||
return accessTokenAuthorization;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isAuthTokenExpired(String authTokenExpiration)
|
public void setIdentityServiceFacade(IdentityServiceFacade facade)
|
||||||
{
|
{
|
||||||
return Instant.now().compareTo(Instant.ofEpochMilli(Long.parseLong(authTokenExpiration))) >= 0;
|
this.identityServiceFacade = facade;
|
||||||
}
|
}
|
||||||
|
|
||||||
private HttpServletRequest decorateBearerHeader(String authToken, HttpServletRequest servletRequest)
|
public void setCookiesService(AdminAuthenticationCookiesService service)
|
||||||
{
|
{
|
||||||
Map<String, String> additionalHeaders = new HashMap<>();
|
this.cookiesService = service;
|
||||||
additionalHeaders.put("Authorization", "Bearer " + authToken);
|
|
||||||
return new AdminConsoleHttpServletRequestWrapper(additionalHeaders, servletRequest);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIdentityServiceFacade(
|
public void setRemoteUserMapper(RemoteUserMapper mapper)
|
||||||
IdentityServiceFacade identityServiceFacade)
|
|
||||||
{
|
{
|
||||||
this.identityServiceFacade = identityServiceFacade;
|
this.remoteUserMapper = mapper;
|
||||||
}
|
|
||||||
|
|
||||||
public void setRemoteUserMapper(RemoteUserMapper remoteUserMapper)
|
|
||||||
{
|
|
||||||
this.remoteUserMapper = remoteUserMapper;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setCookiesService(
|
|
||||||
AdminConsoleAuthenticationCookiesService cookiesService)
|
|
||||||
{
|
|
||||||
this.cookiesService = cookiesService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setIdentityServiceConfig(
|
|
||||||
IdentityServiceConfig identityServiceConfig)
|
|
||||||
{
|
|
||||||
this.identityServiceConfig = identityServiceConfig;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isActive()
|
|
||||||
{
|
|
||||||
return this.isEnabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setActive(boolean isEnabled)
|
|
||||||
{
|
|
||||||
this.isEnabled = isEnabled;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -23,7 +23,7 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.repo.security.authentication.identityservice.admin;
|
package org.alfresco.repo.security.authentication.identityservice.authentication;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static java.util.Collections.enumeration;
|
import static java.util.Collections.enumeration;
|
||||||
@@ -37,20 +37,12 @@ import jakarta.servlet.http.HttpServletRequestWrapper;
|
|||||||
|
|
||||||
import org.alfresco.util.PropertyCheck;
|
import org.alfresco.util.PropertyCheck;
|
||||||
|
|
||||||
public class AdminConsoleHttpServletRequestWrapper extends HttpServletRequestWrapper
|
public class AdditionalHeadersHttpServletRequestWrapper extends HttpServletRequestWrapper
|
||||||
{
|
{
|
||||||
private final Map<String, String> additionalHeaders;
|
private final Map<String, String> additionalHeaders;
|
||||||
private final HttpServletRequest wrappedRequest;
|
private final HttpServletRequest wrappedRequest;
|
||||||
|
|
||||||
/**
|
public AdditionalHeadersHttpServletRequestWrapper(Map<String, String> additionalHeaders, HttpServletRequest request)
|
||||||
* Constructs a request object wrapping the given request.
|
|
||||||
*
|
|
||||||
* @param request
|
|
||||||
* the request to wrap
|
|
||||||
* @throws IllegalArgumentException
|
|
||||||
* if the request is null
|
|
||||||
*/
|
|
||||||
public AdminConsoleHttpServletRequestWrapper(Map<String, String> additionalHeaders, HttpServletRequest request)
|
|
||||||
{
|
{
|
||||||
super(request);
|
super(request);
|
||||||
PropertyCheck.mandatory(this, "additionalHeaders", additionalHeaders);
|
PropertyCheck.mandatory(this, "additionalHeaders", additionalHeaders);
|
@@ -23,7 +23,7 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.repo.security.authentication.identityservice.admin;
|
package org.alfresco.repo.security.authentication.identityservice.authentication;
|
||||||
|
|
||||||
import jakarta.servlet.http.Cookie;
|
import jakarta.servlet.http.Cookie;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
@@ -34,12 +34,12 @@ import org.alfresco.repo.admin.SysAdminParams;
|
|||||||
/**
|
/**
|
||||||
* Service to handle Admin Console authentication-related cookies.
|
* Service to handle Admin Console authentication-related cookies.
|
||||||
*/
|
*/
|
||||||
public class AdminConsoleAuthenticationCookiesService
|
public class AdminAuthenticationCookiesService
|
||||||
{
|
{
|
||||||
private final SysAdminParams sysAdminParams;
|
private final SysAdminParams sysAdminParams;
|
||||||
private final int cookieLifetime;
|
private final int cookieLifetime;
|
||||||
|
|
||||||
public AdminConsoleAuthenticationCookiesService(SysAdminParams sysAdminParams, int cookieLifetime)
|
public AdminAuthenticationCookiesService(SysAdminParams sysAdminParams, int cookieLifetime)
|
||||||
{
|
{
|
||||||
this.sysAdminParams = sysAdminParams;
|
this.sysAdminParams = sysAdminParams;
|
||||||
this.cookieLifetime = cookieLifetime;
|
this.cookieLifetime = cookieLifetime;
|
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2025 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 <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.security.authentication.identityservice.authentication.admin;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||||
|
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.authentication.AbstractIdentityServiceAuthenticator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link ExternalUserAuthenticator} implementation to extract an externally authenticated user ID or to initiate the OIDC authorization code flow.
|
||||||
|
*/
|
||||||
|
public class IdentityServiceAdminConsoleAuthenticator extends AbstractIdentityServiceAuthenticator
|
||||||
|
implements ExternalUserAuthenticator, ActivateableBean
|
||||||
|
{
|
||||||
|
private boolean isEnabled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Set<String> getConfiguredScopes()
|
||||||
|
{
|
||||||
|
return identityServiceConfig.getAdminConsoleScopes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getConfiguredRedirectPath()
|
||||||
|
{
|
||||||
|
return identityServiceConfig.getAdminConsoleRedirectPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive()
|
||||||
|
{
|
||||||
|
return isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(boolean isEnabled)
|
||||||
|
{
|
||||||
|
this.isEnabled = isEnabled;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2025 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 <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.security.authentication.identityservice.authentication.webscripts;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.alfresco.repo.management.subsystems.ActivateableBean;
|
||||||
|
import org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.authentication.AbstractIdentityServiceAuthenticator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link ExternalUserAuthenticator} implementation to extract an externally authenticated user ID or to initiate the OIDC authorization code flow.
|
||||||
|
*/
|
||||||
|
public class IdentityServiceWebScriptsHomeAuthenticator extends AbstractIdentityServiceAuthenticator
|
||||||
|
implements ExternalUserAuthenticator, ActivateableBean
|
||||||
|
{
|
||||||
|
private boolean isEnabled;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String getConfiguredRedirectPath()
|
||||||
|
{
|
||||||
|
return identityServiceConfig.getWebScriptsHomeRedirectPath();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Set<String> getConfiguredScopes()
|
||||||
|
{
|
||||||
|
return identityServiceConfig.getWebScriptsHomeScopes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive()
|
||||||
|
{
|
||||||
|
return this.isEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setActive(boolean isEnabled)
|
||||||
|
{
|
||||||
|
this.isEnabled = isEnabled;
|
||||||
|
}
|
||||||
|
}
|
@@ -135,7 +135,7 @@
|
|||||||
</property>
|
</property>
|
||||||
<property name="interfaces">
|
<property name="interfaces">
|
||||||
<list>
|
<list>
|
||||||
<value>org.alfresco.repo.security.authentication.external.AdminConsoleAuthenticator</value>
|
<value>org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator</value>
|
||||||
<value>org.alfresco.repo.management.subsystems.ActivateableBean</value>
|
<value>org.alfresco.repo.management.subsystems.ActivateableBean</value>
|
||||||
</list>
|
</list>
|
||||||
</property>
|
</property>
|
||||||
@@ -144,6 +144,22 @@
|
|||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean id="WebScriptsHomeAuthenticator"
|
||||||
|
class="org.alfresco.repo.management.subsystems.ChainingSubsystemProxyFactory">
|
||||||
|
<property name="applicationContextManager">
|
||||||
|
<ref bean="Authentication" />
|
||||||
|
</property>
|
||||||
|
<property name="interfaces">
|
||||||
|
<list>
|
||||||
|
<value>org.alfresco.repo.security.authentication.external.ExternalUserAuthenticator</value>
|
||||||
|
<value>org.alfresco.repo.management.subsystems.ActivateableBean</value>
|
||||||
|
</list>
|
||||||
|
</property>
|
||||||
|
<property name="sourceBeanName">
|
||||||
|
<value>webScriptsHomeAuthenticator</value>
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
<!-- Passwords are encoded using MD4 -->
|
<!-- Passwords are encoded using MD4 -->
|
||||||
<!-- This is not ideal and only done to be compatible with NTLM -->
|
<!-- This is not ideal and only done to be compatible with NTLM -->
|
||||||
<!-- authentication against the default authentication mechanism. -->
|
<!-- authentication against the default authentication mechanism. -->
|
||||||
|
@@ -563,6 +563,7 @@ authentication.ticket.validDuration=PT1H
|
|||||||
authentication.ticket.useSingleTicketPerUser=true
|
authentication.ticket.useSingleTicketPerUser=true
|
||||||
|
|
||||||
authentication.alwaysAllowBasicAuthForAdminConsole.enabled=true
|
authentication.alwaysAllowBasicAuthForAdminConsole.enabled=true
|
||||||
|
authentication.alwaysAllowBasicAuthForWebScriptsHome.enabled=true
|
||||||
authentication.getRemoteUserTimeoutMilliseconds=10000
|
authentication.getRemoteUserTimeoutMilliseconds=10000
|
||||||
|
|
||||||
# FTP access
|
# FTP access
|
||||||
|
@@ -104,4 +104,7 @@
|
|||||||
<ref bean="transactionService" />
|
<ref bean="transactionService" />
|
||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean id="webScriptsHomeAuthenticator" class="org.alfresco.repo.security.authentication.external.DefaultWebScriptsHomeAuthenticator" />
|
||||||
|
|
||||||
</beans>
|
</beans>
|
@@ -170,6 +170,9 @@
|
|||||||
<property name="adminConsoleScopes">
|
<property name="adminConsoleScopes">
|
||||||
<value>${identity-service.admin-console.scopes:openid,profile,email,offline_access}</value>
|
<value>${identity-service.admin-console.scopes:openid,profile,email,offline_access}</value>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="webScriptsHomeScopes">
|
||||||
|
<value>${identity-service.webscripts-home.scopes:openid,profile,email,offline_access}</value>
|
||||||
|
</property>
|
||||||
<property name="passwordGrantScopes">
|
<property name="passwordGrantScopes">
|
||||||
<value>${identity-service.password-grant.scopes:openid,profile,email}</value>
|
<value>${identity-service.password-grant.scopes:openid,profile,email}</value>
|
||||||
</property>
|
</property>
|
||||||
@@ -179,6 +182,9 @@
|
|||||||
<property name="jwtClockSkewMs">
|
<property name="jwtClockSkewMs">
|
||||||
<value>${identity-service.jwt-clock-skew-ms:0}</value>
|
<value>${identity-service.jwt-clock-skew-ms:0}</value>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="webScriptsHomeRedirectPath">
|
||||||
|
<value>${identity-service.webscripts-home.redirect-path}</value>
|
||||||
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<!-- Enable control over mapping between request and user ID -->
|
<!-- Enable control over mapping between request and user ID -->
|
||||||
@@ -197,12 +203,12 @@
|
|||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="adminConsoleAuthenticationCookiesService" class="org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleAuthenticationCookiesService">
|
<bean id="adminAuthenticationCookiesService" class="org.alfresco.repo.security.authentication.identityservice.authentication.AdminAuthenticationCookiesService">
|
||||||
<constructor-arg ref="sysAdminParams" />
|
<constructor-arg ref="sysAdminParams" />
|
||||||
<constructor-arg value="${admin.console.cookie.lifetime:86400}" />
|
<constructor-arg value="${admin.console.cookie.lifetime:86400}" />
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
<bean id="adminConsoleAuthenticator" class="org.alfresco.repo.security.authentication.identityservice.admin.IdentityServiceAdminConsoleAuthenticator">
|
<bean id="adminConsoleAuthenticator" class="org.alfresco.repo.security.authentication.identityservice.authentication.admin.IdentityServiceAdminConsoleAuthenticator">
|
||||||
<property name="active">
|
<property name="active">
|
||||||
<value>${identity-service.authentication.enabled}</value>
|
<value>${identity-service.authentication.enabled}</value>
|
||||||
</property>
|
</property>
|
||||||
@@ -210,7 +216,7 @@
|
|||||||
<ref bean="identityServiceFacade"/>
|
<ref bean="identityServiceFacade"/>
|
||||||
</property>
|
</property>
|
||||||
<property name="cookiesService">
|
<property name="cookiesService">
|
||||||
<ref bean="adminConsoleAuthenticationCookiesService" />
|
<ref bean="adminAuthenticationCookiesService" />
|
||||||
</property>
|
</property>
|
||||||
<property name="remoteUserMapper">
|
<property name="remoteUserMapper">
|
||||||
<ref bean="remoteUserMapper" />
|
<ref bean="remoteUserMapper" />
|
||||||
@@ -220,6 +226,24 @@
|
|||||||
</property>
|
</property>
|
||||||
</bean>
|
</bean>
|
||||||
|
|
||||||
|
<bean id="webScriptsHomeAuthenticator" class="org.alfresco.repo.security.authentication.identityservice.authentication.webscripts.IdentityServiceWebScriptsHomeAuthenticator">
|
||||||
|
<property name="active">
|
||||||
|
<value>${identity-service.authentication.enabled}</value>
|
||||||
|
</property>
|
||||||
|
<property name="identityServiceFacade">
|
||||||
|
<ref bean="identityServiceFacade"/>
|
||||||
|
</property>
|
||||||
|
<property name="cookiesService">
|
||||||
|
<ref bean="adminAuthenticationCookiesService" />
|
||||||
|
</property>
|
||||||
|
<property name="remoteUserMapper">
|
||||||
|
<ref bean="remoteUserMapper" />
|
||||||
|
</property>
|
||||||
|
<property name="identityServiceConfig">
|
||||||
|
<ref bean="identityServiceConfig" />
|
||||||
|
</property>
|
||||||
|
</bean>
|
||||||
|
|
||||||
<bean id="jitProvisioningHandler" class="org.alfresco.repo.security.authentication.identityservice.IdentityServiceJITProvisioningHandler">
|
<bean id="jitProvisioningHandler" class="org.alfresco.repo.security.authentication.identityservice.IdentityServiceJITProvisioningHandler">
|
||||||
<constructor-arg ref="PersonService"/>
|
<constructor-arg ref="PersonService"/>
|
||||||
<constructor-arg ref="identityServiceFacade"/>
|
<constructor-arg ref="identityServiceFacade"/>
|
||||||
|
@@ -12,11 +12,13 @@ identity-service.resource=alfresco
|
|||||||
identity-service.credentials.secret=
|
identity-service.credentials.secret=
|
||||||
identity-service.public-client=true
|
identity-service.public-client=true
|
||||||
identity-service.admin-console.redirect-path=/alfresco/s/admin/admin-communitysummary
|
identity-service.admin-console.redirect-path=/alfresco/s/admin/admin-communitysummary
|
||||||
|
identity-service.webscripts-home.redirect-path=/alfresco/s/index
|
||||||
identity-service.signature-algorithms=RS256,PS256
|
identity-service.signature-algorithms=RS256,PS256
|
||||||
identity-service.first-name-attribute=given_name
|
identity-service.first-name-attribute=given_name
|
||||||
identity-service.last-name-attribute=family_name
|
identity-service.last-name-attribute=family_name
|
||||||
identity-service.email-attribute=email
|
identity-service.email-attribute=email
|
||||||
identity-service.admin-console.scopes=openid,profile,email,offline_access
|
identity-service.admin-console.scopes=openid,profile,email,offline_access
|
||||||
|
identity-service.webscripts-home.scopes=openid,profile,email,offline_access
|
||||||
identity-service.password-grant.scopes=openid,profile,email
|
identity-service.password-grant.scopes=openid,profile,email
|
||||||
identity-service.issuer-attribute=issuer
|
identity-service.issuer-attribute=issuer
|
||||||
identity-service.jwt-clock-skew-ms=0
|
identity-service.jwt-clock-skew-ms=0
|
||||||
|
@@ -34,11 +34,12 @@ import org.alfresco.repo.security.authentication.identityservice.IdentityService
|
|||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceJITProvisioningHandlerUnitTest;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceJITProvisioningHandlerUnitTest;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.LazyInstantiatingIdentityServiceFacadeUnitTest;
|
import org.alfresco.repo.security.authentication.identityservice.LazyInstantiatingIdentityServiceFacadeUnitTest;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.SpringBasedIdentityServiceFacadeUnitTest;
|
import org.alfresco.repo.security.authentication.identityservice.SpringBasedIdentityServiceFacadeUnitTest;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleAuthenticationCookiesServiceUnitTest;
|
import org.alfresco.repo.security.authentication.identityservice.authentication.AdditionalHeadersHttpServletRequestWrapperUnitTest;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.admin.AdminConsoleHttpServletRequestWrapperUnitTest;
|
import org.alfresco.repo.security.authentication.identityservice.authentication.AdminAuthenticationCookiesServiceUnitTest;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.admin.IdentityServiceAdminConsoleAuthenticatorUnitTest;
|
import org.alfresco.repo.security.authentication.identityservice.authentication.admin.IdentityServiceAdminConsoleAuthenticatorUnitTest;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.user.AccessTokenToDecodedTokenUserMapperUnitTest;
|
import org.alfresco.repo.security.authentication.identityservice.user.AccessTokenToDecodedTokenUserMapperUnitTest;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.user.TokenUserToOIDCUserMapperUnitTest;
|
import org.alfresco.repo.security.authentication.identityservice.user.TokenUserToOIDCUserMapperUnitTest;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.webscript.IdentityServiceWebScriptsHomeAuthenticatorUnitTest;
|
||||||
import org.alfresco.util.testing.category.DBTests;
|
import org.alfresco.util.testing.category.DBTests;
|
||||||
import org.alfresco.util.testing.category.NonBuildTests;
|
import org.alfresco.util.testing.category.NonBuildTests;
|
||||||
|
|
||||||
@@ -153,9 +154,10 @@ import org.alfresco.util.testing.category.NonBuildTests;
|
|||||||
IdentityServiceJITProvisioningHandlerUnitTest.class,
|
IdentityServiceJITProvisioningHandlerUnitTest.class,
|
||||||
AccessTokenToDecodedTokenUserMapperUnitTest.class,
|
AccessTokenToDecodedTokenUserMapperUnitTest.class,
|
||||||
TokenUserToOIDCUserMapperUnitTest.class,
|
TokenUserToOIDCUserMapperUnitTest.class,
|
||||||
AdminConsoleAuthenticationCookiesServiceUnitTest.class,
|
AdminAuthenticationCookiesServiceUnitTest.class,
|
||||||
AdminConsoleHttpServletRequestWrapperUnitTest.class,
|
AdditionalHeadersHttpServletRequestWrapperUnitTest.class,
|
||||||
IdentityServiceAdminConsoleAuthenticatorUnitTest.class,
|
IdentityServiceAdminConsoleAuthenticatorUnitTest.class,
|
||||||
|
IdentityServiceWebScriptsHomeAuthenticatorUnitTest.class,
|
||||||
ClientRegistrationProviderUnitTest.class,
|
ClientRegistrationProviderUnitTest.class,
|
||||||
org.alfresco.repo.security.authentication.CompositePasswordEncoderTest.class,
|
org.alfresco.repo.security.authentication.CompositePasswordEncoderTest.class,
|
||||||
org.alfresco.repo.security.authentication.PasswordHashingTest.class,
|
org.alfresco.repo.security.authentication.PasswordHashingTest.class,
|
||||||
|
@@ -23,7 +23,7 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.repo.security.authentication.identityservice.admin;
|
package org.alfresco.repo.security.authentication.identityservice.authentication;
|
||||||
|
|
||||||
import static java.util.Collections.enumeration;
|
import static java.util.Collections.enumeration;
|
||||||
import static java.util.Collections.list;
|
import static java.util.Collections.list;
|
||||||
@@ -49,19 +49,18 @@ import org.mockito.Mock;
|
|||||||
import org.alfresco.error.AlfrescoRuntimeException;
|
import org.alfresco.error.AlfrescoRuntimeException;
|
||||||
|
|
||||||
@SuppressWarnings("PMD.UseDiamondOperator")
|
@SuppressWarnings("PMD.UseDiamondOperator")
|
||||||
public class AdminConsoleHttpServletRequestWrapperUnitTest
|
public class AdditionalHeadersHttpServletRequestWrapperUnitTest
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String DEFAULT_HEADER = "default_header";
|
private static final String DEFAULT_HEADER = "default_header";
|
||||||
private static final String DEFAULT_HEADER_VALUE = "default_value";
|
private static final String DEFAULT_HEADER_VALUE = "default_value";
|
||||||
private static final String ADDITIONAL_HEADER = "additional_header";
|
private static final String ADDITIONAL_HEADER = "additional_header";
|
||||||
private static final String ADDITIONAL_HEADER_VALUE = "additional_value";
|
private static final String ADDITIONAL_HEADER_VALUE = "additional_value";
|
||||||
private static final Map<String, String> DEFAULT_HEADERS = new HashMap<String, String>() {
|
private static final Map<String, String> DEFAULT_HEADERS = new HashMap<>() {
|
||||||
{
|
{
|
||||||
put(DEFAULT_HEADER, DEFAULT_HEADER_VALUE);
|
put(DEFAULT_HEADER, DEFAULT_HEADER_VALUE);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private static final Map<String, String> ADDITIONAL_HEADERS = new HashMap<String, String>() {
|
private static final Map<String, String> ADDITIONAL_HEADERS = new HashMap<>() {
|
||||||
{
|
{
|
||||||
put(ADDITIONAL_HEADER, ADDITIONAL_HEADER_VALUE);
|
put(ADDITIONAL_HEADER, ADDITIONAL_HEADER_VALUE);
|
||||||
}
|
}
|
||||||
@@ -69,25 +68,25 @@ public class AdminConsoleHttpServletRequestWrapperUnitTest
|
|||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HttpServletRequest request;
|
private HttpServletRequest request;
|
||||||
private AdminConsoleHttpServletRequestWrapper requestWrapper;
|
private AdditionalHeadersHttpServletRequestWrapper requestWrapper;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
requestWrapper = new AdminConsoleHttpServletRequestWrapper(ADDITIONAL_HEADERS, request);
|
requestWrapper = new AdditionalHeadersHttpServletRequestWrapper(ADDITIONAL_HEADERS, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = AlfrescoRuntimeException.class)
|
@Test(expected = AlfrescoRuntimeException.class)
|
||||||
public void wrapperShouldNotBeInstancedWithoutAdditionalHeaders()
|
public void wrapperShouldNotBeInstancedWithoutAdditionalHeaders()
|
||||||
{
|
{
|
||||||
new AdminConsoleHttpServletRequestWrapper(null, request);
|
new AdditionalHeadersHttpServletRequestWrapper(null, request);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void wrapperShouldNotBeInstancedWithoutRequestsToWrap()
|
public void wrapperShouldNotBeInstancedWithoutRequestsToWrap()
|
||||||
{
|
{
|
||||||
new AdminConsoleHttpServletRequestWrapper(new HashMap<>(), null);
|
new AdditionalHeadersHttpServletRequestWrapper(new HashMap<>(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -112,7 +111,7 @@ public class AdminConsoleHttpServletRequestWrapperUnitTest
|
|||||||
{
|
{
|
||||||
when(request.getHeaderNames()).thenReturn(enumeration(DEFAULT_HEADERS.keySet()));
|
when(request.getHeaderNames()).thenReturn(enumeration(DEFAULT_HEADERS.keySet()));
|
||||||
|
|
||||||
requestWrapper = new AdminConsoleHttpServletRequestWrapper(new HashMap<>(), request);
|
requestWrapper = new AdditionalHeadersHttpServletRequestWrapper(new HashMap<>(), request);
|
||||||
Enumeration<String> headerNames = requestWrapper.getHeaderNames();
|
Enumeration<String> headerNames = requestWrapper.getHeaderNames();
|
||||||
assertNotNull("headerNames should not be null", headerNames);
|
assertNotNull("headerNames should not be null", headerNames);
|
||||||
assertTrue("headerNames should not be empty", headerNames.hasMoreElements());
|
assertTrue("headerNames should not be empty", headerNames.hasMoreElements());
|
||||||
@@ -164,7 +163,7 @@ public class AdminConsoleHttpServletRequestWrapperUnitTest
|
|||||||
Map<String, String> overrideHeaders = new HashMap<>();
|
Map<String, String> overrideHeaders = new HashMap<>();
|
||||||
overrideHeaders.put(DEFAULT_HEADER, overrideHeaderValue);
|
overrideHeaders.put(DEFAULT_HEADER, overrideHeaderValue);
|
||||||
|
|
||||||
requestWrapper = new AdminConsoleHttpServletRequestWrapper(overrideHeaders, request);
|
requestWrapper = new AdditionalHeadersHttpServletRequestWrapper(overrideHeaders, request);
|
||||||
String header = requestWrapper.getHeader(DEFAULT_HEADER);
|
String header = requestWrapper.getHeader(DEFAULT_HEADER);
|
||||||
assertEquals("The header should have the overridden value", overrideHeaderValue, header);
|
assertEquals("The header should have the overridden value", overrideHeaderValue, header);
|
||||||
|
|
||||||
@@ -204,7 +203,7 @@ public class AdminConsoleHttpServletRequestWrapperUnitTest
|
|||||||
Map<String, String> overrideHeaders = new HashMap<>();
|
Map<String, String> overrideHeaders = new HashMap<>();
|
||||||
overrideHeaders.put(DEFAULT_HEADER, overrideHeaderValue);
|
overrideHeaders.put(DEFAULT_HEADER, overrideHeaderValue);
|
||||||
|
|
||||||
requestWrapper = new AdminConsoleHttpServletRequestWrapper(overrideHeaders, request);
|
requestWrapper = new AdditionalHeadersHttpServletRequestWrapper(overrideHeaders, request);
|
||||||
Enumeration<String> headers = requestWrapper.getHeaders(DEFAULT_HEADER);
|
Enumeration<String> headers = requestWrapper.getHeaders(DEFAULT_HEADER);
|
||||||
assertNotNull("The headers enumeration should not be null", headers);
|
assertNotNull("The headers enumeration should not be null", headers);
|
||||||
assertTrue("The headers enumeration should not be empty", headers.hasMoreElements());
|
assertTrue("The headers enumeration should not be empty", headers.hasMoreElements());
|
@@ -23,7 +23,7 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.repo.security.authentication.identityservice.admin;
|
package org.alfresco.repo.security.authentication.identityservice.authentication;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
@@ -46,7 +46,7 @@ import org.mockito.Mock;
|
|||||||
|
|
||||||
import org.alfresco.repo.admin.SysAdminParams;
|
import org.alfresco.repo.admin.SysAdminParams;
|
||||||
|
|
||||||
public class AdminConsoleAuthenticationCookiesServiceUnitTest
|
public class AdminAuthenticationCookiesServiceUnitTest
|
||||||
{
|
{
|
||||||
private static final int DEFAULT_COOKIE_LIFETIME = 86400;
|
private static final int DEFAULT_COOKIE_LIFETIME = 86400;
|
||||||
private static final String COOKIE_NAME = "cookie";
|
private static final String COOKIE_NAME = "cookie";
|
||||||
@@ -59,13 +59,13 @@ public class AdminConsoleAuthenticationCookiesServiceUnitTest
|
|||||||
private SysAdminParams sysAdminParams;
|
private SysAdminParams sysAdminParams;
|
||||||
@Captor
|
@Captor
|
||||||
private ArgumentCaptor<Cookie> cookieCaptor;
|
private ArgumentCaptor<Cookie> cookieCaptor;
|
||||||
private AdminConsoleAuthenticationCookiesService cookiesService;
|
private AdminAuthenticationCookiesService cookiesService;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setUp()
|
public void setUp()
|
||||||
{
|
{
|
||||||
initMocks(this);
|
initMocks(this);
|
||||||
cookiesService = new AdminConsoleAuthenticationCookiesService(sysAdminParams, DEFAULT_COOKIE_LIFETIME);
|
cookiesService = new AdminAuthenticationCookiesService(sysAdminParams, DEFAULT_COOKIE_LIFETIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -138,7 +138,7 @@ public class AdminConsoleAuthenticationCookiesServiceUnitTest
|
|||||||
public void cookieWithCustomMaxAgeShouldBeAddedToTheResponse()
|
public void cookieWithCustomMaxAgeShouldBeAddedToTheResponse()
|
||||||
{
|
{
|
||||||
int customMaxAge = 60;
|
int customMaxAge = 60;
|
||||||
cookiesService = new AdminConsoleAuthenticationCookiesService(sysAdminParams, customMaxAge);
|
cookiesService = new AdminAuthenticationCookiesService(sysAdminParams, customMaxAge);
|
||||||
when(sysAdminParams.getAlfrescoProtocol()).thenReturn("https");
|
when(sysAdminParams.getAlfrescoProtocol()).thenReturn("https");
|
||||||
|
|
||||||
cookiesService.addCookie(COOKIE_NAME, COOKIE_VALUE, response);
|
cookiesService.addCookie(COOKIE_NAME, COOKIE_VALUE, response);
|
@@ -23,7 +23,7 @@
|
|||||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
package org.alfresco.repo.security.authentication.identityservice.admin;
|
package org.alfresco.repo.security.authentication.identityservice.authentication.admin;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
@@ -58,11 +58,12 @@ import org.alfresco.repo.security.authentication.identityservice.IdentityService
|
|||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
|
||||||
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.authentication.AdditionalHeadersHttpServletRequestWrapper;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.authentication.AdminAuthenticationCookiesService;
|
||||||
|
|
||||||
@SuppressWarnings("PMD.AvoidStringBufferField")
|
@SuppressWarnings("PMD.AvoidStringBufferField")
|
||||||
public class IdentityServiceAdminConsoleAuthenticatorUnitTest
|
public class IdentityServiceAdminConsoleAuthenticatorUnitTest
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String ALFRESCO_ACCESS_TOKEN = "ALFRESCO_ACCESS_TOKEN";
|
private static final String ALFRESCO_ACCESS_TOKEN = "ALFRESCO_ACCESS_TOKEN";
|
||||||
private static final String ALFRESCO_REFRESH_TOKEN = "ALFRESCO_REFRESH_TOKEN";
|
private static final String ALFRESCO_REFRESH_TOKEN = "ALFRESCO_REFRESH_TOKEN";
|
||||||
private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION";
|
private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION";
|
||||||
@@ -76,7 +77,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
|
|||||||
@Mock
|
@Mock
|
||||||
IdentityServiceConfig identityServiceConfig;
|
IdentityServiceConfig identityServiceConfig;
|
||||||
@Mock
|
@Mock
|
||||||
AdminConsoleAuthenticationCookiesService cookiesService;
|
AdminAuthenticationCookiesService cookiesService;
|
||||||
@Mock
|
@Mock
|
||||||
RemoteUserMapper remoteUserMapper;
|
RemoteUserMapper remoteUserMapper;
|
||||||
@Mock
|
@Mock
|
||||||
@@ -84,7 +85,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
|
|||||||
@Mock
|
@Mock
|
||||||
AccessToken accessToken;
|
AccessToken accessToken;
|
||||||
@Captor
|
@Captor
|
||||||
ArgumentCaptor<AdminConsoleHttpServletRequestWrapper> requestCaptor;
|
ArgumentCaptor<AdditionalHeadersHttpServletRequestWrapper> requestCaptor;
|
||||||
|
|
||||||
IdentityServiceAdminConsoleAuthenticator authenticator;
|
IdentityServiceAdminConsoleAuthenticator authenticator;
|
||||||
|
|
||||||
@@ -122,7 +123,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
|
|||||||
String.valueOf(Instant.now().plusSeconds(60).toEpochMilli()));
|
String.valueOf(Instant.now().plusSeconds(60).toEpochMilli()));
|
||||||
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
|
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
|
||||||
|
|
||||||
String username = authenticator.getAdminConsoleUser(request, response);
|
String username = authenticator.getUserId(request, response);
|
||||||
|
|
||||||
assertEquals("Bearer JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
|
assertEquals("Bearer JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
|
||||||
assertEquals("admin", username);
|
assertEquals("admin", username);
|
||||||
@@ -143,7 +144,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
|
|||||||
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenReturn(accessTokenAuthorization);
|
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenReturn(accessTokenAuthorization);
|
||||||
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
|
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
|
||||||
|
|
||||||
String username = authenticator.getAdminConsoleUser(request, response);
|
String username = authenticator.getUserId(request, response);
|
||||||
|
|
||||||
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "REFRESHED_JWT_TOKEN", response);
|
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "REFRESHED_JWT_TOKEN", response);
|
||||||
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
|
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
|
||||||
@@ -207,7 +208,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
|
|||||||
|
|
||||||
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenThrow(AuthorizationException.class);
|
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenThrow(AuthorizationException.class);
|
||||||
|
|
||||||
String username = authenticator.getAdminConsoleUser(request, response);
|
String username = authenticator.getUserId(request, response);
|
||||||
|
|
||||||
verify(cookiesService).resetCookie(ALFRESCO_ACCESS_TOKEN, response);
|
verify(cookiesService).resetCookie(ALFRESCO_ACCESS_TOKEN, response);
|
||||||
verify(cookiesService).resetCookie(ALFRESCO_REFRESH_TOKEN, response);
|
verify(cookiesService).resetCookie(ALFRESCO_REFRESH_TOKEN, response);
|
||||||
@@ -228,7 +229,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
|
|||||||
.thenReturn(accessTokenAuthorization);
|
.thenReturn(accessTokenAuthorization);
|
||||||
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
|
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
|
||||||
|
|
||||||
String username = authenticator.getAdminConsoleUser(request, response);
|
String username = authenticator.getUserId(request, response);
|
||||||
|
|
||||||
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "JWT_TOKEN", response);
|
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "JWT_TOKEN", response);
|
||||||
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
|
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
|
||||||
@@ -241,7 +242,7 @@ public class IdentityServiceAdminConsoleAuthenticatorUnitTest
|
|||||||
{
|
{
|
||||||
when(remoteUserMapper.getRemoteUser(request)).thenReturn("admin");
|
when(remoteUserMapper.getRemoteUser(request)).thenReturn("admin");
|
||||||
|
|
||||||
String username = authenticator.getAdminConsoleUser(request, response);
|
String username = authenticator.getUserId(request, response);
|
||||||
|
|
||||||
assertEquals("admin", username);
|
assertEquals("admin", username);
|
||||||
}
|
}
|
@@ -0,0 +1,253 @@
|
|||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* Alfresco Repository
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2005 - 2025 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 <http://www.gnu.org/licenses/>.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package org.alfresco.repo.security.authentication.identityservice.webscript;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import com.nimbusds.oauth2.sdk.Scope;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
|
||||||
|
|
||||||
|
import org.alfresco.repo.security.authentication.external.RemoteUserMapper;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceConfig;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessToken;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AccessTokenAuthorization;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationException;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.IdentityServiceFacade.AuthorizationGrant;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.authentication.AdditionalHeadersHttpServletRequestWrapper;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.authentication.AdminAuthenticationCookiesService;
|
||||||
|
import org.alfresco.repo.security.authentication.identityservice.authentication.webscripts.IdentityServiceWebScriptsHomeAuthenticator;
|
||||||
|
|
||||||
|
@SuppressWarnings("PMD.AvoidStringBufferField")
|
||||||
|
public class IdentityServiceWebScriptsHomeAuthenticatorUnitTest
|
||||||
|
{
|
||||||
|
|
||||||
|
private static final String ALFRESCO_ACCESS_TOKEN = "ALFRESCO_ACCESS_TOKEN";
|
||||||
|
private static final String ALFRESCO_REFRESH_TOKEN = "ALFRESCO_REFRESH_TOKEN";
|
||||||
|
private static final String ALFRESCO_TOKEN_EXPIRATION = "ALFRESCO_TOKEN_EXPIRATION";
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
HttpServletRequest request;
|
||||||
|
@Mock
|
||||||
|
HttpServletResponse response;
|
||||||
|
@Mock
|
||||||
|
IdentityServiceFacade identityServiceFacade;
|
||||||
|
@Mock
|
||||||
|
IdentityServiceConfig identityServiceConfig;
|
||||||
|
@Mock
|
||||||
|
AdminAuthenticationCookiesService cookiesService;
|
||||||
|
@Mock
|
||||||
|
RemoteUserMapper remoteUserMapper;
|
||||||
|
@Mock
|
||||||
|
AccessTokenAuthorization accessTokenAuthorization;
|
||||||
|
@Mock
|
||||||
|
AccessToken accessToken;
|
||||||
|
@Captor
|
||||||
|
ArgumentCaptor<AdditionalHeadersHttpServletRequestWrapper> requestCaptor;
|
||||||
|
|
||||||
|
IdentityServiceWebScriptsHomeAuthenticator authenticator;
|
||||||
|
|
||||||
|
StringBuffer webScriptHomeURL = new StringBuffer("http://localhost:8080/alfresco/s/index");
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup()
|
||||||
|
{
|
||||||
|
initMocks(this);
|
||||||
|
ClientRegistration clientRegistration = mock(ClientRegistration.class);
|
||||||
|
ProviderDetails providerDetails = mock(ProviderDetails.class);
|
||||||
|
Scope scope = Scope.parse(Arrays.asList("openid", "profile", "email", "offline_access"));
|
||||||
|
|
||||||
|
when(clientRegistration.getProviderDetails()).thenReturn(providerDetails);
|
||||||
|
when(clientRegistration.getClientId()).thenReturn("alfresco");
|
||||||
|
when(providerDetails.getAuthorizationUri()).thenReturn("http://localhost:8999/auth");
|
||||||
|
when(providerDetails.getConfigurationMetadata()).thenReturn(Map.of("scopes_supported", scope));
|
||||||
|
when(identityServiceFacade.getClientRegistration()).thenReturn(clientRegistration);
|
||||||
|
when(request.getRequestURL()).thenReturn(webScriptHomeURL);
|
||||||
|
when(remoteUserMapper.getRemoteUser(request)).thenReturn(null);
|
||||||
|
|
||||||
|
authenticator = new IdentityServiceWebScriptsHomeAuthenticator();
|
||||||
|
authenticator.setActive(true);
|
||||||
|
authenticator.setIdentityServiceFacade(identityServiceFacade);
|
||||||
|
authenticator.setCookiesService(cookiesService);
|
||||||
|
authenticator.setRemoteUserMapper(remoteUserMapper);
|
||||||
|
authenticator.setIdentityServiceConfig(identityServiceConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCallRemoteMapperIfTokenIsInCookies()
|
||||||
|
{
|
||||||
|
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("JWT_TOKEN");
|
||||||
|
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
|
||||||
|
String.valueOf(Instant.now().plusSeconds(60).toEpochMilli()));
|
||||||
|
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
|
||||||
|
|
||||||
|
String username = authenticator.getUserId(request, response);
|
||||||
|
|
||||||
|
assertEquals("Bearer JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
|
||||||
|
assertEquals("admin", username);
|
||||||
|
assertTrue(authenticator.isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRefreshExpiredTokenAndCallRemoteMapper()
|
||||||
|
{
|
||||||
|
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("EXPIRED_JWT_TOKEN");
|
||||||
|
when(cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request)).thenReturn("REFRESH_TOKEN");
|
||||||
|
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
|
||||||
|
String.valueOf(Instant.now().minusSeconds(60).toEpochMilli()));
|
||||||
|
when(accessToken.getTokenValue()).thenReturn("REFRESHED_JWT_TOKEN");
|
||||||
|
when(accessToken.getExpiresAt()).thenReturn(Instant.now().plusSeconds(60));
|
||||||
|
when(accessTokenAuthorization.getAccessToken()).thenReturn(accessToken);
|
||||||
|
when(accessTokenAuthorization.getRefreshTokenValue()).thenReturn("REFRESH_TOKEN");
|
||||||
|
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenReturn(accessTokenAuthorization);
|
||||||
|
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
|
||||||
|
|
||||||
|
String username = authenticator.getUserId(request, response);
|
||||||
|
|
||||||
|
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "REFRESHED_JWT_TOKEN", response);
|
||||||
|
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
|
||||||
|
assertEquals("Bearer REFRESHED_JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
|
||||||
|
assertEquals("admin", username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCallAuthChallengeWebScriptHome() throws IOException
|
||||||
|
{
|
||||||
|
|
||||||
|
String redirectPath = "/alfresco/s/index";
|
||||||
|
when(request.getRequestURL()).thenReturn(webScriptHomeURL);
|
||||||
|
when(identityServiceConfig.getWebScriptsHomeScopes()).thenReturn(Set.of("openid", "email", "profile", "offline_access"));
|
||||||
|
when(identityServiceConfig.getWebScriptsHomeRedirectPath()).thenReturn(redirectPath);
|
||||||
|
ArgumentCaptor<String> authenticationRequest = ArgumentCaptor.forClass(String.class);
|
||||||
|
String expectedUri = "http://localhost:8999/auth?client_id=alfresco&redirect_uri=%s%s&response_type=code&scope="
|
||||||
|
.formatted("http://localhost:8080", redirectPath);
|
||||||
|
|
||||||
|
authenticator.requestAuthentication(request, response);
|
||||||
|
|
||||||
|
verify(response).sendRedirect(authenticationRequest.capture());
|
||||||
|
assertTrue(authenticationRequest.getValue().contains(expectedUri));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("openid"));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("profile"));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("email"));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("offline_access"));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("state"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCallAuthChallengeWebScriptHomeWithAudience() throws IOException
|
||||||
|
{
|
||||||
|
String audience = "http://localhost:8082";
|
||||||
|
String redirectPath = "/alfresco/s/index";
|
||||||
|
when(request.getRequestURL()).thenReturn(webScriptHomeURL);
|
||||||
|
when(identityServiceConfig.getAudience()).thenReturn(audience);
|
||||||
|
when(identityServiceConfig.getWebScriptsHomeRedirectPath()).thenReturn(redirectPath);
|
||||||
|
when(identityServiceConfig.getWebScriptsHomeScopes()).thenReturn(Set.of("openid", "email", "profile", "offline_access"));
|
||||||
|
ArgumentCaptor<String> authenticationRequest = ArgumentCaptor.forClass(String.class);
|
||||||
|
String expectedUri = "http://localhost:8999/auth?client_id=alfresco&redirect_uri=%s%s&response_type=code&scope="
|
||||||
|
.formatted("http://localhost:8080", redirectPath);
|
||||||
|
|
||||||
|
authenticator.requestAuthentication(request, response);
|
||||||
|
|
||||||
|
verify(response).sendRedirect(authenticationRequest.capture());
|
||||||
|
assertTrue(authenticationRequest.getValue().contains(expectedUri));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("openid"));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("profile"));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("email"));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("offline_access"));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("audience=%s".formatted(audience)));
|
||||||
|
assertTrue(authenticationRequest.getValue().contains("state"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldResetCookiesAndCallAuthChallenge() throws IOException
|
||||||
|
{
|
||||||
|
when(cookiesService.getCookie(ALFRESCO_ACCESS_TOKEN, request)).thenReturn("EXPIRED_JWT_TOKEN");
|
||||||
|
when(cookiesService.getCookie(ALFRESCO_REFRESH_TOKEN, request)).thenReturn("REFRESH_TOKEN");
|
||||||
|
when(cookiesService.getCookie(ALFRESCO_TOKEN_EXPIRATION, request)).thenReturn(
|
||||||
|
String.valueOf(Instant.now().minusSeconds(60).toEpochMilli()));
|
||||||
|
|
||||||
|
when(identityServiceFacade.authorize(any(AuthorizationGrant.class))).thenThrow(AuthorizationException.class);
|
||||||
|
|
||||||
|
String username = authenticator.getUserId(request, response);
|
||||||
|
|
||||||
|
verify(cookiesService).resetCookie(ALFRESCO_ACCESS_TOKEN, response);
|
||||||
|
verify(cookiesService).resetCookie(ALFRESCO_REFRESH_TOKEN, response);
|
||||||
|
verify(cookiesService).resetCookie(ALFRESCO_TOKEN_EXPIRATION, response);
|
||||||
|
assertNull(username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldAuthorizeCodeAndSetCookies()
|
||||||
|
{
|
||||||
|
when(request.getParameter("code")).thenReturn("auth_code");
|
||||||
|
when(accessToken.getTokenValue()).thenReturn("JWT_TOKEN");
|
||||||
|
when(accessToken.getExpiresAt()).thenReturn(Instant.now().plusSeconds(60));
|
||||||
|
when(accessTokenAuthorization.getAccessToken()).thenReturn(accessToken);
|
||||||
|
when(accessTokenAuthorization.getRefreshTokenValue()).thenReturn("REFRESH_TOKEN");
|
||||||
|
when(identityServiceFacade.authorize(
|
||||||
|
AuthorizationGrant.authorizationCode("auth_code", webScriptHomeURL.toString())))
|
||||||
|
.thenReturn(accessTokenAuthorization);
|
||||||
|
when(remoteUserMapper.getRemoteUser(requestCaptor.capture())).thenReturn("admin");
|
||||||
|
|
||||||
|
String username = authenticator.getUserId(request, response);
|
||||||
|
|
||||||
|
verify(cookiesService).addCookie(ALFRESCO_ACCESS_TOKEN, "JWT_TOKEN", response);
|
||||||
|
verify(cookiesService).addCookie(ALFRESCO_REFRESH_TOKEN, "REFRESH_TOKEN", response);
|
||||||
|
assertEquals("Bearer JWT_TOKEN", requestCaptor.getValue().getHeader("Authorization"));
|
||||||
|
assertEquals("admin", username);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldExtractUsernameFromAuthorizationHeader()
|
||||||
|
{
|
||||||
|
when(remoteUserMapper.getRemoteUser(request)).thenReturn("admin");
|
||||||
|
|
||||||
|
String username = authenticator.getUserId(request, response);
|
||||||
|
|
||||||
|
assertEquals("admin", username);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user