/* * Copyright 2016 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed 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.keycloak.adapters.osgi; import org.eclipse.jetty.security.ConstraintMapping; import org.eclipse.jetty.util.security.Constraint; import org.jboss.logging.Logger; import org.ops4j.pax.web.service.WebContainer; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.service.http.HttpContext; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; import java.net.URL; import java.security.SecureRandom; import java.util.Arrays; import java.util.List; /** * Integration with pax-web, which allows to inject custom jetty-web.xml configuration from current bundle classpath into {@link WebContainer} * and allows to inject custom security constraint for securing resources by Keycloak. * * <p>It assumes that pax-web {@link WebContainer} is used as implementation of OSGI {@link org.osgi.service.http.HttpService}, which * is true in karaf/fuse environment</p> * * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a> */ public class PaxWebIntegrationService { protected static final Logger log = Logger.getLogger(PaxWebIntegrationService.class); private BundleContext bundleContext; private String jettyWebXmlLocation; private List<ConstraintMapping> constraintMappings; // Using jetty constraint mapping just because of compatibility with other fuse services private ServiceTracker webContainerTracker; private HttpContext httpContext; public BundleContext getBundleContext() { return bundleContext; } public void setBundleContext(BundleContext bundleContext) { this.bundleContext = bundleContext; } public String getJettyWebXmlLocation() { return jettyWebXmlLocation; } public void setJettyWebXmlLocation(String jettyWebXmlLocation) { this.jettyWebXmlLocation = jettyWebXmlLocation; } public List<ConstraintMapping> getConstraintMappings() { return constraintMappings; } public void setConstraintMappings(List<ConstraintMapping> constraintMappings) { this.constraintMappings = constraintMappings; } protected ServiceTracker getWebContainerTracker() { return webContainerTracker; } protected HttpContext getHttpContext() { return httpContext; } public void start() { ServiceTrackerCustomizer trackerCustomizer = new ServiceTrackerCustomizer() { @Override public Object addingService(ServiceReference reference) { return addingWebContainerCallback(reference); } @Override public void modifiedService(ServiceReference reference, Object service) { } @Override public void removedService(ServiceReference reference, Object service) { removingWebContainerCallback(reference); } }; webContainerTracker = new ServiceTracker(bundleContext, WebContainer.class.getName(), trackerCustomizer); webContainerTracker.open(); } public void stop() { webContainerTracker.remove(webContainerTracker.getServiceReference()); } protected WebContainer addingWebContainerCallback(ServiceReference webContainerServiceReference) { WebContainer service = (WebContainer) bundleContext.getService(webContainerServiceReference); httpContext = service.createDefaultHttpContext(); addJettyWebXml(service); if (constraintMappings == null) { throw new IllegalStateException("constraintMappings was null!"); } for (ConstraintMapping constraintMapping : constraintMappings) { addConstraintMapping(service, constraintMapping); } service.registerLoginConfig("BASIC", "does-not-matter", null, null, httpContext); return service; } protected void addJettyWebXml(WebContainer service) { String jettyWebXmlLoc; if (this.jettyWebXmlLocation == null) { jettyWebXmlLoc = "/WEB-INF/jetty-web.xml"; } else { jettyWebXmlLoc = this.jettyWebXmlLocation; } URL jettyWebXml = bundleContext.getBundle().getResource(jettyWebXmlLoc); if (jettyWebXml != null) { log.debug("Found jetty-web XML configuration on bundle classpath on " + jettyWebXmlLoc); service.registerJettyWebXml(jettyWebXml, httpContext); } else { log.debug("Not found jetty-web XML configuration on bundle classpath on " + jettyWebXmlLoc); } } protected void addConstraintMapping(WebContainer service, ConstraintMapping constraintMapping) { Constraint constraint = constraintMapping.getConstraint(); String[] roles = constraint.getRoles(); // name property is unavailable on constraint object :/ String name = "Constraint-" + new SecureRandom().nextInt(Integer.MAX_VALUE); int dataConstraint = constraint.getDataConstraint(); String dataConstraintStr; switch (dataConstraint) { case Constraint.DC_UNSET: dataConstraintStr = null; break; case Constraint.DC_NONE: dataConstraintStr = "NONE"; break; case Constraint.DC_CONFIDENTIAL: dataConstraintStr = "CONFIDENTIAL"; break; case Constraint.DC_INTEGRAL: dataConstraintStr = "INTEGRAL"; break; default: log.warnv("Unknown data constraint: " + dataConstraint); dataConstraintStr = "CONFIDENTIAL"; } List<String> rolesList = Arrays.asList(roles); log.debug("Adding security constraint name=" + name + ", url=" + constraintMapping.getPathSpec() + ", dataConstraint=" + dataConstraintStr + ", canAuthenticate=" + constraint.getAuthenticate() + ", roles=" + rolesList); service.registerConstraintMapping(name, constraintMapping.getPathSpec(), null, dataConstraintStr, constraint.getAuthenticate(), rolesList, httpContext); } protected void removingWebContainerCallback(ServiceReference serviceReference) { WebContainer service = (WebContainer)bundleContext.getService(serviceReference); if (service != null) { service.unregisterLoginConfig(httpContext); service.unregisterConstraintMapping(httpContext); } } }