FELIX-3705 import new JAAS OSGi support (contributed by Chetan Mehrotra, thank you very much)
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1399574 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/jaas/pom.xml b/jaas/pom.xml
new file mode 100644
index 0000000..8e5072e
--- /dev/null
+++ b/jaas/pom.xml
@@ -0,0 +1,230 @@
+<!--
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>felix-parent</artifactId>
+ <version>2.1</version>
+ <relativePath>../pom/pom.xml</relativePath>
+ </parent>
+
+ <artifactId>org.apache.felix.jaas</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <packaging>bundle</packaging>
+
+ <name>Apache Felix JAAS Support</name>
+ <description />
+
+ <scm>
+ <connection>scm:svn:http://svn.apache.org/repos/asf/felix/trunk/jaas</connection>
+ <developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/trunk/jaas</developerConnection>
+ <url>http://svn.apache.org/repos/asf/felix/jaas</url>
+ </scm>
+
+ <properties>
+ </properties>
+
+ <build>
+ <plugins>
+ <!--
+ We do not use DS but the SCR plugin is used to generate the
+ metatype.xml from the SCR annotations
+ -->
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-scr-plugin</artifactId>
+ <version>1.7.4</version>
+ <executions>
+ <execution>
+ <id>generate-scr-scrdescriptor</id>
+ <goals>
+ <goal>scr</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>2.5.1</version>
+ <!-- We use the new JDK 1.6 ConfigurationSpi API so can set the version to 1.6-->
+ <configuration>
+ <source>1.6</source>
+ <target>1.6</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>2.3.7</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>
+ ${project.artifactId}
+ </Bundle-SymbolicName>
+ <Bundle-Vendor>
+ The Apache Software Foundation
+ </Bundle-Vendor>
+ <Bundle-Activator>
+ org.apache.felix.jaas.internal.Activator
+ </Bundle-Activator>
+ <_removeheaders>
+ Embed-Dependency,Private-Package,Include-Resource
+ </_removeheaders>
+ </instructions>
+ </configuration>
+ </plugin>
+ <!-- Create jar which can be put in boot classpath to enable use of
+ ProxyLoginModule in normal JAAS mode of authentication -->
+ <plugin>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>boot-library</id>
+ <goals><goal>jar</goal></goals>
+ <phase>package</phase>
+ <configuration>
+ <classifier>boot</classifier>
+ <includes>
+ <include>org/apache/felix/jaas/boot/*.class</include>
+ <include>META-INF/NOTICE</include>
+ <include>META-INF/DEPENDENCIES</include>
+ <include>META-INF/LICENSE</include>
+ </includes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- Required for pax exam-->
+ <plugin>
+ <groupId>org.apache.servicemix.tooling</groupId>
+ <artifactId>depends-maven-plugin</artifactId>
+ <version>1.2</version>
+ <executions>
+ <execution>
+ <id>generate-depends-file</id>
+ <goals>
+ <goal>generate-depends-file</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ <version>4.2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>4.2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>biz.aQute</groupId>
+ <artifactId>bndlib</artifactId>
+ <version>1.50.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.scr.annotations</artifactId>
+ <version>1.6.0</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.3</version>
+ </dependency>
+
+ <!-- testing -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ <version>4.10</version>
+ </dependency>
+ </dependencies>
+
+ <profiles>
+ <!--
+ copy the package such that IDEs may easily use it without
+ setting the system property
+ -->
+ <profile>
+ <id>ide</id>
+ <build>
+ <plugins>
+ <plugin>
+ <artifactId>maven-antrun-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>scr-file-create</id>
+ <phase>package</phase>
+ <goals>
+ <goal>run</goal>
+ </goals>
+ <configuration>
+ <target>
+ <copy file="${project.build.directory}/${project.build.finalName}.jar"
+ tofile="${project.build.directory}/jaas.jar" />
+ <copy file="${project.build.directory}/${project.build.finalName}-boot.jar"
+ tofile="${project.build.directory}/jaas-boot.jar" />
+ </target>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+
+ <profile>
+ <id>felix</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ </activation>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.framework</artifactId>
+ <version>4.0.2</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>equinox</id>
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse</groupId>
+ <artifactId>osgi</artifactId>
+ <version>3.7.1.R37x_v20110808-1106</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </profile>
+ </profiles>
+</project>
diff --git a/jaas/src/main/java/org/apache/felix/jaas/LoginContextFactory.java b/jaas/src/main/java/org/apache/felix/jaas/LoginContextFactory.java
new file mode 100644
index 0000000..59e1143
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/LoginContextFactory.java
@@ -0,0 +1,33 @@
+/*
+ * 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.jaas;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+
+public interface LoginContextFactory
+{
+
+ LoginContext createLoginContext(String realm, Subject subject, CallbackHandler handler) throws LoginException;
+
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/LoginModuleFactory.java b/jaas/src/main/java/org/apache/felix/jaas/LoginModuleFactory.java
new file mode 100644
index 0000000..a9edf97
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/LoginModuleFactory.java
@@ -0,0 +1,38 @@
+/*
+ * 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.jaas;
+
+import javax.security.auth.spi.LoginModule;
+
+
+/**
+ * User: chetanm
+ * Date: 7/9/12
+ * Time: 5:43 PM
+ */
+public interface LoginModuleFactory
+{
+
+ String JAAS_CONTROL_FLAG = "jaas.controlFlag";
+ String JAAS_REALM_NAME = "jaas.realmName";
+
+ LoginModule createLoginModule();
+
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/boot/ProxyLoginModule.java b/jaas/src/main/java/org/apache/felix/jaas/boot/ProxyLoginModule.java
new file mode 100644
index 0000000..c706c8f
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/boot/ProxyLoginModule.java
@@ -0,0 +1,78 @@
+/*
+ * 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.jaas.boot;
+
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+
+public class ProxyLoginModule implements LoginModule
+{
+ public static final String PROP_LOGIN_MODULE_FACTORY = "org.apache.felix.jaas.LoginModuleFactory";
+
+ private LoginModule delegate;
+
+ public void initialize(Subject subject, CallbackHandler callbackHandler,
+ Map<String, ?> sharedState, Map<String, ?> options)
+ {
+ BootLoginModuleFactory factory = (BootLoginModuleFactory) options.get(PROP_LOGIN_MODULE_FACTORY);
+ if (factory == null)
+ {
+ throw new IllegalStateException("Specify LoginModuleFactory through ["
+ + PROP_LOGIN_MODULE_FACTORY
+ + "] property as part of configuration options");
+ }
+ delegate = factory.createLoginModule();
+ delegate.initialize(subject, callbackHandler, sharedState, options);
+ }
+
+ public boolean login() throws LoginException
+ {
+ return delegate.login();
+ }
+
+ public boolean commit() throws LoginException
+ {
+ return delegate.commit();
+ }
+
+ public boolean abort() throws LoginException
+ {
+ return delegate.abort();
+ }
+
+ public boolean logout() throws LoginException
+ {
+ return delegate.logout();
+ }
+
+ /**
+ * Factory interface to create LoginModule instance. This is exactly same as
+ * LoginModuleFactory. This is done to keep the boot package self sufficient
+ */
+ public static interface BootLoginModuleFactory
+ {
+ LoginModule createLoginModule();
+ }
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/boot/package-info.java b/jaas/src/main/java/org/apache/felix/jaas/boot/package-info.java
new file mode 100644
index 0000000..c39e890
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/boot/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides support for performing JAAS based authentication in OSGi
+ *
+ * @version 1.0
+ */
+@Version("1.0")
+@Export(optional = "provide:=true")
+package org.apache.felix.jaas.boot;
+
+import aQute.bnd.annotation.Version;
+import aQute.bnd.annotation.Export;
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/Activator.java b/jaas/src/main/java/org/apache/felix/jaas/internal/Activator.java
new file mode 100644
index 0000000..b014e72
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/Activator.java
@@ -0,0 +1,67 @@
+/*
+ * 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.jaas.internal;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+
+public class Activator implements BundleActivator
+{
+
+ private BundleLoginModuleCreator loginModuleCreator;
+ private JaasConfigFactory jaasConfigFactory;
+ private ConfigSpiOsgi configSpi;
+ private JaasWebConsolePlugin webConsolePlugin;
+ private Logger logger;
+
+ @Override
+ public void start(BundleContext context) throws Exception
+ {
+ logger = new Logger(context);
+ loginModuleCreator = new BundleLoginModuleCreator(context, logger);
+ jaasConfigFactory = new JaasConfigFactory(context, loginModuleCreator, logger);
+ configSpi = new ConfigSpiOsgi(context, logger);
+ webConsolePlugin = new JaasWebConsolePlugin(context, configSpi,
+ loginModuleCreator);
+
+ logger.open();
+ loginModuleCreator.open();
+ configSpi.open();
+ }
+
+ @Override
+ public void stop(BundleContext context) throws Exception
+ {
+ if (loginModuleCreator != null)
+ {
+ loginModuleCreator.close();
+ }
+
+ if (configSpi != null)
+ {
+ configSpi.close();
+ }
+
+ if (logger != null)
+ {
+ logger.close();
+ }
+ }
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/BundleLoginModuleCreator.java b/jaas/src/main/java/org/apache/felix/jaas/internal/BundleLoginModuleCreator.java
new file mode 100644
index 0000000..4926049
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/BundleLoginModuleCreator.java
@@ -0,0 +1,212 @@
+/*
+ * 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.jaas.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.security.auth.spi.LoginModule;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.BundleTracker;
+
+class BundleLoginModuleCreator extends BundleTracker implements LoginModuleCreator
+{
+
+ private static final String JAAS_MODULE_CLASS = "Jaas-ModuleClass";
+
+ private final Map<String, LoginModuleInfo> loginModuleInfo = new ConcurrentHashMap<String, LoginModuleInfo>();
+
+ private final Logger log;
+
+ public BundleLoginModuleCreator(BundleContext context, Logger log)
+ {
+ super(context, Bundle.ACTIVE, null /* customizer */);
+ this.log = log;
+ }
+
+ public LoginModule newInstance(String className)
+ {
+ LoginModuleInfo lmInfo = loginModuleInfo.get(className);
+
+ //TODO Rethink about exception handling. Probably introduce custom exception classes
+ if (lmInfo == null)
+ {
+ throw new AssertionError("No bundle exists to create LoginModule from "
+ + className);
+ }
+
+ try
+ {
+ return lmInfo.newInstance();
+ }
+ catch (IllegalAccessException e)
+ {
+ throw new RuntimeException("Error occurred while creating LoginModule for "
+ + className, e);
+ }
+ catch (InstantiationException e)
+ {
+ throw new RuntimeException("Error occurred while creating LoginModule for "
+ + className, e);
+ }
+ }
+
+ public Map<Bundle, Set<String>> getBundleToLoginModuleMapping()
+ {
+ //With R5 we could have used Map<Bundle, T> BundleTracker.getTracked()
+ //Determine the bundle -> login module classes map
+ Map<Bundle, Set<String>> bundleMap = new HashMap<Bundle, Set<String>>();
+ for (LoginModuleInfo e : loginModuleInfo.values())
+ {
+ Bundle b = e.getBundle();
+ @SuppressWarnings("unchecked")
+ Set<String> classNames = (Set<String>) getObject(b);
+
+ if (classNames == null)
+ {
+ continue;
+ }
+ bundleMap.put(b, classNames);
+ }
+ return bundleMap;
+ }
+
+ // ---------- BundleTracker integration ----------------------------------------------
+
+ @Override
+ public Object addingBundle(Bundle bundle, BundleEvent event)
+ {
+ if (providesLoginModule(bundle))
+ {
+ return registerBundle(bundle);
+ }
+ return null;
+ }
+
+ @Override
+ public void removedBundle(Bundle bundle, BundleEvent event, Object object)
+ {
+ @SuppressWarnings("unchecked")
+ Set<String> classNames = (Set<String>) object;
+
+ for (String className : classNames)
+ {
+ loginModuleInfo.remove(className);
+ }
+ }
+
+ private boolean providesLoginModule(Bundle bundle)
+ {
+ return bundle.getHeaders().get(JAAS_MODULE_CLASS) != null;
+ }
+
+ private Set<String> registerBundle(Bundle bundle)
+ {
+ Set<String> classNames = Util.parseHeader((String) bundle.getHeaders().get(
+ JAAS_MODULE_CLASS));
+ for (String className : classNames)
+ {
+ LoginModuleInfo bi = new LoginModuleInfo(className, bundle, log);
+ if (bi.isValid())
+ {
+
+ //Duplicate registration check
+ if (loginModuleInfo.containsKey(className))
+ {
+ LoginModuleInfo existingInfo = loginModuleInfo.get(className);
+ String msg = String.format(
+ "LoginModule class %s is already registered with Bundle %s. Entry "
+ + "from bundle %s would be ignored", className,
+ existingInfo.getBundle(), bundle);
+ log.log(LogService.LOG_WARNING, msg);
+ continue;
+ }
+
+ loginModuleInfo.put(className, bi);
+ log.log(LogService.LOG_INFO, "Registering LoginModule class ["
+ + className + "] from Bundle" + bundle);
+ }
+ else
+ {
+ log.log(LogService.LOG_WARNING,
+ "Could not load LoginModule class " + bi.getClassName()
+ + " from bundle " + bundle);
+ }
+ }
+ return classNames;
+ }
+
+ static final class LoginModuleInfo
+ {
+ private final String className;
+ private final Bundle bundle;
+ private final Class<LoginModule> clazz;
+
+ @SuppressWarnings("unchecked")
+ public LoginModuleInfo(String className, Bundle bundle, Logger log)
+ {
+ this.className = className;
+ this.bundle = bundle;
+
+ Class<LoginModule> clazz = null;
+ try
+ {
+ clazz = bundle.loadClass(className);
+ }
+ catch (ClassNotFoundException e)
+ {
+ log.log(LogService.LOG_WARNING, "Error loading class [" + className
+ + "] from bundle " + bundle, e);
+ }
+ this.clazz = clazz;
+ }
+
+ public LoginModule newInstance() throws IllegalAccessException,
+ InstantiationException
+ {
+ if (clazz == null)
+ {
+ throw new IllegalStateException("LoginModule class not initialized");
+ }
+ return clazz.newInstance();
+ }
+
+ public boolean isValid()
+ {
+ return clazz != null;
+ }
+
+ public String getClassName()
+ {
+ return className;
+ }
+
+ public Bundle getBundle()
+ {
+ return bundle;
+ }
+ }
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/ConfigLoginModuleProvider.java b/jaas/src/main/java/org/apache/felix/jaas/internal/ConfigLoginModuleProvider.java
new file mode 100644
index 0000000..445712c
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/ConfigLoginModuleProvider.java
@@ -0,0 +1,88 @@
+/*
+ * 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.jaas.internal;
+
+import static javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
+
+import java.util.Collections;
+import java.util.Map;
+
+import javax.security.auth.spi.LoginModule;
+
+
+final class ConfigLoginModuleProvider implements LoginModuleProvider
+{
+ private final Map options;
+ private final LoginModuleControlFlag controlFlag;
+ private final int ranking;
+ private final String realmName;
+ private final String className;
+ private final LoginModuleCreator moduleCreator;
+
+ @SuppressWarnings("unchecked")
+ ConfigLoginModuleProvider(String realmName, String className, Map options, LoginModuleControlFlag controlFlag, int order, LoginModuleCreator moduleCreator)
+ {
+ this.options = Collections.unmodifiableMap(options);
+ this.controlFlag = controlFlag;
+ this.ranking = order;
+ this.realmName = realmName;
+ this.className = className;
+ this.moduleCreator = moduleCreator;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Map<String, ?> options()
+ {
+ return options;
+ }
+
+ public LoginModuleControlFlag getControlFlag()
+ {
+ return controlFlag;
+ }
+
+ public int ranking()
+ {
+ return ranking;
+ }
+
+ public String realmName()
+ {
+ return realmName;
+ }
+
+ public String getClassName()
+ {
+ return className;
+ }
+
+ public LoginModule createLoginModule()
+ {
+ return moduleCreator.newInstance(className);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "ConfigLoginModuleProvider{" + "flag=" + controlFlag + ", ranking="
+ + ranking + ", realmName='" + realmName + '\'' + ", className='" + className
+ + '\'' + '}';
+ }
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/ConfigSpiOsgi.java b/jaas/src/main/java/org/apache/felix/jaas/internal/ConfigSpiOsgi.java
new file mode 100644
index 0000000..175046f
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/ConfigSpiOsgi.java
@@ -0,0 +1,591 @@
+/*
+ * 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.jaas.internal;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.Provider;
+import java.security.Security;
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.*;
+
+import org.apache.felix.jaas.LoginContextFactory;
+import org.apache.felix.jaas.LoginModuleFactory;
+import org.apache.felix.jaas.boot.ProxyLoginModule;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.ConfigurationPolicy;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyOption;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
+
+@Component(label = "%jaas.spi.name", description = "%jaas.spi.description", metatype = true, ds = false, name = "org.apache.felix.jaas.ConfigurationSpi", policy = ConfigurationPolicy.REQUIRE)
+public class ConfigSpiOsgi extends ConfigurationSpi implements ManagedService, ServiceTrackerCustomizer, LoginContextFactory
+{
+ /**
+ * Name of the algorithm to use to fetch JAAS Config
+ */
+ public static final String JAAS_CONFIG_ALGO_NAME = "JavaLoginConfig";
+
+ public static final String SERVICE_PID = "org.apache.felix.jaas.ConfigurationSpi";
+
+ private static enum GlobalConfigurationPolicy
+ {
+ DEFAULT, REPLACE, PROXY
+ }
+
+ private Map<String, Realm> configs = Collections.emptyMap();
+
+ private final Logger log;
+
+ @Property
+ private static final String JAAS_DEFAULT_REALM_NAME = "jaas.defaultRealmName";
+ private String defaultRealmName;
+
+ private static final String DEFAULT_CONFIG_PROVIDER_NAME = "FelixJaasProvider";
+ @Property(value = DEFAULT_CONFIG_PROVIDER_NAME)
+ private static final String JAAS_CONFIG_PROVIDER_NAME = "jaas.configProviderName";
+
+ @Property(value = "default", options = {
+ @PropertyOption(name = "default", value = "%jaas.configPolicy.default"),
+ @PropertyOption(name = "replace", value = "%jaas.configPolicy.replace"),
+ @PropertyOption(name = "proxy", value = "%jaas.configPolicy.proxy") })
+ static final String JAAS_CONFIG_POLICY = "jaas.globalConfigPolicy";
+
+ private final Configuration osgiConfig = new OsgiConfiguration();
+
+ private final Configuration originalConfig;
+
+ private final Configuration proxyConfig;
+
+ private volatile GlobalConfigurationPolicy globalConfigPolicy = GlobalConfigurationPolicy.DEFAULT;
+
+ private final Map<ServiceReference, LoginModuleProvider> providerMap = new ConcurrentHashMap<ServiceReference, LoginModuleProvider>();
+
+ private volatile String jaasConfigProviderName;
+
+ private final Object lock = new Object();
+
+ private final BundleContext context;
+
+ private final ServiceTracker tracker;
+
+ private ServiceRegistration spiReg;
+
+ public ConfigSpiOsgi(BundleContext context, Logger log)
+ {
+ this.context = context;
+ this.log = log;
+ this.tracker = new ServiceTracker(context, LoginModuleFactory.class.getName(),
+ this);
+
+ Properties props = new Properties();
+ props.setProperty(Constants.SERVICE_VENDOR, "Apache Software Foundation");
+ props.setProperty(Constants.SERVICE_PID, SERVICE_PID);
+
+ this.context.registerService(ManagedService.class.getName(), this, props);
+
+ //TODO Should this registration be made conditional i.e. service is only registered
+ //only if there active LoginModules present
+ this.context.registerService(LoginContextFactory.class.getName(), this, new Properties());
+
+ this.originalConfig = getGlobalConfiguration();
+ this.proxyConfig = new DelegatingConfiguration(osgiConfig, originalConfig);
+ }
+
+ @Override
+ public LoginContext createLoginContext(String realm, Subject subject,
+ CallbackHandler handler) throws LoginException
+ {
+ if (realm == null)
+ {
+ realm = defaultRealmName;
+ }
+
+ final Thread currentThread = Thread.currentThread();
+ final ClassLoader cl = currentThread.getContextClassLoader();
+ try
+ {
+ currentThread.setContextClassLoader(ProxyLoginModule.class.getClassLoader());
+ Configuration config = Configuration.getInstance("JavaLoginConfig", null,
+ jaasConfigProviderName);
+ return new LoginContext(realm, subject, handler, config);
+ }
+ catch (NoSuchProviderException e)
+ {
+ throw new LoginException(e.getMessage());
+ }
+ catch (NoSuchAlgorithmException e)
+ {
+ throw new LoginException(e.getMessage());
+ }
+ finally
+ {
+ currentThread.setContextClassLoader(cl);
+ }
+ }
+
+ @Override
+ protected AppConfigurationEntry[] engineGetAppConfigurationEntry(String name)
+ {
+ Realm realm = configs.get(name);
+ if (realm == null)
+ {
+ log.log(LogService.LOG_WARNING, "No JAAS module configured for realm " + name);
+ return null;
+ }
+
+ return realm.engineGetAppConfigurationEntry();
+ }
+
+ Map<String, Realm> getAllConfiguration()
+ {
+ return configs;
+ }
+
+ private void recreateConfigs()
+ {
+ Map<String, Realm> realmToConfigMap = new HashMap<String, Realm>();
+ for (LoginModuleProvider lmp : providerMap.values())
+ {
+ String realmName = lmp.realmName();
+ if (realmName == null)
+ {
+ realmName = defaultRealmName;
+ }
+
+ Realm realm = realmToConfigMap.get(realmName);
+ if (realm == null)
+ {
+ realm = new Realm(realmName);
+ realmToConfigMap.put(realmName, realm);
+ }
+
+ realm.add(new AppConfigurationHolder(lmp));
+ }
+
+ for (Realm realm : realmToConfigMap.values())
+ {
+ realm.afterPropertiesSet();
+ }
+
+ //We also register the Spi with OSGI SR if any configuration is available
+ //This would allow any client component to determine when it should start
+ //and use the config
+ if (!realmToConfigMap.isEmpty() && spiReg == null)
+ {
+ Properties props = new Properties();
+ props.setProperty("providerName", "felix");
+
+ synchronized (lock)
+ {
+ spiReg = context.registerService(ConfigurationSpi.class.getName(), this,
+ props);
+ }
+ }
+
+ synchronized (lock)
+ {
+ this.configs = Collections.unmodifiableMap(realmToConfigMap);
+ }
+ }
+
+ //--------------LifeCycle methods -------------------------------------
+
+ void open()
+ {
+ this.tracker.open();
+ this.configs = Collections.emptyMap();
+ }
+
+ void close()
+ {
+ this.tracker.close();
+ deregisterProvider(jaasConfigProviderName);
+
+ synchronized (lock)
+ {
+ providerMap.clear();
+ configs = null; //Cannot call clear as its an unmodifiable map
+ }
+
+ if (globalConfigPolicy != GlobalConfigurationPolicy.DEFAULT)
+ {
+ restoreOriginalConfiguration();
+ }
+ }
+
+ // --------------Config handling ----------------------------------------
+
+ @Override
+ public synchronized void updated(Dictionary properties) throws ConfigurationException
+ {
+ //TODO Do not know but for fresh install it is null
+ if (properties == null)
+ {
+ return;
+ }
+ String newDefaultRealmName = Util.toString(
+ properties.get(JAAS_DEFAULT_REALM_NAME), null);
+ if (newDefaultRealmName == null)
+ {
+ throw new IllegalArgumentException(
+ "Default JAAS realm name must be specified");
+ }
+
+ if (!newDefaultRealmName.equals(defaultRealmName))
+ {
+ defaultRealmName = newDefaultRealmName;
+ recreateConfigs();
+ }
+
+ String newProviderName = Util.toString(properties.get(JAAS_CONFIG_PROVIDER_NAME),
+ DEFAULT_CONFIG_PROVIDER_NAME);
+
+ deregisterProvider(jaasConfigProviderName);
+ registerProvider(newProviderName);
+ jaasConfigProviderName = newProviderName;
+
+ manageGlobalConfiguration(properties);
+ }
+
+ private void manageGlobalConfiguration(Dictionary props)
+ {
+ String configPolicy = Util.toString(props.get(JAAS_CONFIG_POLICY),
+ GlobalConfigurationPolicy.DEFAULT.name());
+ configPolicy = Util.trimToNull(configPolicy);
+
+ GlobalConfigurationPolicy policy = GlobalConfigurationPolicy.DEFAULT;
+ if (configPolicy != null)
+ {
+ policy = GlobalConfigurationPolicy.valueOf(configPolicy.toUpperCase());
+ }
+
+ this.globalConfigPolicy = policy;
+
+ if (policy == GlobalConfigurationPolicy.REPLACE)
+ {
+ Configuration.setConfiguration(osgiConfig);
+ log.log(LogService.LOG_INFO,
+ "Replacing the global JAAS configuration with OSGi based configuration");
+ }
+ else if (policy == GlobalConfigurationPolicy.PROXY)
+ {
+ Configuration.setConfiguration(proxyConfig);
+ log.log(
+ LogService.LOG_INFO,
+ "Replacing the global JAAS configuration with OSGi based proxy configuration. "
+ + "It would look first in the OSGi based configuration and if not found would use the default global "
+ + "configuration");
+ }
+ else if (policy == GlobalConfigurationPolicy.DEFAULT)
+ {
+ restoreOriginalConfiguration();
+ }
+ }
+
+ private void restoreOriginalConfiguration()
+ {
+ if (originalConfig == null)
+ {
+ return;
+ }
+
+ Configuration current = Configuration.getConfiguration();
+ if (current != originalConfig)
+ {
+ Configuration.setConfiguration(originalConfig);
+ }
+ }
+
+ // --------------JAAS/JCA/Security ----------------------------------------
+
+ private void registerProvider(String providerName)
+ {
+ Security.addProvider(new OSGiProvider(providerName));
+ log.log(LogService.LOG_INFO, "Registered provider " + providerName
+ + " for managing JAAS config with type " + JAAS_CONFIG_ALGO_NAME);
+ }
+
+ private void deregisterProvider(String providerName)
+ {
+ Security.removeProvider(providerName);
+ log.log(LogService.LOG_INFO, "Removed provider " + providerName + " type "
+ + JAAS_CONFIG_ALGO_NAME + " from Security providers list");
+ }
+
+ // ---------- ServiceTracker ----------------------------------------------
+
+ @Override
+ public Object addingService(ServiceReference reference)
+ {
+ LoginModuleFactory lmf = (LoginModuleFactory) context.getService(reference);
+ registerFactory(reference, lmf);
+ recreateConfigs();
+ return lmf;
+ }
+
+ @Override
+ public void modifiedService(ServiceReference reference, Object service)
+ {
+ recreateConfigs();
+ }
+
+ @Override
+ public void removedService(ServiceReference reference, Object service)
+ {
+ deregisterFactory(reference);
+ recreateConfigs();
+ context.ungetService(reference);
+ }
+
+ private void deregisterFactory(ServiceReference ref)
+ {
+ LoginModuleProvider lmp = providerMap.remove(ref);
+ if (lmp != null)
+ {
+ log.log(LogService.LOG_INFO, "Deregistering LoginModuleFactory " + lmp);
+ }
+ }
+
+ private void registerFactory(ServiceReference ref, LoginModuleFactory lmf)
+ {
+ LoginModuleProvider lmfExt;
+ if (lmf instanceof LoginModuleProvider)
+ {
+ lmfExt = (LoginModuleProvider) lmf;
+ }
+ else
+ {
+ lmfExt = new OsgiLoginModuleProvider(ref, lmf);
+ }
+ log.log(LogService.LOG_INFO, "Registering LoginModuleFactory " + lmf);
+ providerMap.put(ref, lmfExt);
+ }
+
+ private static Configuration getGlobalConfiguration()
+ {
+ try
+ {
+ return Configuration.getConfiguration();
+ }
+ catch (Exception e)
+ {
+ // means no JAAS configuration file OR no permission to read it
+ }
+ return null;
+ }
+
+ private class OSGiProvider extends Provider
+ {
+ public static final String TYPE_CONFIGURATION = "Configuration";
+
+ OSGiProvider(String providerName)
+ {
+ super(providerName, 1.0, "OSGi based provider for Jaas configuration");
+ }
+
+ @Override
+ public synchronized Service getService(String type, String algorithm)
+ {
+ if (TYPE_CONFIGURATION.equals(type)
+ && JAAS_CONFIG_ALGO_NAME.equals(algorithm))
+ {
+ return new ConfigurationService(this);
+ }
+ return super.getService(type, algorithm);
+ }
+ }
+
+ private class ConfigurationService extends Provider.Service
+ {
+
+ public ConfigurationService(Provider provider)
+ {
+ super(provider, OSGiProvider.TYPE_CONFIGURATION, //the type of this service
+ JAAS_CONFIG_ALGO_NAME, //the algorithm name
+ ConfigSpiOsgi.class.getName(), //the name of the class implementing this service
+ Collections.<String> emptyList(), //List of aliases or null if algorithm has no aliases
+ Collections.<String, String> emptyMap()); //Map of attributes or null if this implementation
+ }
+
+ @Override
+ public Object newInstance(Object constructorParameter)
+ throws NoSuchAlgorithmException
+ {
+ //constructorParameter is the one which is passed as Configuration.Parameters params
+ //for now we do not make use of that
+ return ConfigSpiOsgi.this;
+ }
+ }
+
+ //---------------------------- Global Configuration Handling
+
+ private class OsgiConfiguration extends Configuration
+ {
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name)
+ {
+ return ConfigSpiOsgi.this.engineGetAppConfigurationEntry(name);
+ }
+ }
+
+ private class DelegatingConfiguration extends Configuration
+ {
+ private final Configuration primary;
+ private final Configuration secondary;
+
+ private DelegatingConfiguration(Configuration primary, Configuration secondary)
+ {
+ this.primary = primary;
+ this.secondary = secondary;
+ }
+
+ @Override
+ public AppConfigurationEntry[] getAppConfigurationEntry(String name)
+ {
+ // check if jaas-loginModule or fallback is configured
+ AppConfigurationEntry[] result = null;
+ try
+ {
+ result = primary.getAppConfigurationEntry(name);
+ }
+ catch (Exception e)
+ {
+ // means no JAAS configuration file OR no permission to read it
+ }
+
+ if (result == null)
+ {
+ try
+ {
+ result = secondary.getAppConfigurationEntry(name);
+ }
+ catch (Exception e)
+ {
+ // WLP 9.2.0 throws IllegalArgumentException for unknown appName
+ }
+ }
+
+ return result;
+ }
+ }
+
+ //-------------------------------------OSGi Config Management
+
+ static final class Realm
+ {
+ private final String realmName;
+ private AppConfigurationEntry[] configArray;
+ private List<AppConfigurationHolder> configs = new ArrayList<AppConfigurationHolder>();
+
+ Realm(String realmName)
+ {
+ this.realmName = realmName;
+ }
+
+ public void add(AppConfigurationHolder config)
+ {
+ configs.add(config);
+ }
+
+ public void afterPropertiesSet()
+ {
+ Collections.sort(configs);
+ configArray = new AppConfigurationEntry[configs.size()];
+ for (int i = 0; i < configs.size(); i++)
+ {
+ configArray[i] = configs.get(i).getEntry();
+ }
+ configs = Collections.unmodifiableList(configs);
+ }
+
+ public String getRealmName()
+ {
+ return realmName;
+ }
+
+ public List<AppConfigurationHolder> getConfigs()
+ {
+ return configs;
+ }
+
+ public AppConfigurationEntry[] engineGetAppConfigurationEntry()
+ {
+ return Arrays.copyOf(configArray, configArray.length);
+ }
+
+ @Override
+ public String toString()
+ {
+ return "Realm{" + "realmName='" + realmName + '\'' + '}';
+ }
+ }
+
+ static final class AppConfigurationHolder implements Comparable<AppConfigurationHolder>
+ {
+ private static final String LOGIN_MODULE_CLASS = ProxyLoginModule.class.getName();
+ private final LoginModuleProvider provider;
+ private final int ranking;
+ private final AppConfigurationEntry entry;
+
+ public AppConfigurationHolder(LoginModuleProvider provider)
+ {
+ this.provider = provider;
+ this.ranking = provider.ranking();
+
+ Map<String, Object> options = new HashMap<String, Object>(provider.options());
+ options.put(ProxyLoginModule.PROP_LOGIN_MODULE_FACTORY, provider);
+ this.entry = new AppConfigurationEntry(LOGIN_MODULE_CLASS,
+ provider.getControlFlag(), Collections.unmodifiableMap(options));
+ }
+
+ public int compareTo(AppConfigurationHolder that)
+ {
+ if (this.ranking == that.ranking)
+ {
+ return 0;
+ }
+ return this.ranking > that.ranking ? -1 : 1;
+ }
+
+ public AppConfigurationEntry getEntry()
+ {
+ return entry;
+ }
+
+ public LoginModuleProvider getProvider()
+ {
+ return provider;
+ }
+ }
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/ControlFlag.java b/jaas/src/main/java/org/apache/felix/jaas/internal/ControlFlag.java
new file mode 100644
index 0000000..de07d92
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/ControlFlag.java
@@ -0,0 +1,80 @@
+/*
+ * 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.jaas.internal;
+
+import static javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
+
+/**
+ * User: chetanm
+ * Date: 7/9/12
+ * Time: 9:55 PM
+ */
+@SuppressWarnings("UnusedDeclaration")
+enum ControlFlag
+{
+
+ REQUIRED(LoginModuleControlFlag.REQUIRED), REQUISITE(LoginModuleControlFlag.REQUISITE), SUFFICIENT(
+ LoginModuleControlFlag.SUFFICIENT), OPTIONAL(LoginModuleControlFlag.OPTIONAL), ;
+
+ private final LoginModuleControlFlag flag;
+
+ private ControlFlag(LoginModuleControlFlag flag)
+ {
+ this.flag = flag;
+ }
+
+ public LoginModuleControlFlag flag()
+ {
+ return flag;
+ }
+
+ public static ControlFlag from(String val)
+ {
+ val = Util.trimToNull(val);
+ if (val == null)
+ {
+ return REQUIRED;
+ }
+
+ val = val.toUpperCase();
+ return ControlFlag.valueOf(val);
+ }
+
+ public static String toString(LoginModuleControlFlag flag)
+ {
+ if (flag == LoginModuleControlFlag.REQUIRED)
+ {
+ return "REQUIRED";
+ }
+ else if (flag == LoginModuleControlFlag.REQUISITE)
+ {
+ return "REQUISITE";
+ }
+ else if (flag == LoginModuleControlFlag.SUFFICIENT)
+ {
+ return "SUFFICIENT";
+ }
+ else if (flag == LoginModuleControlFlag.OPTIONAL)
+ {
+ return "OPTIONAL";
+ }
+ throw new IllegalArgumentException("Unknown flag " + flag);
+ }
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/JaasConfigFactory.java b/jaas/src/main/java/org/apache/felix/jaas/internal/JaasConfigFactory.java
new file mode 100644
index 0000000..476923a
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/JaasConfigFactory.java
@@ -0,0 +1,171 @@
+/*
+ * 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.jaas.internal;
+
+import static org.apache.felix.jaas.internal.Util.trimToNull;
+
+import java.util.*;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.apache.felix.jaas.LoginModuleFactory;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.PropertyOption;
+import org.apache.felix.scr.annotations.PropertyUnbounded;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedServiceFactory;
+import org.osgi.service.log.LogService;
+
+@Component(label = "%jaas.name", description = "%jaas.description", metatype = true, ds = false, name = JaasConfigFactory.SERVICE_PID, configurationFactory = true)
+public class JaasConfigFactory implements ManagedServiceFactory
+{
+
+ public static final String SERVICE_PID = "org.apache.felix.jaas.Configuration.factory";
+
+ @Property
+ static final String JAAS_CLASS_NAME = "jaas.classname";
+
+ @Property(value = "required", options = {
+ @PropertyOption(name = "required", value = "%jaas.flag.required"),
+ @PropertyOption(name = "requisite", value = "%jaas.flag.requisite"),
+ @PropertyOption(name = "sufficient", value = "%jaas.flag.sufficient"),
+ @PropertyOption(name = "optional", value = "%jaas.flag.optional") })
+ static final String JAAS_CONTROL_FLAG = "jaas.controlFlag";
+
+ @Property(intValue = 0)
+ static final String JAAS_RANKING = "jaas.ranking";
+
+ @Property(unbounded = PropertyUnbounded.ARRAY)
+ static final String JAAS_OPTIONS = "jaas.options";
+
+ @Property
+ static final String JAAS_REALM_NAME = "jaas.realmName";
+
+ private final Logger log;
+
+ private final LoginModuleCreator factory;
+
+ private final BundleContext context;
+
+ private final Map<String, ServiceRegistration> registrations = new ConcurrentHashMap<String, ServiceRegistration>();
+
+ public JaasConfigFactory(BundleContext context, LoginModuleCreator factory, Logger log)
+ {
+ this.context = context;
+ this.factory = factory;
+ this.log = log;
+
+ Properties props = new Properties();
+ props.setProperty(Constants.SERVICE_VENDOR, "Apache Software Foundation");
+ props.setProperty(Constants.SERVICE_PID, SERVICE_PID);
+ context.registerService(ManagedServiceFactory.class.getName(), this, props);
+ }
+
+ @Override
+ public String getName()
+ {
+ return "JaasConfigFactory";
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void updated(String pid, Dictionary config) throws ConfigurationException
+ {
+ String className = trimToNull(Util.toString(config.get(JAAS_CLASS_NAME), null));
+ String flag = trimToNull(Util.toString(config.get(JAAS_CONTROL_FLAG), "required"));
+ int ranking = Util.toInteger(config.get(JAAS_RANKING), 0);
+
+ String[] props = Util.toStringArray(config.get(JAAS_OPTIONS), new String[0]);
+ Map options = toMap(props);
+ String realmName = trimToNull(Util.toString(config.get(JAAS_REALM_NAME), null));
+
+ if (className == null)
+ {
+ log.log(LogService.LOG_WARNING,
+ "Class name for the LoginModule is required. Configuration would be ignored"
+ + config);
+ return;
+ }
+
+ //Combine the config. As the jaas.options is required for capturing config
+ //via felix webconsole. However in normal usage people would like to provide
+ //key=value pair directly in config. So merge both to provide a combined
+ //view
+ Map combinedOptions = convertToMap(config);
+ combinedOptions.putAll(options);
+
+ LoginModuleProvider lmf = new ConfigLoginModuleProvider(realmName, className,
+ combinedOptions, ControlFlag.from(flag).flag(), ranking, factory);
+
+ ServiceRegistration reg = context.registerService(
+ LoginModuleFactory.class.getName(), lmf, new Properties());
+ registrations.put(pid, reg);
+ }
+
+ @Override
+ public void deleted(String pid)
+ {
+ ServiceRegistration reg = registrations.remove(pid);
+ if (reg != null)
+ {
+ reg.unregister();
+ }
+ }
+
+ //~----------------------------------- Utility Methods
+
+ private static Map<String, Object> toMap(String[] props)
+ {
+ //TODO support system property substitution e.g. ${user.home}
+ //in property values
+ Map<String, Object> result = new HashMap<String, Object>();
+ for (String kv : props)
+ {
+ int indexOfEqual = kv.indexOf('=');
+ if (indexOfEqual > 0)
+ {
+ String key = trimToNull(kv.substring(0, indexOfEqual));
+ String value = trimToNull(kv.substring(indexOfEqual + 1));
+ if (key != null && value != null)
+ {
+ result.put(key, value);
+ }
+ }
+ }
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Map convertToMap(Dictionary config)
+ {
+ Map copy = new HashMap();
+ Enumeration e = config.keys();
+ while (e.hasMoreElements())
+ {
+ Object key = e.nextElement();
+ Object value = config.get(key);
+ copy.put(key, value);
+ }
+ return copy;
+ }
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/JaasWebConsolePlugin.java b/jaas/src/main/java/org/apache/felix/jaas/internal/JaasWebConsolePlugin.java
new file mode 100644
index 0000000..03321da
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/JaasWebConsolePlugin.java
@@ -0,0 +1,292 @@
+/*
+ * 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.jaas.internal;
+
+import static org.apache.felix.jaas.internal.ConfigSpiOsgi.AppConfigurationHolder;
+import static org.apache.felix.jaas.internal.ConfigSpiOsgi.Realm;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+public class JaasWebConsolePlugin extends HttpServlet
+{
+
+ private final ConfigSpiOsgi configSpi;
+
+ private final BundleLoginModuleCreator loginModuleCreator;
+
+ public JaasWebConsolePlugin(BundleContext context, ConfigSpiOsgi configSpi, BundleLoginModuleCreator loginModuleCreator)
+ {
+ this.configSpi = configSpi;
+ this.loginModuleCreator = loginModuleCreator;
+
+ Properties props = new Properties();
+ props.put(Constants.SERVICE_VENDOR, "Apache Software Foundation");
+ props.put(Constants.SERVICE_DESCRIPTION, "JAAS Web Console Plugin");
+ props.put("felix.webconsole.label", "jaas");
+ props.put("felix.webconsole.title", "JAAS");
+ props.put("felix.webconsole.configprinter.modes", "always");
+ context.registerService(Servlet.class.getName(), this, props);
+ }
+
+ @Override
+ protected void doGet(final HttpServletRequest req, final HttpServletResponse resp)
+ throws ServletException, IOException
+ {
+ if (req.getPathInfo().endsWith("/data.json"))
+ {
+ getJson(resp);
+ }
+ else
+ {
+ getHtml(resp);
+ }
+
+ }
+
+ private void getHtml(HttpServletResponse resp) throws IOException
+ {
+ final PrintWriter pw = resp.getWriter();
+
+ printAppConfigurationDetails(pw);
+ printAvailableModuleDetails(pw);
+
+ }
+
+ private void printAvailableModuleDetails(PrintWriter pw)
+ {
+ Map<Bundle, Set<String>> bundleMap = getAvailableLoginModuleInfo();
+
+ pw.println("<p class=\"statline ui-state-highlight\">${Available LoginModules}</p>");
+ if (bundleMap.isEmpty())
+ {
+ return;
+ }
+
+ pw.println("<table class=\"nicetable\">");
+ pw.println("<thead><tr>");
+ pw.println("<th class=\"header\">${Bundle}</th>");
+ pw.println("<th class=\"header\">${Classes}</th>");
+ pw.println("</tr></thead>");
+
+ String rowClass = "odd";
+ for (Map.Entry<Bundle, Set<String>> e : bundleMap.entrySet())
+ {
+ Bundle b = e.getKey();
+ pw.print("<tr class=\"%s ui-state-default\">");
+ pw.printf("<td><a href=\"${pluginRoot}/../bundles/%s\">%s (%s)</a></td>",
+ b.getBundleId(), b.getSymbolicName(), b.getBundleId());
+ pw.printf("<td>");
+ for (String className : e.getValue())
+ {
+ pw.print(className);
+ pw.print("<br/>");
+ }
+ pw.print("</td>");
+ pw.println("</tr>");
+
+ if (rowClass.equals("odd"))
+ {
+ rowClass = "even";
+ }
+ else
+ {
+ rowClass = "odd";
+ }
+ }
+ pw.println("</table>");
+ }
+
+ private void printAppConfigurationDetails(PrintWriter pw)
+ {
+ Map<String, Realm> configs = getConfigurationDetails();
+ if (configs.isEmpty())
+ {
+ pw.println("No JAAS LoginModule registered");
+ return;
+ }
+
+ pw.println("<p class=\"statline ui-state-highlight\">${Registered LoginModules}</p>");
+
+ pw.println("<table class=\"nicetable\">");
+ pw.println("<thead><tr>");
+ pw.println("<th class=\"header\">${Realm}</th>");
+ pw.println("<th class=\"header\">${Rank}</th>");
+ pw.println("<th class=\"header\">${Control Flag}</th>");
+ pw.println("<th class=\"header\">${Type}</th>");
+ pw.println("<th class=\"header\">${Classname}</th>");
+ pw.println("</tr></thead>");
+
+ for (Realm r : configs.values())
+ {
+ String realmName = r.getRealmName();
+ pw.printf(
+ "<tr class=\"ui-state-default\"><td>%s</td><td colspan=\"4\"></td></tr>",
+ realmName);
+
+ String rowClass = "odd";
+ for (AppConfigurationHolder ah : r.getConfigs())
+ {
+ LoginModuleProvider lp = ah.getProvider();
+ String type = getType(lp);
+ pw.printf("<tr class=\"%s ui-state-default\"><td></td><td>%d</td>",
+ rowClass, lp.ranking());
+ pw.printf("<td>%s</td>", ControlFlag.toString(lp.getControlFlag()));
+ pw.printf("<td>%s</td>", type);
+
+ pw.printf("<td>");
+ pw.print(lp.getClassName());
+
+ if (lp instanceof OsgiLoginModuleProvider)
+ {
+ ServiceReference sr = ((OsgiLoginModuleProvider) lp).getServiceReference();
+ Object id = sr.getProperty(Constants.SERVICE_ID);
+ pw.printf("<a href=\"${pluginRoot}/../services/%s\">(%s)</a>", id, id);
+ }
+ else if (lp instanceof ConfigLoginModuleProvider)
+ {
+ Map config = lp.options();
+ Object id = config.get(Constants.SERVICE_PID);
+ pw.printf("<a href=\"${pluginRoot}/../configMgr/%s\">(Details)</a>",
+ id);
+ }
+ pw.printf("</td>");
+
+ pw.println("</tr>");
+ if (rowClass.equals("odd"))
+ {
+ rowClass = "even";
+ }
+ else
+ {
+ rowClass = "odd";
+ }
+ }
+ }
+ pw.println("</table>");
+ }
+
+ private String getType(LoginModuleProvider lp)
+ {
+ String type = "Service";
+ if (lp instanceof ConfigLoginModuleProvider)
+ {
+ type = "Configuration";
+ }
+ return type;
+ }
+
+ private void getJson(HttpServletResponse resp)
+ {
+
+ }
+
+ /**
+ * @see org.apache.felix.webconsole.ConfigurationPrinter#printConfiguration(java.io.PrintWriter)
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ public void printConfiguration(final PrintWriter pw)
+ {
+ pw.println("JAAS Configuration Details:");
+ pw.println();
+ pw.println("Registered LoginModules");
+ Map<String, Realm> configs = getConfigurationDetails();
+ if (configs.isEmpty())
+ {
+ pw.println("No JAAS LoginModule registered");
+ }
+ else
+ {
+ for (Realm r : configs.values())
+ {
+ String realmName = r.getRealmName();
+ pw.printf("Realm : %s \n", realmName);
+ for (AppConfigurationHolder ah : r.getConfigs())
+ {
+ addSpace(pw, 1);
+ pw.printf("%s \n", ah.getProvider().getClassName());
+
+ addSpace(pw, 2);
+ pw.printf("Flag : %s \n",
+ ControlFlag.toString(ah.getProvider().getControlFlag()));
+ addSpace(pw, 2);
+ pw.printf("Type : %s \n", getType(ah.getProvider()));
+ addSpace(pw, 2);
+ pw.printf("Ranking : %d \n", ah.getProvider().ranking());
+ }
+ }
+ }
+
+ pw.println();
+
+ Map<Bundle, Set<String>> bundleMap = getAvailableLoginModuleInfo();
+ pw.println("Available LoginModules");
+ if (bundleMap.isEmpty())
+ {
+ //Nothing to do
+ }
+ else
+ {
+ for (Map.Entry<Bundle, Set<String>> e : bundleMap.entrySet())
+ {
+ Bundle b = e.getKey();
+ pw.printf("%s (%s) \n", b.getSymbolicName(), b.getBundleId());
+ for (String className : e.getValue())
+ {
+ addSpace(pw, 1);
+ pw.println(className);
+ }
+ }
+ }
+ }
+
+ private static void addSpace(PrintWriter pw, int count)
+ {
+ for (int i = 0; i < count; i++)
+ {
+ pw.print(" ");
+ }
+ }
+
+ private Map<String, Realm> getConfigurationDetails()
+ {
+ return configSpi.getAllConfiguration();
+ }
+
+ private Map<Bundle, Set<String>> getAvailableLoginModuleInfo()
+ {
+ return loginModuleCreator.getBundleToLoginModuleMapping();
+ }
+
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/Logger.java b/jaas/src/main/java/org/apache/felix/jaas/internal/Logger.java
new file mode 100644
index 0000000..1d62dc2
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/Logger.java
@@ -0,0 +1,57 @@
+/*
+ * 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.jaas.internal;
+
+import org.osgi.framework.BundleContext;
+import org.osgi.service.log.LogService;
+import org.osgi.util.tracker.ServiceTracker;
+
+class Logger extends ServiceTracker
+{
+
+ public Logger(BundleContext context)
+ {
+ super(context, LogService.class.getName(), null);
+ }
+
+ public void log(int level, String message)
+ {
+ LogService log = getLog();
+ if (log != null)
+ {
+ log.log(level, message);
+ }
+
+ }
+
+ public void log(int level, String message, Throwable exception)
+ {
+ LogService log = getLog();
+ if (log != null)
+ {
+ log.log(level, message, exception);
+ }
+ }
+
+ private LogService getLog()
+ {
+ return (LogService) getService();
+ }
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/LoginModuleCreator.java b/jaas/src/main/java/org/apache/felix/jaas/internal/LoginModuleCreator.java
new file mode 100644
index 0000000..d5db0a4
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/LoginModuleCreator.java
@@ -0,0 +1,35 @@
+/*
+ * 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.jaas.internal;
+
+import javax.security.auth.spi.LoginModule;
+
+
+/**
+ * User: chetanm
+ * Date: 7/9/12
+ * Time: 10:17 PM
+ */
+public interface LoginModuleCreator
+{
+
+ LoginModule newInstance(String className);
+
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/LoginModuleProvider.java b/jaas/src/main/java/org/apache/felix/jaas/internal/LoginModuleProvider.java
new file mode 100644
index 0000000..4e237f4
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/LoginModuleProvider.java
@@ -0,0 +1,41 @@
+/*
+ * 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.jaas.internal;
+
+import static javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
+
+import java.util.Map;
+
+import org.apache.felix.jaas.LoginModuleFactory;
+import org.apache.felix.jaas.boot.ProxyLoginModule;
+
+public interface LoginModuleProvider extends LoginModuleFactory, ProxyLoginModule.BootLoginModuleFactory
+{
+
+ Map<String, ?> options();
+
+ LoginModuleControlFlag getControlFlag();
+
+ int ranking();
+
+ String realmName();
+
+ String getClassName();
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/OsgiLoginModuleProvider.java b/jaas/src/main/java/org/apache/felix/jaas/internal/OsgiLoginModuleProvider.java
new file mode 100644
index 0000000..57e29c2
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/OsgiLoginModuleProvider.java
@@ -0,0 +1,93 @@
+/*
+ * 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.jaas.internal;
+
+import static javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
+
+import java.util.Collections;
+import java.util.Map;
+
+import javax.security.auth.spi.LoginModule;
+
+import org.apache.felix.jaas.LoginModuleFactory;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceReference;
+
+final class OsgiLoginModuleProvider implements LoginModuleProvider
+{
+ private final LoginModuleFactory delegate;
+ private final int ranking;
+ private final LoginModuleControlFlag flag;
+ private final String realmName;
+ private final ServiceReference serviceReference;
+
+ public OsgiLoginModuleProvider(ServiceReference sr, LoginModuleFactory delegate)
+ {
+ this.delegate = delegate;
+ this.ranking = Util.toInteger(sr.getProperty(Constants.SERVICE_RANKING), 0);
+ this.flag = ControlFlag.from(
+ (String) sr.getProperty(LoginModuleFactory.JAAS_CONTROL_FLAG)).flag();
+ this.realmName = (String) sr.getProperty(LoginModuleFactory.JAAS_REALM_NAME);
+ this.serviceReference = sr;
+ }
+
+ public Map<String, ?> options()
+ {
+ return Collections.emptyMap();
+ }
+
+ public LoginModuleControlFlag getControlFlag()
+ {
+ return flag;
+ }
+
+ public int ranking()
+ {
+ return ranking;
+ }
+
+ public String realmName()
+ {
+ return realmName;
+ }
+
+ public LoginModule createLoginModule()
+ {
+ return delegate.createLoginModule();
+ }
+
+ public String getClassName()
+ {
+ return delegate.getClass().getName();
+ }
+
+ public ServiceReference getServiceReference()
+ {
+ return serviceReference;
+ }
+
+ @Override
+ public String toString()
+ {
+ return "OsgiLoginModuleProvider{" + "className=" + delegate.getClass().getName()
+ + ", ranking=" + ranking + ", flag=" + flag + ", realmName='" + realmName
+ + '\'' + '}';
+ }
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/internal/Util.java b/jaas/src/main/java/org/apache/felix/jaas/internal/Util.java
new file mode 100644
index 0000000..515891f
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/internal/Util.java
@@ -0,0 +1,210 @@
+/*
+ * 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.jaas.internal;
+
+import java.util.*;
+
+
+final class Util
+{
+
+ public static Set<String> parseHeader(String header)
+ {
+ //Could have used Sling commons ManifestHeader.parse
+ //but our requirement are simple
+
+ header = trimToNull(header);
+ if (header == null)
+ {
+ return new HashSet<String>();
+ }
+
+ String[] splits = header.split(",");
+ Set<String> values = new HashSet<String>();
+ for (String s : splits)
+ {
+ s = trimToNull(s);
+ if (s != null)
+ {
+ values.add(s);
+ }
+ }
+ return values;
+ }
+
+ //Instead of adding dependency on commons StringUtil we copy the used method below
+
+ public static String trimToNull(String str)
+ {
+ String ts = trim(str);
+ return isEmpty(ts) ? null : ts;
+ }
+
+ private static String trim(String str)
+ {
+ return str == null ? null : str.trim();
+ }
+
+ public static boolean isEmpty(String str)
+ {
+ return str == null || str.length() == 0;
+ }
+
+ //----------------Methods taken from org.apache.sling.commons.osgi.PropertiesUtil
+
+ //These are required to safely access properties from ConfigurationAdmin
+
+ /**
+ * Returns the parameter as a string or the
+ * <code>defaultValue</code> if the parameter is <code>null</code>.
+ * @param propValue the property value or <code>null</code>
+ * @param defaultValue the default string value
+ */
+ public static String toString(Object propValue, String defaultValue)
+ {
+ propValue = toObject(propValue);
+ return (propValue != null) ? propValue.toString() : defaultValue;
+ }
+
+ /**
+ * Returns the parameter as an integer or the
+ * <code>defaultValue</code> if the parameter is <code>null</code> or if
+ * the parameter is not an <code>Integer</code> and cannot be converted to
+ * an <code>Integer</code> from the parameter's string value.
+ * @param propValue the property value or <code>null</code>
+ * @param defaultValue the default integer value
+ */
+ public static int toInteger(Object propValue, int defaultValue)
+ {
+ propValue = toObject(propValue);
+ if (propValue instanceof Integer)
+ {
+ return (Integer) propValue;
+ }
+ else if (propValue != null)
+ {
+ try
+ {
+ return Integer.valueOf(String.valueOf(propValue));
+ }
+ catch (NumberFormatException nfe)
+ {
+ // don't care, fall through to default value
+ }
+ }
+
+ return defaultValue;
+ }
+
+ /**
+ * Returns the parameter as a single value. If the
+ * parameter is neither an array nor a <code>java.util.Collection</code> the
+ * parameter is returned unmodified. If the parameter is a non-empty array,
+ * the first array element is returned. If the property is a non-empty
+ * <code>java.util.Collection</code>, the first collection element is returned.
+ * Otherwise <code>null</code> is returned.
+ * @param propValue the parameter to convert.
+ */
+ private static Object toObject(Object propValue)
+ {
+ if (propValue == null)
+ {
+ return null;
+ }
+ else if (propValue.getClass().isArray())
+ {
+ Object[] prop = (Object[]) propValue;
+ return prop.length > 0 ? prop[0] : null;
+ }
+ else if (propValue instanceof Collection<?>)
+ {
+ Collection<?> prop = (Collection<?>) propValue;
+ return prop.isEmpty() ? null : prop.iterator().next();
+ }
+ else
+ {
+ return propValue;
+ }
+ }
+
+ /**
+ * Returns the parameter as an array of Strings. If
+ * the parameter is a scalar value its string value is returned as a single
+ * element array. If the parameter is an array, the elements are converted to
+ * String objects and returned as an array. If the parameter is a collection, the
+ * collection elements are converted to String objects and returned as an array.
+ * Otherwise (if the property is <code>null</code>) a provided default value is
+ * returned.
+ * @param propValue The object to convert.
+ * @param defaultArray The default array to return.
+ */
+ public static String[] toStringArray(Object propValue, String[] defaultArray)
+ {
+ if (propValue == null)
+ {
+ // no value at all
+ return defaultArray;
+
+ }
+ else if (propValue instanceof String)
+ {
+ // single string
+ return new String[] { (String) propValue };
+
+ }
+ else if (propValue instanceof String[])
+ {
+ // String[]
+ return (String[]) propValue;
+
+ }
+ else if (propValue.getClass().isArray())
+ {
+ // other array
+ Object[] valueArray = (Object[]) propValue;
+ List<String> values = new ArrayList<String>(valueArray.length);
+ for (Object value : valueArray)
+ {
+ if (value != null)
+ {
+ values.add(value.toString());
+ }
+ }
+ return values.toArray(new String[values.size()]);
+
+ }
+ else if (propValue instanceof Collection<?>)
+ {
+ // collection
+ Collection<?> valueCollection = (Collection<?>) propValue;
+ List<String> valueList = new ArrayList<String>(valueCollection.size());
+ for (Object value : valueCollection)
+ {
+ if (value != null)
+ {
+ valueList.add(value.toString());
+ }
+ }
+ return valueList.toArray(new String[valueList.size()]);
+ }
+
+ return defaultArray;
+ }
+}
diff --git a/jaas/src/main/java/org/apache/felix/jaas/package-info.java b/jaas/src/main/java/org/apache/felix/jaas/package-info.java
new file mode 100644
index 0000000..40828e5
--- /dev/null
+++ b/jaas/src/main/java/org/apache/felix/jaas/package-info.java
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+/**
+ * Provides support for performing JAAS based authentication in OSGi
+ *
+ * @version 1.0
+ */
+@Version("1.0")
+@Export(optional = "provide:=true")
+package org.apache.felix.jaas;
+
+import aQute.bnd.annotation.Version;
+import aQute.bnd.annotation.Export;
diff --git a/jaas/src/main/resources/OSGI-INF/l10n/bundle.properties b/jaas/src/main/resources/OSGI-INF/l10n/bundle.properties
new file mode 100644
index 0000000..c458298
--- /dev/null
+++ b/jaas/src/main/resources/OSGI-INF/l10n/bundle.properties
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+intro=JAAS Configuration Details
diff --git a/jaas/src/main/resources/OSGI-INF/metatype/metatype.properties b/jaas/src/main/resources/OSGI-INF/metatype/metatype.properties
new file mode 100644
index 0000000..029db59
--- /dev/null
+++ b/jaas/src/main/resources/OSGI-INF/metatype/metatype.properties
@@ -0,0 +1,62 @@
+#
+# 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.
+#
+# suppress inspection "UnusedProperty" for whole file
+
+jaas.name=Apache Felix JAAS Configuration Factory
+jaas.description=Captures JAAS configuration with options, control flag and classname
+
+
+jaas.classname.name=Class Name
+jaas.classname.description=Fully qualified name of the LoginModule class
+
+jaas.controlFlag.name=Control Flag
+jaas.controlFlag.description=The Flag value controls the overall behavior as authentication proceeds down the stack
+
+jaas.flag.required=Required
+jaas.flag.requisite=Requisite
+jaas.flag.sufficient=Sufficient
+jaas.flag.optional=Optional
+
+jaas.ranking.name = Ranking
+jaas.ranking.description = The relative ranking of this configuration.
+
+jaas.options.name = Options
+jaas.options.description = Properties in the form of key value pairs that are passed on to the LoginModule(name=value pairs)
+
+jaas.realmName.name = Realm Name
+jaas.realmName.description = Name of the application
+
+jaas.spi.name = Apache Felix JAAS Configuration SPI
+jaas.spi.description= JAAS Configuration SPI implementation which provides configuration based on OSGi ConfigAdmin
+
+jaas.defaultRealmName.name = Default JAAS Realm
+jaas.defaultRealmName.description = Default realm name to use if no realm is explicitly defined for LoginModule
+
+jaas.configProviderName.name=JAAS Config Provider name
+jaas.configProviderName.description=Name of the provider used to register the OSGi based configuration provider
+
+jaas.globalConfigPolicy.name=Global Configuration Policy
+jaas.globalConfigPolicy.description=Policy to manage global configuration. (1) Default: Global configuration is not \
+ modified. (2). Replace Global Configuration: Global configuration is replaced with OSGi based configuration \
+ (3). Proxy Global Configuration: Global configuration would be replaced with proxy configuration. The proxy \
+ would check with OSGi based configuration. If no config is found it would look in default global configuration
+
+jaas.configPolicy.default=Default
+jaas.configPolicy.replace=Replace Global Configuration
+jaas.configPolicy.proxy=Proxy Global Configuration
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 62d4d5c..0b6f6e1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -122,6 +122,7 @@
<module>webconsole-plugins/event</module>
<module>fileinstall</module>
<module>useradmin</module>
+ <module>jaas</module>
<module>gogo</module>