WebServiceAuth.java
/*
** Module : WebServiceAuth.java
** Abstract : Base class for authenticating and authorizing a web request.
**
** Copyright (c) 2022-2024, Golden Code Development Corporation.
**
** -#- -I- --Date-- ------------------------------------Description-------------------------------------------
** 001 CA 20220404 Created the first version.
** 002 GBB 20230512 Logging methods replaced by CentralLogger/ConversionStatus.
** 003 SBI 20230602 Changed string presentations of the web services that each service path would be represented
** by the absolute path from the web service root.
** 004 GBB 20230825 SecurityManager legacy web methods moved to LegacyWebSecurityManager.
** 005 RAA 20240604 Avoided a null contextPath when doing authorization.
** 006 GBB 20240826 Moving FileSystem to osresource package.
*/
/*
** This program is free software: you can redistribute it and/or modify
** it under the terms of the GNU Affero General Public License as
** published by the Free Software Foundation, either version 3 of the
** License, or (at your option) any later version.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU Affero General Public License for more details.
**
** You may find a copy of the GNU Affero GPL version 3 at the following
** location: https://www.gnu.org/licenses/agpl-3.0.en.html
**
** Additional terms under GNU Affero GPL version 3 section 7:
**
** Under Section 7 of the GNU Affero GPL version 3, the following additional
** terms apply to the works covered under the License. These additional terms
** are non-permissive additional terms allowed under Section 7 of the GNU
** Affero GPL version 3 and may not be removed by you.
**
** 0. Attribution Requirement.
**
** You must preserve all legal notices or author attributions in the covered
** work or Appropriate Legal Notices displayed by works containing the covered
** work. You may not remove from the covered work any author or developer
** credit already included within the covered work.
**
** 1. No License To Use Trademarks.
**
** This license does not grant any license or rights to use the trademarks
** Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
** of Golden Code Development Corporation. You are not authorized to use the
** name Golden Code, FWD, or the names of any author or contributor, for
** publicity purposes without written authorization.
**
** 2. No Misrepresentation of Affiliation.
**
** You may not represent yourself as Golden Code Development Corporation or FWD.
**
** You may not represent yourself for publicity purposes as associated with
** Golden Code Development Corporation, FWD, or any author or contributor to
** the covered work, without written authorization.
**
** 3. No Misrepresentation of Source or Origin.
**
** You may not represent the covered work as solely your work. All modified
** versions of the covered work must be marked in a reasonable way to make it
** clear that the modified work is not originating from Golden Code Development
** Corporation or FWD. All modified versions must contain the notices of
** attribution required in this license.
*/
package com.goldencode.p2j.main;
import java.nio.file.*;
import javax.servlet.http.*;
import org.eclipse.jetty.http.*;
import com.goldencode.p2j.security.*;
import com.goldencode.p2j.security.SecurityManager;
import com.goldencode.p2j.util.osresource.FileSystem;
import com.goldencode.p2j.util.logging.CentralLogger;
/**
* Base class for authenticating and authorizing a web request.
*/
abstract class WebServiceAuth
{
/** Logger. */
protected static final CentralLogger LOG = CentralLogger.get(WebServiceAuth.class.getName());
/** The name of the header holding the FWD authentication token. */
private static final String FWD_SESSION_ID_HEADER_NAME = "FwdSessionId";
/**
* When <code>true</code>, the login service must be used to authenticate. Otherwise, each service call must
* have the authentication data in its request.
*/
protected final boolean loginApiAuth;
/** The timeout for the created context. */
protected final int timeout;
/** The cached {@link LegacyWebSecurityManager} instance. */
protected final LegacyWebSecurityManager wsm;
/** The web service type (REST, SOAP, WEBHANDLER). */
private final String type;
/** The resource plugin used to perform the authorization. */
private final WebServiceResource webServiceResource;
/**
* Create a web service authentication and authorization with the specified details.
*
* @param type
* The web service type (REST, SOAP, WEBHANDLER).
* @param loginApiAuth
* Flag indicating if there is an explicit login API to be used.
* @param timeout
* The context timeout.
*/
public WebServiceAuth(String type, boolean loginApiAuth, int timeout)
{
this.type = type;
this.loginApiAuth = loginApiAuth;
this.timeout = timeout;
SecurityManager sm = SecurityManager.getInstance();
this.wsm = sm.legacyWebSm;
this.webServiceResource = (WebServiceResource) sm.getPluginInstance("webservice");
}
/**
* Perform the actual login, which will create the FWD context associated with this web request.
*
* @param request
* The HTTP request.
*
* @return The authentication token, or <code>null</code> if the authentication failed.
*/
protected abstract String login(HttpServletRequest request);
/**
* Authenticate this request, but do not create the FWD context.
*
* @param request
* The HTTP request.
*
* @return <code>true</code> if the request contains valid credentials.
*/
protected abstract boolean authenticate(HttpServletRequest request);
/**
* Perform the logout operation. This will destroy the context associated with the specified token.
*
* @param token
* The authentication token
*
* @return <code>true</code> if the context was destroyed.
*/
protected boolean logout(String token)
{
if (token == null)
{
return false;
}
boolean logout = wsm.destroyWebRequestContext(token);
return logout;
}
/**
* Authenticate the web request. If the {@link #loginApiAuth} is set, the authorization token will be read
* from the {@value #FWD_SESSION_ID_HEADER_NAME} header, and will return <code>true</code> if the context
* exists for this token.
* <p>
* Otherwise, the request is authenticated.
*
* @param request
* The HTTP request.
* @param response
* The HTTP response.
*
* @return <code>true</code> if the request could be authenticated.
*/
protected final boolean authenticate(HttpServletRequest request, HttpServletResponse response)
{
if (!loginApiAuth)
{
// if we reach this point, it means each request must the authentication details; the context is not
// created here, but when the authorization is performed
boolean auth = authenticate(request);
if (!auth)
{
response.setStatus(HttpStatus.UNAUTHORIZED_401);
}
return auth;
}
// otherwise, the token must be supplied in the request
String token = getAuthorizationToken(request);
if (token == null || !wsm.hasWebRequestContext(token))
{
response.setStatus(HttpStatus.FORBIDDEN_403);
return false;
}
return true;
}
/**
* Authorize this web request. If the {@link #loginApiAuth} flag is not set, an {@link #login} is first
* tried to authenticate the request, after which the authorization will be checked for the created context.
* <p>
* If the request can't be authenticated, the response status code will be set to
* {@link HttpStatus#UNAUTHORIZED_401}.
* <p>
* If the context is not authorized, the response status code will be set to
* {@link HttpStatus#FORBIDDEN_403}.
*
* @param target
* The target path.
* @param request
* The HTTP request.
* @param response
* The HTTP response.
*
* @return The authentication token, or <code>null</code> if the request can't be authenticated or
* authorized.
*/
protected final String authorize(String target, HttpServletRequest request, HttpServletResponse response)
{
String token = null;
if (!loginApiAuth)
{
// attempt an actual login, which (if authorized) will create the token and the FWD context, too.
token = login(request);
if (token == null)
{
response.setStatus(HttpStatus.UNAUTHORIZED_401);
return null;
}
}
else
{
// the token must be specified for each request
token = getAuthorizationToken(request);
}
if (token == null || !wsm.hasWebRequestContext(token))
{
response.setStatus(HttpStatus.FORBIDDEN_403);
return null;
}
boolean res = wsm.checkAuthorization(token, () ->
{
// the syntax is:
// REST:HTTP-METHOD:/path/to/service
// WEBHANDLER:HTTP-METHOD:/path/to/service
// SOAP:HTTP-METHOD:/path/to/service:namespace/binding/operation
String contextPath = request.getContextPath();
if (contextPath == null)
{
contextPath = "";
}
String instance = type.toUpperCase() + ":" +
request.getMethod().toUpperCase() + ":" +
Paths.get(contextPath, target)
.normalize()
.toString()
.replaceAll(FileSystem.ANY_PATH_SEPARATOR, "/");
return webServiceResource.isAllowed(instance);
});
if (!res)
{
if (!loginApiAuth)
{
logout(token);
}
response.setStatus(HttpStatus.FORBIDDEN_403);
return null;
}
return token;
}
/**
* Read the authorization token from the HTTP request's {@value #FWD_SESSION_ID_HEADER_NAME} header.
*
* @param request
* The HTTP response.
*
* @return The authorization token.
*/
protected String getAuthorizationToken(HttpServletRequest request)
{
return request.getHeader(FWD_SESSION_ID_HEADER_NAME);
}
/**
* Set the authorization token to the HTTP response, in the {@value #FWD_SESSION_ID_HEADER_NAME} header.
*
* @param token
* The authorization token.
* @param response
* The HTTP response.
*/
protected void setAuthorizationToken(String token, HttpServletResponse response)
{
response.setHeader(FWD_SESSION_ID_HEADER_NAME, token);
}
}