AdminRestHandler.java
/*
** Module : AdminRestHandler.java
** Abstract : Class that handles requests for administration of 4GL resources purposes.
**
** Copyright (c) 2025, Golden Code Development Corporation.
**
** -#- -I- --Date-- ---------------------------------------Description----------------------------------------
** 001 OM 20250310 First revision, configuration and authentication of REST handlers.
** 002 OM 20250408 Restrict processing to requests to specific context path.
*/
/*
** 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.admin.server;
import com.goldencode.p2j.main.*;
import com.goldencode.p2j.util.*;
import com.goldencode.p2j.util.logging.*;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.*;
import org.eclipse.jetty.util.*;
import org.reflections.*;
import org.reflections.scanners.*;
import org.reflections.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.*;
import java.util.logging.*;
/**
* This class is the central point of initialization and authentication of all REST administrative handlers.
* It is the only one who registers itself to {@code StandardServer} with {@code /admin} base path. It will:
* <ul>
* <li>perform the initialization by scanning the classpath for classes which extend
* {@link BaseAdminRestHandler}. These classes must declare an {@code initialize} static method which
* should return their singleton if the class decided it is required;
* <li>do the authentication (login and logout) for all other handlers for specific resources;
* <li>intercept all REST request and dispatch them to ALL configured handlers, They are responsible for
* checking the authorization using the provided {@link WebAuthHandler} and serve only the
* requests they are registered in the ACLs.
* </ul>
*/
public class AdminRestHandler
extends HandlerCollection
{
/** Logger */
private static final CentralLogger LOG = CentralLogger.get(AdminRestHandler.class);
/** The handler used for authentication and authorization. */
protected final WebAuthHandler authHandler;
/** The context handler. */
private final ContextHandler handler;
/** The singleton which handles tenant REST requests. */
private static AdminRestHandler instance;
/** The root of the contexts this handler is able to process. */
private final String CONTEXT_PREFIX = "/admin";
private AdminRestHandler(int timeout)
{
// authHandler is used to authenticate all calls to specialized BaseAdminRestHandler classes
authHandler = new WebAuthHandler("admin", "basic", "/admin/login", "/admin/logout", timeout);
handler = new ContextHandler(CONTEXT_PREFIX);
handler.setAllowNullPathInfo(true);
handler.setHandler(this);
}
/**
* Tries to initialize this REST handler. It does this by reading the directory and creating the
* {@code AdminRestHandler} according to the settings.
*
* @return An instance of {@link AdminRestHandler} on success or {@code null} if the directory is not
* configured with a REST handler for tenant administration.
*/
public static AdminRestHandler initialize()
{
if (!Utils.getDirectoryNodeBoolean(null, "admin-rest/enabled", false, false))
{
return null; // quick out
}
synchronized (AdminRestHandler.class)
{
if (instance == null)
{
instance = new AdminRestHandler(
Utils.getDirectoryNodeInt(null, "admin-rest/timeout", 3600, false));
String crtPackage = AdminRestHandler.class.getPackage().getName();
Reflections reflections = new Reflections(
new ConfigurationBuilder().setUrls(ClasspathHelper.forPackage(crtPackage))
.setScanners(Scanners.SubTypes));
Set<Class<? extends BaseAdminRestHandler>> hnds =
reflections.getSubTypesOf(BaseAdminRestHandler.class);
for (Class<? extends BaseAdminRestHandler> hndClass : hnds)
{
Method initMethod;
try
{
initMethod = hndClass.getDeclaredMethod("initialize", WebAuthHandler.class);
}
catch (NoSuchMethodException e)
{
LOG.log(Level.WARNING,
hndClass + " does not expose a \"public static initialize(WebAuthHandler)\" " +
"method so it cannot be registered as a REST handler.", e);
continue;
}
Object result;
try
{
result = initMethod.invoke(null, instance.authHandler);
}
catch (IllegalAccessException | InvocationTargetException e)
{
LOG.log(Level.WARNING, "Instantiation of \"" + hndClass + "\" failed.", e);
continue;
}
if (! (result instanceof BaseAdminRestHandler))
{
LOG.log(Level.WARNING,
"Method \"public static initialize(WebAuthHandler)\" of " + hndClass +
" is not returning an \"BaseAdminRestHandler\" instance.");
continue;
}
instance.addHandler((BaseAdminRestHandler) result);
}
}
return instance.getHandlers().length == 0 ? null : instance;
}
}
/**
* Obtain the {@link ContextHandler}, if one was created by the initialization process.
*
* @return the current the {@link ContextHandler}.
*/
public org.eclipse.jetty.server.Handler getHandler()
{
return handler;
}
/**
* Method for handling the requests. Will perform checking, authentication and authorization before
* dispatching the execution to proper method/verb.
*
* @param target
* The target of the request - either a URI or a name.
* @param baseRequest
* The original unwrapped request object.
* @param request
* The request either as the {@link Request} object or a wrapper of that request.
* @param response
* The response as the {@link Response} object or a wrapper of that request.
*/
@Override
public void handle(String target,
Request baseRequest,
HttpServletRequest request,
HttpServletResponse response)
throws IOException, ServletException
{
// quick validation
if (baseRequest.isHandled())
{
return;
}
if (target == null)
{
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
return;
}
// normalize the target to get rid of double //
target = URIUtil.compactPath(target);
if (target.endsWith("/"))
{
target = target.substring(0, target.length() - 1);
}
// process only the know context subtree
if (!target.startsWith(CONTEXT_PREFIX))
{
return;
}
// authentication. The authorization will be done by each AdminRestHandler child:
authHandler.handle(target, baseRequest, request, response);
if (baseRequest.isHandled())
{
return; // authentication done, nothing else to do
}
super.handle(target, baseRequest, request, response);
}
}