This is an automated email from the ASF dual-hosted git repository.

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/felix-dev.git


The following commit(s) were added to refs/heads/master by this push:
     new 93e0dd81b4 FELIX-6626 : Support jakarta servlet registration
93e0dd81b4 is described below

commit 93e0dd81b4c22ec4f72d00f1437c136ce6f30ad7
Author: Carsten Ziegeler <[email protected]>
AuthorDate: Tue Aug 22 14:55:42 2023 +0200

    FELIX-6626 : Support jakarta servlet registration
---
 .../felix/webconsole/AbstractWebConsolePlugin.java |   3 +-
 .../internal/configuration/ConfigManager.java      |   2 +-
 ...vletTracker.java => JakartaServiceTracker.java} | 103 ++++++++++++++++++++-
 .../webconsole/internal/servlet/PluginHolder.java  |   2 +-
 .../felix/webconsole/servlet/AbstractServlet.java  |   3 +
 .../felix/webconsole/spi/SecurityProvider.java     |  84 +++++++++++++++++
 6 files changed, 191 insertions(+), 6 deletions(-)

diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
index 557e943166..9183a28804 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/AbstractWebConsolePlugin.java
@@ -135,6 +135,7 @@ public abstract class AbstractWebConsolePlugin extends 
HttpServlet {
 
     private BundleContext bundleContext;
 
+    @SuppressWarnings("deprecation")
     private static volatile BrandingPlugin brandingPlugin = 
DefaultBrandingPlugin.getInstance();
 
     private static volatile int logLevel;
@@ -670,7 +671,7 @@ public abstract class AbstractWebConsolePlugin extends 
HttpServlet {
      * @param request the HTTP request coming from the user
      * @param pw the writer, where the HTML data is rendered
      */
-    @SuppressWarnings({ "rawtypes" })
+    @SuppressWarnings({ "rawtypes", "deprecation" })
     protected void renderTopNavigation( HttpServletRequest request, 
PrintWriter pw )
     {
         // assume pathInfo to not be null, else this would not be called
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
index f358fce785..5233dc621a 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/configuration/ConfigManager.java
@@ -411,7 +411,7 @@ public class ConfigManager extends SimpleWebConsolePlugin 
implements OsgiManager
         jw.object();
         final ConfigAdminSupport cas = getConfigurationAdminSupport();
         // check for osgi installer plugin
-        @SuppressWarnings("unchecked")
+        @SuppressWarnings({"unchecked", "deprecation" })
         final Map<String, Object> labelMap = (Map<String, Object>) 
request.getAttribute(WebConsoleConstants.ATTR_LABEL_MAP);
         jw.key("jsonsupport").value( 
labelMap.containsKey("osgi-installer-config-printer") ); //$NON-NLS-1$
         final boolean hasMetatype = cas.getMetaTypeSupport() != null;
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletTracker.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServiceTracker.java
similarity index 50%
rename from 
webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletTracker.java
rename to 
webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServiceTracker.java
index 7a9445a311..661b104df3 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServletTracker.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/JakartaServiceTracker.java
@@ -21,29 +21,41 @@ package org.apache.felix.webconsole.internal.servlet;
 
 import java.io.Closeable;
 import java.net.URL;
+import java.util.Dictionary;
+import java.util.Hashtable;
 
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.felix.http.jakartawrappers.HttpServletRequestWrapper;
+import org.apache.felix.http.jakartawrappers.HttpServletResponseWrapper;
 import org.apache.felix.http.javaxwrappers.ServletWrapper;
 import org.apache.felix.webconsole.WebConsoleConstants;
+import org.apache.felix.webconsole.WebConsoleSecurityProvider3;
 import org.apache.felix.webconsole.internal.Util;
 import org.apache.felix.webconsole.internal.servlet.Plugin.ServletPlugin;
 import org.apache.felix.webconsole.servlet.AbstractServlet;
+import org.apache.felix.webconsole.spi.SecurityProvider;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.Filter;
 import org.osgi.framework.InvalidSyntaxException;
 import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
 import org.osgi.util.tracker.ServiceTracker;
 import org.osgi.util.tracker.ServiceTrackerCustomizer;
 
 import jakarta.servlet.Servlet;
 
-public class JakartaServletTracker implements Closeable, 
ServiceTrackerCustomizer<Servlet, JakartaServletTracker.JakartaServletPlugin> {
+public class JakartaServiceTracker implements Closeable, 
ServiceTrackerCustomizer<Servlet, JakartaServiceTracker.JakartaServletPlugin> {
 
     private final ServiceTracker<Servlet, JakartaServletPlugin> servletTracker;
 
     private final PluginHolder pluginHolder;
 
-    public JakartaServletTracker( final PluginHolder pluginHolder, final 
BundleContext context ) {
+    private final JakartaSecurityProviderTracker securityProviderTracker;
+
+    public JakartaServiceTracker( final PluginHolder pluginHolder, final 
BundleContext context ) {
         this.pluginHolder = pluginHolder;
         Filter filter = null;
         try {
@@ -55,11 +67,13 @@ public class JakartaServletTracker implements Closeable, 
ServiceTrackerCustomize
         }
         this.servletTracker = new ServiceTracker<>(context, filter, this);
         servletTracker.open();
+        this.securityProviderTracker = new 
JakartaSecurityProviderTracker(context);
     }
 
     @Override
     public void close() {
-        servletTracker.close();
+        this.servletTracker.close();
+        this.securityProviderTracker.close();
     }
 
     @Override
@@ -115,4 +129,87 @@ public class JakartaServletTracker implements Closeable, 
ServiceTrackerCustomize
             return null;
         }
     }
+
+    public static class JakartaSecurityProviderTracker implements 
ServiceTrackerCustomizer<SecurityProvider, 
ServiceRegistration<WebConsoleSecurityProvider3>> {
+
+        private final ServiceTracker<SecurityProvider, 
ServiceRegistration<WebConsoleSecurityProvider3>> tracker;
+
+        private final BundleContext bundleContext;
+
+        public JakartaSecurityProviderTracker( final BundleContext context ) {
+            this.bundleContext = context;
+            this.tracker = new ServiceTracker<>(context, 
SecurityProvider.class, this);
+            tracker.open();
+        }
+
+        public void close() {
+            tracker.close();
+        }
+
+        @Override
+        public ServiceRegistration<WebConsoleSecurityProvider3> addingService( 
final ServiceReference<SecurityProvider> reference ) {
+            final SecurityProvider provider = 
this.bundleContext.getService(reference);
+            if ( provider != null ) {
+                final JakartaSecurityProvider jakartaSecurityProvider = new 
JakartaSecurityProvider(provider);
+                final Dictionary<String, Object> props = new Hashtable<>();
+                if (reference.getProperty(Constants.SERVICE_RANKING) != null) {
+                    props.put(Constants.SERVICE_RANKING, 
reference.getProperty(Constants.SERVICE_RANKING));
+                }
+                final ServiceRegistration<WebConsoleSecurityProvider3> reg = 
this.bundleContext.registerService(WebConsoleSecurityProvider3.class, 
jakartaSecurityProvider, props);
+                return reg;
+            }
+            return null;
+        }
+
+        @Override
+        public void modifiedService( final ServiceReference<SecurityProvider> 
reference, final ServiceRegistration<WebConsoleSecurityProvider3> service ) {
+            // nothing to do
+        }
+
+        @Override
+        public void removedService( final ServiceReference<SecurityProvider> 
reference, final ServiceRegistration<WebConsoleSecurityProvider3> service ) {
+            this.bundleContext.ungetService(reference);
+            try {
+                service.unregister();
+            } catch ( final IllegalStateException ise ) {
+                // ignore
+            }
+        }
+    }
+
+    public static class JakartaSecurityProvider implements 
WebConsoleSecurityProvider3 {
+            
+        private final SecurityProvider provider;
+
+        public JakartaSecurityProvider(final SecurityProvider provider) {
+            this.provider = provider;
+        }
+
+        @Override
+        public void logout(final HttpServletRequest request, final 
HttpServletResponse response) {
+            
this.provider.logout((jakarta.servlet.http.HttpServletRequest)HttpServletRequestWrapper.getWrapper(request),
 
+                
(jakarta.servlet.http.HttpServletResponse)HttpServletResponseWrapper.getWrapper(response));
+        }
+
+        @Override
+        public boolean authenticate(final HttpServletRequest request, final 
HttpServletResponse response) {
+            final Object user = 
this.provider.authenticate((jakarta.servlet.http.HttpServletRequest)HttpServletRequestWrapper.getWrapper(request),
 
+                
(jakarta.servlet.http.HttpServletResponse)HttpServletResponseWrapper.getWrapper(response));
+            if (user != null) {
+                
request.setAttribute(WebConsoleSecurityProvider3.USER_ATTRIBUTE, user);
+            }
+            return user != null;
+        }
+
+        @Override
+        public Object authenticate(final String username, final String 
password) {
+            // no need to implement
+            return null;
+        }
+
+        @Override
+        public boolean authorize(final Object user, final String role) {
+            return this.provider.authorize(user, role);
+        }
+    }
 }
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
index 86c91930c3..d8e6bcafce 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/internal/servlet/PluginHolder.java
@@ -98,7 +98,7 @@ class PluginHolder implements 
ServiceTrackerCustomizer<Servlet, Plugin> {
     void open() {
         this.servletTracker.open();
         try {
-            this.jakartaTracker = new JakartaServletTracker(this, 
this.getBundleContext());
+            this.jakartaTracker = new JakartaServiceTracker(this, 
this.getBundleContext());
             this.osgiManager.log(LogService.LOG_INFO, "Jakarta Servlet bridge 
enabled");
         } catch ( final Throwable t) {
             // ignore
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java
index 23f2f3f88b..bd7ff0d1d5 100644
--- 
a/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/servlet/AbstractServlet.java
@@ -46,6 +46,9 @@ import jakarta.servlet.http.HttpServletResponse;
  * <p>
  * For html (content) requests, the web console automatically draws the header,
  * footer and navigation.
+ * <p>
+ * Support for Jakarta servlets requires that the Jakarta Servlet API and the
+ * Apache Felix Http Wrappers are available in the runtime.
  *
  * @see ServletConstants#PLUGIN_LABEL
  * @see ServletConstants#PLUGIN_TITLE
diff --git 
a/webconsole/src/main/java/org/apache/felix/webconsole/spi/SecurityProvider.java
 
b/webconsole/src/main/java/org/apache/felix/webconsole/spi/SecurityProvider.java
new file mode 100644
index 0000000000..2546aae0ea
--- /dev/null
+++ 
b/webconsole/src/main/java/org/apache/felix/webconsole/spi/SecurityProvider.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.felix.webconsole.spi;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+
+import org.osgi.annotation.versioning.ConsumerType;
+
+/**
+ * The <code>SecurityProvider</code> is a service interface allowing
+ * to use an external system to authenticate users before granting access to 
the
+ * Web Console.
+ * <p>
+ * Support for Jakarta servlets requires that the Jakarta Servlet API and the
+ * Apache Felix Http Wrappers are available in the runtime.
+ *
+ * @since 1.2.0
+ */
+@ConsumerType
+public interface SecurityProvider {
+
+    /**
+     * Checks whether the authenticated user has the given role permission.
+     *
+     * @param user The object referring to the authenticated user. This is the
+     *      object returned from the {@link #authenticate(HttpServletRequest, 
HttpServletResponse)}
+     *      method and will never be <code>null</code>.
+     * @param role The requested role
+     * @return <code>true</code> if the user is given permission for the given
+     *      role.
+     */
+    boolean authorize( Object user, String role );
+
+    /**
+     * Authenticates the given request or asks the client for credentials.
+     * <p>
+     * Implementations of this method are expected to respect and implement
+     * the semantics of the <code>ServletContextHelper.handleSecurity</code> 
method
+     * as specified in the OSGi HTTP Service specification.
+     * <p>
+     * If this method returns an object (non null) it is assumed the request
+     * provided valid credentials identifying the user as accepted to access
+     * the web console.
+     * <p>
+     * If this method returns {@code null} the request to the web console
+     * is terminated without any more response sent back to the client. That is
+     * the implementation is expected to have informed the client in case of
+     * non-granted access.
+     *
+     * @param request The request object
+     * @param response The response object
+     * @return An object representing the user if the request provided valid 
credentials.
+     *   Otherwise return {@code null}.
+     */
+    Object authenticate( HttpServletRequest request, HttpServletResponse 
response );
+
+    /**
+     * This method will be called by the web console when the user clicks the 
logout button. The security provider
+     * shouldn't invalidate the session, it will be invalidated after this 
method exits.
+     * 
+     * However the security provider must delete any cookies or objects, that 
matters during the authorization process.
+     * 
+     * @param request the request
+     * @param response the response
+     */
+    void logout(HttpServletRequest request, HttpServletResponse response);
+}

Reply via email to