| /* |
| * 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.brooklyn.rest.security.provider; |
| |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.concurrent.atomic.AtomicLong; |
| import java.util.function.Supplier; |
| |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpSession; |
| |
| import org.apache.brooklyn.api.mgmt.ManagementContext; |
| import org.apache.brooklyn.config.StringConfigMap; |
| import org.apache.brooklyn.core.internal.BrooklynProperties; |
| import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; |
| import org.apache.brooklyn.rest.BrooklynWebConfig; |
| import org.apache.brooklyn.util.core.ClassLoaderUtils; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.osgi.framework.Bundle; |
| import org.osgi.framework.BundleContext; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| public class DelegatingSecurityProvider implements SecurityProvider { |
| |
| private static final Logger log = LoggerFactory.getLogger(DelegatingSecurityProvider.class); |
| protected final ManagementContext mgmt; |
| |
| public DelegatingSecurityProvider(ManagementContext mgmt) { |
| this.mgmt = mgmt; |
| } |
| |
| private SecurityProvider delegate; |
| |
| public synchronized SecurityProvider getDelegate() { |
| if (delegate == null) { |
| delegate = loadDelegate(); |
| } |
| return delegate; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private synchronized SecurityProvider loadDelegate() { |
| StringConfigMap brooklynProperties = mgmt.getConfig(); |
| |
| SecurityProvider presetDelegate = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE); |
| if (presetDelegate!=null) { |
| log.trace("Brooklyn security: using pre-set security provider {}", presetDelegate); |
| return presetDelegate; |
| } |
| |
| String className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME); |
| |
| if (delegate != null && BrooklynWebConfig.hasNoSecurityOptions(mgmt.getConfig())) { |
| log.debug("Brooklyn security: {} refusing to change from {}: No security provider set in reloaded properties.", |
| this, delegate); |
| return delegate; |
| } |
| |
| try { |
| String bundle = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_BUNDLE); |
| // use synch block to prevent multiple instances and messages from loading |
| synchronized (DelegatingSecurityProvider.class) { |
| // try again with preset delegate |
| presetDelegate = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE); |
| if (presetDelegate!=null) { |
| log.trace("Brooklyn security: using pre-set security provider, found late - {}", presetDelegate); |
| return presetDelegate; |
| } |
| |
| if (bundle != null) { |
| String bundleVersion = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_BUNDLE_VERSION); |
| log.info("Brooklyn security: using security provider " + className + " from " + bundle + ":" + bundleVersion); |
| BundleContext bundleContext = ((ManagementContextInternal) mgmt).getOsgiManager().get().getFramework().getBundleContext(); |
| delegate = loadProviderFromBundle(mgmt, bundleContext, bundle, bundleVersion, className); |
| saveDelegate(); |
| } else { |
| log.info("Brooklyn security: using security provider " + className); |
| ClassLoaderUtils clu = new ClassLoaderUtils(this, mgmt); |
| Class<? extends SecurityProvider> clazz = (Class<? extends SecurityProvider>) clu.loadClass(className); |
| delegate = createSecurityProviderInstance(mgmt, clazz); |
| saveDelegate(); |
| } |
| } |
| } catch (Exception e) { |
| log.warn("Brooklyn security: unable to instantiate security provider " + className + "; all logins are being disallowed", e); |
| delegate = new BlackholeSecurityProvider(); |
| } |
| return delegate; |
| } |
| |
| private void saveDelegate() { |
| // Deprecated in 0.11.0. Add to release notes and remove in next release. |
| ((BrooklynProperties)mgmt.getConfig()).put(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE, delegate); |
| mgmt.getScratchpad().put(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE, delegate); |
| } |
| |
| public static SecurityProvider loadProviderFromBundle( |
| ManagementContext mgmt, BundleContext bundleContext, |
| String symbolicName, String version, String className) { |
| try { |
| Collection<Bundle> bundles = getMatchingBundles(bundleContext, symbolicName, version); |
| if (bundles.isEmpty()) { |
| throw new IllegalStateException("No bundle " + symbolicName + ":" + version + " found"); |
| } else if (bundles.size() > 1) { |
| log.warn("Brooklyn security: found multiple bundles matching symbolicName " + symbolicName + " and version " + version + |
| " while trying to load security provider " + className + ". Will use first one that loads the class successfully."); |
| } |
| SecurityProvider p = tryLoadClass(mgmt, className, bundles); |
| if (p == null) { |
| throw new ClassNotFoundException("Unable to load class " + className + " from bundle " + symbolicName + ":" + version); |
| } |
| return p; |
| } catch (Exception e) { |
| Exceptions.propagateIfFatal(e); |
| throw new IllegalStateException("Can not load or create security provider " + className + " for bundle " + symbolicName + ":" + version, e); |
| } |
| } |
| |
| private static SecurityProvider tryLoadClass(ManagementContext mgmt, String className, Collection<Bundle> bundles) |
| throws NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { |
| for (Bundle b : bundles) { |
| try { |
| @SuppressWarnings("unchecked") |
| Class<? extends SecurityProvider> securityProviderType = (Class<? extends SecurityProvider>) b.loadClass(className); |
| return DelegatingSecurityProvider.createSecurityProviderInstance(mgmt, securityProviderType); |
| } catch (ClassNotFoundException e) { |
| } |
| } |
| return null; |
| } |
| |
| private static Collection<Bundle> getMatchingBundles(BundleContext bundleContext, final String symbolicName, final String version) { |
| Collection<Bundle> bundles = new ArrayList<>(); |
| for (Bundle b : bundleContext.getBundles()) { |
| if (b.getSymbolicName().equals(symbolicName) && |
| (version == null || b.getVersion().toString().equals(version))) { |
| bundles.add(b); |
| } |
| } |
| return bundles; |
| } |
| |
| public static SecurityProvider createSecurityProviderInstance(ManagementContext mgmt, |
| Class<? extends SecurityProvider> clazz) throws NoSuchMethodException, InstantiationException, |
| IllegalAccessException, InvocationTargetException { |
| Constructor<? extends SecurityProvider> constructor = null; |
| Object delegateO; |
| try { |
| constructor = clazz.getConstructor(ManagementContext.class); |
| } catch (NoSuchMethodException e) { |
| // ignore |
| } |
| if (constructor!=null) { |
| delegateO = constructor.newInstance(mgmt); |
| } else { |
| try { |
| constructor = clazz.getConstructor(); |
| } catch (NoSuchMethodException e) { |
| // ignore |
| } |
| if (constructor!=null) { |
| delegateO = constructor.newInstance(); |
| } else { |
| throw new NoSuchMethodException("Security provider "+clazz+" does not have required no-arg or 1-arg (mgmt) constructor"); |
| } |
| } |
| |
| if (!(delegateO instanceof SecurityProvider)) { |
| // if classloaders get mangled it will be a different CL's SecurityProvider |
| throw new ClassCastException("Delegate is either not a security provider or has an incompatible classloader: "+delegateO); |
| } |
| return (SecurityProvider) delegateO; |
| } |
| |
| @Override |
| public boolean isAuthenticated(HttpSession session) { |
| return getDelegate().isAuthenticated(session); |
| } |
| |
| @Override |
| public boolean authenticate(HttpServletRequest request, Supplier<HttpSession> sessionSupplierOnSuccess, String user, String pass) throws SecurityProviderDeniedAuthentication { |
| boolean authenticated = getDelegate().authenticate(request, sessionSupplierOnSuccess, user, pass); |
| if (log.isTraceEnabled() && authenticated) { |
| log.trace("User {} authenticated with provider {}", user, getDelegate()); |
| } else if (!authenticated && log.isDebugEnabled()) { |
| log.debug("Failed authentication for user {} with provider {}", user, getDelegate()); |
| } |
| return authenticated; |
| } |
| |
| @Override |
| public boolean logout(HttpSession session) { |
| return getDelegate().logout(session); |
| } |
| |
| @Override |
| public boolean requiresUserPass() { |
| return getDelegate().requiresUserPass(); |
| } |
| |
| @Override |
| public String toString() { |
| return super.toString()+"["+getDelegate()+"]"; |
| } |
| |
| } |