[KARAF-6579] Add Commands jaas:realm-add and jaas:module-add to manag… (#1057)
diff --git a/jaas/command/src/main/java/org/apache/karaf/jaas/command/ModuleAddCommand.java b/jaas/command/src/main/java/org/apache/karaf/jaas/command/ModuleAddCommand.java
new file mode 100644
index 0000000..934b118
--- /dev/null
+++ b/jaas/command/src/main/java/org/apache/karaf/jaas/command/ModuleAddCommand.java
@@ -0,0 +1,131 @@
+/*
+ * 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.karaf.jaas.command;
+
+import org.apache.karaf.jaas.command.completers.LoginModuleNameCompleter;
+import org.apache.karaf.jaas.config.JaasRealm;
+import org.apache.karaf.jaas.config.impl.Config;
+import org.apache.karaf.jaas.config.impl.Module;
+import org.apache.karaf.jaas.modules.BackingEngine;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+
+@Command(scope = "jaas", name = "module-add", description = "Add a Login Module")
+@Service
+public class ModuleAddCommand extends JaasCommandSupport {
+
+ @Argument(index = 0, name = "loginModule", description = "Class Name of Login Module", required = true, multiValued = false)
+ @Completion(LoginModuleNameCompleter.class)
+ private String loginModule;
+
+ @Argument(index = 1, name = "properties", description = "Pair of Properties (key value)", required = false, multiValued = true)
+ private List<String> propertiesList;
+
+ @Override
+ protected Object doExecute(BackingEngine engine) throws Exception {
+ return null;
+ }
+
+ @Override
+ public Object execute() throws Exception {
+ // Fetch Realm
+ JaasRealm realm = (JaasRealm) session.get(JAAS_REALM);
+
+ if (realm == null) {
+ System.err.println("No JAAS Realm has been selected");
+ throw new IllegalStateException("No JAAS Realm has been selected");
+ }
+ if (!(realm instanceof Config)) {
+ System.err.println("Selected JAAS Realm was not added via jaas:add-realm, only those are supported!");
+ throw new IllegalStateException("Selected JAAS Realm was not added via jaas:add-realm, only those are supported!");
+ }
+
+ if (!checkIfClassExists(loginModule)) {
+ System.err.println("Module class '" + loginModule + "' is unknown!");
+ throw new IllegalArgumentException("Module class '" + loginModule + "' is unknown!");
+ }
+ Module module = createModuleFromCmdParameters(loginModule, propertiesList);
+
+ // Add the Login Module to the current Realm
+ List<Module> modulesList = new ArrayList<>(Arrays.asList(((Config) realm).getModules()));
+ modulesList.add(module);
+ Module[] newModules = modulesList.toArray(new Module[]{});
+ ((Config) realm).setModules(newModules);
+ return null;
+ }
+
+ /**
+ * Parses the Command Line Parameters given to create a valid Module and Properties from it.
+ * @param loginModule Class Name of the login Module
+ * @param propertiesList List of Properties interpreted as "key1 value1 key2 value2"
+ * @return Module
+ */
+ static Module createModuleFromCmdParameters(String loginModule, List<String> propertiesList) {
+ // Parse Properties
+ if (propertiesList != null && propertiesList.size() > 0 && (propertiesList.size() % 2) == 1) {
+ // Properties are uneven... bad
+ System.err.println("Properties have to be given as \"key1 value1 key2 value2 ...\" but number of Arguments is uneven!");
+ return null;
+ }
+ Properties properties = new Properties();
+ if (propertiesList != null) {
+ for (int i = 0; i < propertiesList.size(); i += 2) {
+ properties.put(propertiesList.get(i), propertiesList.get(i + 1));
+ }
+ }
+ // Assemble Login Module
+ Module module = new Module();
+ module.setClassName(loginModule);
+ module.setFlags("required");
+ module.setOptions(properties);
+ return module;
+ }
+
+ public String getLoginModule() {
+ return loginModule;
+ }
+
+ public void setLoginModule(String loginModule) {
+ this.loginModule = loginModule;
+ }
+
+ public List<String> getPropertiesList() {
+ return propertiesList;
+ }
+
+ public void setPropertiesList(List<String> propertiesList) {
+ this.propertiesList = propertiesList;
+ }
+
+ private boolean checkIfClassExists(String loginModule) {
+ try {
+ this.getClass().getClassLoader().loadClass(loginModule);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+}
diff --git a/jaas/command/src/main/java/org/apache/karaf/jaas/command/RealmAddCommand.java b/jaas/command/src/main/java/org/apache/karaf/jaas/command/RealmAddCommand.java
new file mode 100644
index 0000000..b70655b
--- /dev/null
+++ b/jaas/command/src/main/java/org/apache/karaf/jaas/command/RealmAddCommand.java
@@ -0,0 +1,95 @@
+/*
+ * 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.karaf.jaas.command;
+
+import org.apache.karaf.jaas.command.completers.LoginModuleNameCompleter;
+import org.apache.karaf.jaas.command.completers.RealmCompleter;
+import org.apache.karaf.jaas.config.JaasRealm;
+import org.apache.karaf.jaas.config.impl.Config;
+import org.apache.karaf.jaas.config.impl.Module;
+import org.apache.karaf.jaas.modules.BackingEngine;
+import org.apache.karaf.shell.api.action.Argument;
+import org.apache.karaf.shell.api.action.Command;
+import org.apache.karaf.shell.api.action.Completion;
+import org.apache.karaf.shell.api.action.lifecycle.Reference;
+import org.apache.karaf.shell.api.action.lifecycle.Service;
+import org.osgi.framework.BundleContext;
+
+import java.util.List;
+
+@Command(scope = "jaas", name = "realm-add", description = "Add a realm")
+@Service
+public class RealmAddCommand extends JaasCommandSupport {
+
+ @Reference
+ private BundleContext context;
+
+ @Argument(index = 0, name = "realmname", description = "Realm Name", required = true, multiValued = false)
+ @Completion(RealmCompleter.class)
+ private String realmname;
+
+ @Argument(index = 1, name = "loginModule", description = "Class Name of Login Module", required = true, multiValued = false)
+ @Completion(LoginModuleNameCompleter.class)
+ private String loginModule;
+
+ @Argument(index = 2, name = "properties", description = "Pair of Properties (key value)", required = false, multiValued = true)
+ private List<String> propertiesList;
+
+ @Override
+ protected Object doExecute(BackingEngine engine) throws Exception {
+ return null;
+ }
+
+ @Override
+ public Object execute() throws Exception {
+ Module initialModule = ModuleAddCommand.createModuleFromCmdParameters(loginModule, propertiesList);
+ if (initialModule == null) {
+ // If we could not identify a Module we cannot create a realm => exit (sys.err was already written)
+ return null;
+ }
+ // Create realm
+ Config realm = new Config();
+ realm.setName(realmname);
+ realm.setModules(new Module[]{initialModule});
+ realm.setBundleContext(context);
+
+ context.registerService(JaasRealm.class, realm, null);
+ return null;
+ }
+
+ public BundleContext getContext() {
+ return context;
+ }
+
+ public void setContext(BundleContext context) {
+ this.context = context;
+ }
+
+ public String getRealmname() {
+ return realmname;
+ }
+
+ public void setRealmname(String realmname) {
+ this.realmname = realmname;
+ }
+
+ @Override
+ public String toString() {
+ return "RealmAddCommand{" +
+ "realmname='" + realmname + '\'' +
+ '}';
+ }
+}
diff --git a/jaas/command/src/test/java/org/apache/karaf/jaas/command/ManageRealmCommandTest.java b/jaas/command/src/test/java/org/apache/karaf/jaas/command/ManageRealmCommandTest.java
index 71397a1..206220c 100644
--- a/jaas/command/src/test/java/org/apache/karaf/jaas/command/ManageRealmCommandTest.java
+++ b/jaas/command/src/test/java/org/apache/karaf/jaas/command/ManageRealmCommandTest.java
@@ -16,21 +16,27 @@
package org.apache.karaf.jaas.command;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Properties;
import org.apache.karaf.jaas.config.impl.Config;
import org.apache.karaf.jaas.config.impl.Module;
import org.apache.karaf.shell.api.console.Session;
+import org.easymock.Capture;
import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.newCapture;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
public class ManageRealmCommandTest {
@@ -52,6 +58,74 @@
doVerifyIndex(cmd, 2, realms);
}
+ @Test
+ public void testRealmAdd() throws Exception {
+ RealmAddCommand cmd = new RealmAddCommand();
+ cmd.setRealmname("myDummyRealm");
+
+ // prepare mocks
+ Session session = createMock(Session.class);
+ BundleContext bundleContext = createMock(BundleContext.class);
+ Bundle bundle = createMock(Bundle.class);
+
+ // prepare command
+ cmd.setContext(bundleContext);
+ cmd.setSession(session);
+
+ Object[] mocks = { session, bundleContext, bundle };
+
+ expect(bundleContext.registerService(anyObject(Class.class),
+ (Object)anyObject(), anyObject())).andReturn(null).anyTimes();
+
+ replay(mocks);
+ cmd.execute();
+ verify(mocks);
+ }
+
+ @Test
+ public void testModuleAdd() throws Exception {
+ RealmAddCommand cmd = new RealmAddCommand();
+ cmd.setRealmname("myDummyRealm");
+
+ ModuleAddCommand addCmd = new ModuleAddCommand();
+ addCmd.setLoginModule(DummyClass.class.getName());
+ addCmd.setPropertiesList(Collections.emptyList());
+
+ // prepare mocks
+ Session session = createMock(Session.class);
+ BundleContext bundleContext = createMock(BundleContext.class);
+ Bundle bundle = createMock(Bundle.class);
+
+ // prepare command
+ cmd.setContext(bundleContext);
+ cmd.setSession(session);
+ addCmd.setSession(session);
+
+ Object[] mocks = { session, bundleContext, bundle };
+
+ expect(session.get(ManageRealmCommand.JAAS_ENTRY)).andReturn(null).anyTimes();
+ expect(session.get(ManageRealmCommand.JAAS_CMDS)).andReturn(null).anyTimes();
+ expect(bundleContext.getBundle()).andReturn(bundle).anyTimes();
+ expect(bundle.getBundleId()).andReturn(4711L).anyTimes();
+
+ Capture<Object> captureSingleArgument = newCapture();
+ expect(bundleContext.registerService(anyObject(Class.class),
+ (Object)capture(captureSingleArgument), anyObject())).andReturn(null).anyTimes();
+ expect(session.get(ManageRealmCommand.JAAS_REALM)).andAnswer(() -> captureSingleArgument.getValue()).anyTimes();
+
+ replay(mocks);
+ cmd.execute();
+ addCmd.execute();
+ verify(mocks);
+
+ assertNotNull((Config) captureSingleArgument.getValue());
+
+ // Now check if two modules are installed (1 initial + 1 addon)
+ assertEquals(2, ((Config) captureSingleArgument.getValue()).getModules().length);
+ }
+
+ public static class DummyClass {}
+
/**
* Verify that command selects the correct realm, given some index.
*
diff --git a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/BackingEngine.java b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/BackingEngine.java
index b5d7275..fa9ea1f 100644
--- a/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/BackingEngine.java
+++ b/jaas/modules/src/main/java/org/apache/karaf/jaas/modules/BackingEngine.java
@@ -21,6 +21,7 @@
import org.apache.karaf.jaas.boot.principal.GroupPrincipal;
import org.apache.karaf.jaas.boot.principal.RolePrincipal;
import org.apache.karaf.jaas.boot.principal.UserPrincipal;
+import org.apache.karaf.jaas.config.impl.Module;
public interface BackingEngine {
diff --git a/manual/src/main/asciidoc/user-guide/security.adoc b/manual/src/main/asciidoc/user-guide/security.adoc
index 23ca535..a0209cf 100644
--- a/manual/src/main/asciidoc/user-guide/security.adoc
+++ b/manual/src/main/asciidoc/user-guide/security.adoc
@@ -64,6 +64,29 @@
You can manage an existing realm, login module, or create your own realm using the `jaas:realm-manage` command.
+===== Adding Realms or Login Modules
+
+If a new realm should be added Apache Karaf provides an easy way to create a new Realm (although with limited flexibility compared to other approaches like blueprint or directly via DS).
+The `jaas:realm-add` command can be used for that purpose.
+Note, that it takes at least 2 Parameters (name of the realm and class name of the initial Login Module) or even more, if the Login Module needs parameters.
+For example, a new realm `myrealm` which uses the `PropertiesLoginModule` with a `users` file located in `/tmp/users` can be added by the command
+
+----
+jaas:realm-add myrealm org.apache.karaf.jaas.modules.properties.PropertiesLoginModule users "/tmp/users"
+----
+
+To add a new Login Module to an existing realm the `jaas:module-add` command can be used. It is similar in semantics than the `jaas:realm-add` command except that it takes no realm name.
+Instead, the realm to modify has to be selected via `jaas:realm-manage` previously.
+Note that the options for the Login Module are given as a list of parameters which is interpreted as a map in the order
+"key1 value1 key2 value2".
+So the command
+
+----
+jaas:module-add org.apache.karaf.jaas.modules.properties.PropertiesLoginModule users "/tmp/users"
+----
+
+will add a `PropertiesLoginModule` with parameters map `users -> /tmp/users` to the selected realm.
+
==== Users, groups, roles, and passwords
As we saw, by default, Apache Karaf uses a PropertiesLoginModule.