Refactor Argeo APIs
authorMathieu Baudier <mbaudier@argeo.org>
Mon, 11 Mar 2024 07:59:54 +0000 (08:59 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Mon, 11 Mar 2024 07:59:54 +0000 (08:59 +0100)
36 files changed:
Makefile
org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapNameUtils.java [new file with mode: 0644]
org.argeo.api.cli/.classpath [deleted file]
org.argeo.api.cli/.project [deleted file]
org.argeo.api.cli/bnd.bnd [deleted file]
org.argeo.api.cli/build.properties [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java [deleted file]
org.argeo.api.cli/src/org/argeo/api/cli/package-info.java [deleted file]
org.argeo.api.cms/META-INF/.gitignore [deleted file]
org.argeo.api.cms/bnd.bnd
org.argeo.api.cms/src/org/argeo/api/cms/auth/ImpliedByPrincipal.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/auth/RoleNameUtils.java [new file with mode: 0644]
org.argeo.api.cms/src/org/argeo/api/cms/auth/SystemRole.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/CommandArgsException.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/CommandRuntimeException.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/CommandsCli.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/DescribedCommand.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/HelpCommand.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/PrintHelpRequestException.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/api/cli/package-info.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/CurrentUser.java
org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/SystemRole.java [deleted file]
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/CmsSystemRole.java
org.argeo.cms/src/org/argeo/cms/directory/ldap/AbstractLdapDirectory.java
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapDao.java
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapHierarchyUnit.java
org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java [deleted file]
org.argeo.cms/src/org/argeo/cms/osgi/useradmin/DirectoryUserAdmin.java

index 407f4214b3412eb99c4a08aefcb2129537423069..497de15b0e1e2a03e0dc816b00669a25920a1fff 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -7,7 +7,6 @@ org.argeo.init \
 org.argeo.api.uuid \
 org.argeo.api.register \
 org.argeo.api.acr \
-org.argeo.api.cli \
 org.argeo.api.cms \
 org.argeo.cms \
 org.argeo.cms.ux \
diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapNameUtils.java b/org.argeo.api.acr/src/org/argeo/api/acr/ldap/LdapNameUtils.java
new file mode 100644 (file)
index 0000000..72fa65a
--- /dev/null
@@ -0,0 +1,69 @@
+package org.argeo.api.acr.ldap;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.naming.ldap.Rdn;
+
+/** Utilities to simplify using {@link LdapName}. */
+public class LdapNameUtils {
+
+       public static LdapName relativeName(LdapName prefix, LdapName dn) {
+               try {
+                       if (!dn.startsWith(prefix))
+                               throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn);
+                       LdapName res = (LdapName) dn.clone();
+                       for (int i = 0; i < prefix.size(); i++) {
+                               res.remove(0);
+                       }
+                       return res;
+               } catch (InvalidNameException e) {
+                       throw new IllegalStateException("Cannot find realtive name", e);
+               }
+       }
+
+       public static LdapName getParent(LdapName dn) {
+               try {
+                       LdapName parent = (LdapName) dn.clone();
+                       parent.remove(parent.size() - 1);
+                       return parent;
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot get parent of " + dn, e);
+               }
+       }
+
+       public static Rdn getParentRdn(LdapName dn) {
+               if (dn.size() < 2)
+                       throw new IllegalArgumentException(dn + " has no parent");
+               Rdn parentRdn = dn.getRdn(dn.size() - 2);
+               return parentRdn;
+       }
+
+       public static LdapName toLdapName(String distinguishedName) {
+               try {
+                       return new LdapName(distinguishedName);
+               } catch (InvalidNameException e) {
+                       throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e);
+               }
+       }
+
+       public static Rdn getLastRdn(LdapName dn) {
+               return dn.getRdn(dn.size() - 1);
+       }
+
+       public static String getLastRdnAsString(LdapName dn) {
+               return getLastRdn(dn).toString();
+       }
+
+       public static String getLastRdnValue(String dn) {
+               return getLastRdnValue(toLdapName(dn));
+       }
+
+       public static String getLastRdnValue(LdapName dn) {
+               return getLastRdn(dn).getValue().toString();
+       }
+
+       /** singleton */
+       private LdapNameUtils() {
+
+       }
+}
diff --git a/org.argeo.api.cli/.classpath b/org.argeo.api.cli/.classpath
deleted file mode 100644 (file)
index 81fe078..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<classpath>
-       <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="src" path="src"/>
-       <classpathentry kind="output" path="bin"/>
-</classpath>
diff --git a/org.argeo.api.cli/.project b/org.argeo.api.cli/.project
deleted file mode 100644 (file)
index 8f5cd41..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<projectDescription>
-       <name>org.argeo.api.cli</name>
-       <comment></comment>
-       <projects>
-       </projects>
-       <buildSpec>
-               <buildCommand>
-                       <name>org.eclipse.jdt.core.javabuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.ManifestBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-               <buildCommand>
-                       <name>org.eclipse.pde.SchemaBuilder</name>
-                       <arguments>
-                       </arguments>
-               </buildCommand>
-       </buildSpec>
-       <natures>
-               <nature>org.eclipse.pde.PluginNature</nature>
-               <nature>org.eclipse.jdt.core.javanature</nature>
-       </natures>
-</projectDescription>
diff --git a/org.argeo.api.cli/bnd.bnd b/org.argeo.api.cli/bnd.bnd
deleted file mode 100644 (file)
index e69de29..0000000
diff --git a/org.argeo.api.cli/build.properties b/org.argeo.api.cli/build.properties
deleted file mode 100644 (file)
index 34d2e4d..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
-               .
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandArgsException.java
deleted file mode 100644 (file)
index 935247f..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-package org.argeo.api.cli;
-
-/** Exception thrown when the provided arguments are not correct. */
-public class CommandArgsException extends IllegalArgumentException {
-       private static final long serialVersionUID = -7271050747105253935L;
-       private String commandName;
-       private volatile CommandsCli commandsCli;
-
-       public CommandArgsException(Exception cause) {
-               super(cause.getMessage(), cause);
-       }
-
-       public CommandArgsException(String message) {
-               super(message);
-       }
-
-       public String getCommandName() {
-               return commandName;
-       }
-
-       public void setCommandName(String commandName) {
-               this.commandName = commandName;
-       }
-
-       public CommandsCli getCommandsCli() {
-               return commandsCli;
-       }
-
-       public void setCommandsCli(CommandsCli commandsCli) {
-               this.commandsCli = commandsCli;
-       }
-
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandRuntimeException.java
deleted file mode 100644 (file)
index 52c0334..0000000
+++ /dev/null
@@ -1,35 +0,0 @@
-package org.argeo.api.cli;
-
-import java.util.List;
-
-/** {@link RuntimeException} referring during a command run. */
-public class CommandRuntimeException extends RuntimeException {
-       private static final long serialVersionUID = 5595999301269377128L;
-
-       private final DescribedCommand<?> command;
-       private final List<String> arguments;
-
-       public CommandRuntimeException(Throwable e, DescribedCommand<?> command, List<String> arguments) {
-               this(null, e, command, arguments);
-       }
-
-       public CommandRuntimeException(String message, DescribedCommand<?> command, List<String> arguments) {
-               this(message, null, command, arguments);
-       }
-
-       public CommandRuntimeException(String message, Throwable e, DescribedCommand<?> command, List<String> arguments) {
-               super(message == null ? "(" + command.getClass().getName() + " " + arguments.toString() + ")"
-                               : message + " (" + command.getClass().getName() + " " + arguments.toString() + ")", e);
-               this.command = command;
-               this.arguments = arguments;
-       }
-
-       public DescribedCommand<?> getCommand() {
-               return command;
-       }
-
-       public List<String> getArguments() {
-               return arguments;
-       }
-
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java b/org.argeo.api.cli/src/org/argeo/api/cli/CommandsCli.java
deleted file mode 100644 (file)
index 5bbfcfa..0000000
+++ /dev/null
@@ -1,157 +0,0 @@
-package org.argeo.api.cli;
-
-import java.io.StringWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeMap;
-import java.util.function.Function;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.CommandLineParser;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.MissingOptionException;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
-
-/** Base class for a CLI managing sub commands. */
-public abstract class CommandsCli implements DescribedCommand<Object> {
-       private final String commandName;
-       private Map<String, Function<List<String>, ?>> commands = new TreeMap<>();
-
-       protected final Options options = new Options();
-
-       public CommandsCli(String commandName) {
-               this.commandName = commandName;
-       }
-
-       @Override
-       public Object apply(List<String> args) {
-               String cmd = null;
-               List<String> newArgs = new ArrayList<>();
-               boolean isHelpOption = false;
-               try {
-                       CommandLineParser clParser = new DefaultParser();
-                       CommandLine commonCl = clParser.parse(getOptions(), args.toArray(new String[args.size()]), true);
-                       List<String> leftOvers = commonCl.getArgList();
-                       for (String arg : leftOvers) {
-                               if (arg.equals("--" + HelpCommand.HELP_OPTION.getLongOpt())) {
-                                       isHelpOption = true;
-                                       // TODO break?
-                               }
-
-                               if (!arg.startsWith("-") && cmd == null) {
-                                       cmd = arg;
-                               } else {
-                                       newArgs.add(arg);
-                               }
-                       }
-               } catch (ParseException e) {
-                       CommandArgsException cae = new CommandArgsException(e);
-                       throw cae;
-               }
-
-               Function<List<String>, ?> function = cmd != null ? getCommand(cmd) : getDefaultCommand();
-
-               // --help option
-               if (!(function instanceof CommandsCli))
-                       if (function instanceof DescribedCommand<?> command)
-                               if (isHelpOption) {
-                                       throw new PrintHelpRequestException(cmd, this);
-//                                     StringWriter out = new StringWriter();
-//                                     HelpCommand.printHelp(command, out);
-//                                     System.out.println(out.toString());
-//                                     return null;
-                               }
-
-               if (function == null)
-                       throw new IllegalArgumentException("Uknown command " + cmd);
-               try {
-                       Object value = function.apply(newArgs);
-                       return value != null ? value.toString() : null;
-               } catch (CommandArgsException e) {
-                       if (e.getCommandName() == null) {
-                               e.setCommandName(cmd);
-                               e.setCommandsCli(this);
-                       }
-                       throw e;
-               } catch (IllegalArgumentException e) {
-                       CommandArgsException cae = new CommandArgsException(e);
-                       cae.setCommandName(cmd);
-                       throw cae;
-               }
-       }
-
-       @Override
-       public Options getOptions() {
-               return options;
-       }
-
-       protected void addCommand(String cmd, Function<List<String>, ?> function) {
-               commands.put(cmd, function);
-
-       }
-
-       @Override
-       public String getUsage() {
-               return "[command]";
-       }
-
-       protected void addCommandsCli(CommandsCli commandsCli) {
-               addCommand(commandsCli.getCommandName(), commandsCli);
-               commandsCli.addCommand(HelpCommand.HELP, new HelpCommand(this, commandsCli));
-       }
-
-       public String getCommandName() {
-               return commandName;
-       }
-
-       public Set<String> getSubCommands() {
-               return commands.keySet();
-       }
-
-       public Function<List<String>, ?> getCommand(String command) {
-               return commands.get(command);
-       }
-
-       public HelpCommand getHelpCommand() {
-               return (HelpCommand) getCommand(HelpCommand.HELP);
-       }
-
-       public Function<List<String>, String> getDefaultCommand() {
-               return getHelpCommand();
-       }
-
-       /** In order to implement quickly a main method. */
-       public static void mainImpl(CommandsCli cli, String[] args) {
-               try {
-                       cli.addCommand(HelpCommand.HELP, new HelpCommand(null, cli));
-                       Object output = cli.apply(Arrays.asList(args));
-                       if (output != null)
-                               System.out.println(output);
-                       System.exit(0);
-               } catch (PrintHelpRequestException e) {
-                       StringWriter out = new StringWriter();
-                       HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
-                       System.out.println(out.toString());
-               } catch (CommandArgsException e) {
-                       System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage());
-                       Throwable cause = e.getCause();
-                       if (!(cause instanceof MissingOptionException))
-                               e.printStackTrace();
-                       if (e.getCommandName() != null) {
-                               StringWriter out = new StringWriter();
-                               HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
-                               System.err.println(out.toString());
-                       } else {
-                               e.printStackTrace();
-                       }
-                       System.exit(1);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-       }
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java b/org.argeo.api.cli/src/org/argeo/api/cli/DescribedCommand.java
deleted file mode 100644 (file)
index 51cb2ce..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-package org.argeo.api.cli;
-
-import java.io.StringWriter;
-import java.util.Arrays;
-import java.util.List;
-import java.util.function.Function;
-
-import org.apache.commons.cli.CommandLine;
-import org.apache.commons.cli.DefaultParser;
-import org.apache.commons.cli.Options;
-import org.apache.commons.cli.ParseException;
-
-/** A command that can be described. */
-public interface DescribedCommand<T> extends Function<List<String>, T> {
-       default Options getOptions() {
-               return new Options();
-       }
-
-       String getDescription();
-
-       default String getUsage() {
-               return null;
-       }
-
-       default String getExamples() {
-               return null;
-       }
-
-       default CommandLine toCommandLine(List<String> args) {
-               try {
-                       DefaultParser parser = new DefaultParser();
-                       return parser.parse(getOptions(), args.toArray(new String[args.size()]));
-               } catch (ParseException e) {
-                       throw new CommandArgsException(e);
-               }
-       }
-
-       /** In order to implement quickly a main method. */
-       public static void mainImpl(DescribedCommand<?> command, String[] args) {
-               try {
-                       Object output = command.apply(Arrays.asList(args));
-                       System.out.println(output);
-                       System.exit(0);
-               } catch (PrintHelpRequestException e) {
-                       StringWriter out = new StringWriter();
-                       HelpCommand.printHelp(command, out);
-                       System.out.println(out.toString());
-                       System.exit(1);
-               } catch (IllegalArgumentException e) {
-                       StringWriter out = new StringWriter();
-                       HelpCommand.printHelp(command, out);
-                       System.err.println(out.toString());
-                       System.exit(1);
-               } catch (Exception e) {
-                       e.printStackTrace();
-                       System.exit(1);
-               }
-       }
-
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java b/org.argeo.api.cli/src/org/argeo/api/cli/HelpCommand.java
deleted file mode 100644 (file)
index cd51d76..0000000
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.argeo.api.cli;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.List;
-import java.util.function.Function;
-
-import org.apache.commons.cli.HelpFormatter;
-import org.apache.commons.cli.Option;
-import org.apache.commons.cli.Options;
-
-/** A special command that can describe {@link DescribedCommand}. */
-public class HelpCommand implements DescribedCommand<String> {
-       /**
-        * System property forcing the root command to this value (typically the name of
-        * a script).
-        */
-       public final static String ROOT_COMMAND_PROPERTY = "org.argeo.api.cli.rootCommand";
-
-       final static String HELP = "help";
-       final static Option HELP_OPTION = Option.builder().longOpt(HELP).desc("print this help").build();
-
-       private CommandsCli commandsCli;
-       private CommandsCli parentCommandsCli;
-
-       // Help formatting
-       private static int helpWidth = 80;
-       private static int helpLeftPad = 4;
-       private static int helpDescPad = 20;
-
-       public HelpCommand(CommandsCli parentCommandsCli, CommandsCli commandsCli) {
-               super();
-               this.parentCommandsCli = parentCommandsCli;
-               this.commandsCli = commandsCli;
-       }
-
-       @Override
-       public String apply(List<String> args) {
-               StringWriter out = new StringWriter();
-
-               if (args.size() == 0) {// overview
-                       printHelp(commandsCli, out);
-               } else {
-                       String cmd = args.get(0);
-                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
-                       if (function == null)
-                               return "Command " + cmd + " not found.";
-                       Options options;
-                       String examples;
-                       DescribedCommand<?> command = null;
-                       if (function instanceof DescribedCommand) {
-                               command = (DescribedCommand<?>) function;
-                               options = command.getOptions();
-                               examples = command.getExamples();
-                       } else {
-                               options = new Options();
-                               examples = null;
-                       }
-                       String description = getShortDescription(function);
-                       String commandCall = getCommandUsage(cmd, command);
-                       HelpFormatter formatter = new HelpFormatter();
-                       formatter.printHelp(new PrintWriter(out), helpWidth, commandCall, description, options, helpLeftPad,
-                                       helpDescPad, examples, false);
-               }
-               return out.toString();
-       }
-
-       private static String getShortDescription(Function<List<String>, ?> function) {
-               if (function instanceof DescribedCommand) {
-                       return ((DescribedCommand<?>) function).getDescription();
-               } else {
-                       return function.toString();
-               }
-       }
-
-       public String getCommandUsage(String cmd, DescribedCommand<?> command) {
-               String commandCall = getCommandCall(commandsCli) + " " + cmd;
-               assert command != null;
-               if (command != null && command.getUsage() != null) {
-                       commandCall = commandCall + " " + command.getUsage();
-               }
-               return commandCall;
-       }
-
-       @Override
-       public String getDescription() {
-               return "Shows this help or describes a command";
-       }
-
-       @Override
-       public String getUsage() {
-               return "[command]";
-       }
-
-       public CommandsCli getParentCommandsCli() {
-               return parentCommandsCli;
-       }
-
-       protected String getCommandCall(CommandsCli commandsCli) {
-               HelpCommand hc = commandsCli.getHelpCommand();
-               if (hc.getParentCommandsCli() != null) {
-                       return getCommandCall(hc.getParentCommandsCli()) + " " + commandsCli.getCommandName();
-               } else {
-                       String rootCommand = System.getProperty(ROOT_COMMAND_PROPERTY);
-                       if (rootCommand != null)
-                               return rootCommand;
-                       return commandsCli.getCommandName();
-               }
-       }
-
-       public static void printHelp(DescribedCommand<?> command, StringWriter out) {
-               String usage = "java " + command.getClass().getName()
-                               + (command.getUsage() != null ? " " + command.getUsage() : "");
-               HelpFormatter formatter = new HelpFormatter();
-               Options options = command.getOptions();
-               options.addOption(HelpCommand.HELP_OPTION);
-               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
-                               helpDescPad, command.getExamples(), false);
-
-       }
-
-       public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) {
-               if (commandName == null) {
-                       printHelp(commandsCli, out);
-                       return;
-               }
-               DescribedCommand<?> command = (DescribedCommand<?>) commandsCli.getCommand(commandName);
-               String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command);
-               HelpFormatter formatter = new HelpFormatter();
-               Options options = command.getOptions();
-               options.addOption(HelpCommand.HELP_OPTION);
-               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
-                               helpDescPad, command.getExamples(), false);
-
-       }
-
-       public static void printHelp(CommandsCli commandsCli, StringWriter out) {
-               out.append(commandsCli.getDescription()).append('\n');
-               String leftPad = spaces(helpLeftPad);
-               for (String cmd : commandsCli.getSubCommands()) {
-                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
-                       assert function != null;
-                       out.append(leftPad);
-                       out.append(cmd);
-                       // TODO deal with long commands
-                       out.append(spaces(helpDescPad - cmd.length()));
-                       out.append(getShortDescription(function));
-                       out.append('\n');
-               }
-       }
-
-       private static String spaces(int count) {
-               // Java 11
-               // return " ".repeat(count);
-               if (count <= 0)
-                       return "";
-               else {
-                       StringBuilder sb = new StringBuilder(count);
-                       for (int i = 0; i < count; i++)
-                               sb.append(' ');
-                       return sb.toString();
-               }
-       }
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java b/org.argeo.api.cli/src/org/argeo/api/cli/PrintHelpRequestException.java
deleted file mode 100644 (file)
index 017386b..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.argeo.api.cli;
-
-/** An exception indicating that help should be printed. */
-class PrintHelpRequestException extends RuntimeException {
-
-       private static final long serialVersionUID = -9029122270660656639L;
-
-       private String commandName;
-       private volatile CommandsCli commandsCli;
-
-       public PrintHelpRequestException(String commandName, CommandsCli commandsCli) {
-               super();
-               this.commandName = commandName;
-               this.commandsCli = commandsCli;
-       }
-
-       public static long getSerialversionuid() {
-               return serialVersionUID;
-       }
-
-       public String getCommandName() {
-               return commandName;
-       }
-
-       public CommandsCli getCommandsCli() {
-               return commandsCli;
-       }
-
-}
diff --git a/org.argeo.api.cli/src/org/argeo/api/cli/package-info.java b/org.argeo.api.cli/src/org/argeo/api/cli/package-info.java
deleted file mode 100644 (file)
index 114fd02..0000000
+++ /dev/null
@@ -1,2 +0,0 @@
-/** Command line API. */
-package org.argeo.api.cli;
\ No newline at end of file
diff --git a/org.argeo.api.cms/META-INF/.gitignore b/org.argeo.api.cms/META-INF/.gitignore
deleted file mode 100644 (file)
index 4854a41..0000000
+++ /dev/null
@@ -1 +0,0 @@
-/MANIFEST.MF
index 0b7c5a4da7b489aa09accf8fa7e242298817e1f4..19a9abdf282f3f278db9fc520d205bd1b1fbcaa6 100644 (file)
@@ -1,7 +1,6 @@
 Import-Package: \
 javax.transaction.xa,\
 javax.security.*,\
-org.osgi.service.useradmin,\
 *
 
 Export-Package: org.argeo.api.cms.*
\ No newline at end of file
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/auth/ImpliedByPrincipal.java b/org.argeo.api.cms/src/org/argeo/api/cms/auth/ImpliedByPrincipal.java
new file mode 100644 (file)
index 0000000..21a6325
--- /dev/null
@@ -0,0 +1,72 @@
+package org.argeo.api.cms.auth;
+
+import java.security.Principal;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.xml.namespace.QName;
+
+/**
+ * A {@link Principal} which has been implied by an authorisation. If it is
+ * empty it means this is an additional identity, otherwise it lists the users
+ * (typically the logged-in user but possibly empty {@link ImpliedByPrincipal}s)
+ * which have implied it. When an additional identity is removed, the related
+ * {@link ImpliedByPrincipal}s can thus be removed.
+ */
+public final class ImpliedByPrincipal implements Principal {
+       private final String name;
+       private final QName roleName;
+       private final boolean systemRole;
+       private final String context;
+
+       private Set<Principal> causes = new HashSet<Principal>();
+
+       public ImpliedByPrincipal(String name, Principal userPrincipal) {
+               this.name = name;
+               roleName = RoleNameUtils.getLastRdnAsName(name);
+               systemRole = RoleNameUtils.isSystemRole(roleName);
+               context = RoleNameUtils.getContext(name);
+               if (userPrincipal != null)
+                       causes.add(userPrincipal);
+       }
+
+       public String getName() {
+               return name;
+       }
+
+       /*
+        * OBJECT
+        */
+
+       public QName getRoleName() {
+               return roleName;
+       }
+
+       public String getContext() {
+               return context;
+       }
+
+       public boolean isSystemRole() {
+               return systemRole;
+       }
+
+       @Override
+       public int hashCode() {
+               return name.hashCode();
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               if (obj instanceof ImpliedByPrincipal) {
+                       ImpliedByPrincipal that = (ImpliedByPrincipal) obj;
+                       // TODO check members too?
+                       return name.equals(that.name);
+               }
+               return false;
+       }
+
+       @Override
+       public String toString() {
+               return name.toString();
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/auth/RoleNameUtils.java b/org.argeo.api.cms/src/org/argeo/api/cms/auth/RoleNameUtils.java
new file mode 100644 (file)
index 0000000..52e2380
--- /dev/null
@@ -0,0 +1,41 @@
+package org.argeo.api.cms.auth;
+
+import static org.argeo.api.acr.RuntimeNamespaceContext.getNamespaceContext;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.ArgeoNamespace;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.ldap.LdapNameUtils;
+
+/** Simplifies analysis of system roles. */
+public class RoleNameUtils {
+       public static String getLastRdnValue(String dn) {
+               return LdapNameUtils.getLastRdnValue(dn);
+//             // we don't use LdapName for portability with Android
+//             // TODO make it more robust
+//             String[] parts = dn.split(",");
+//             String[] rdn = parts[0].split("=");
+//             return rdn[1];
+       }
+
+       public static QName getLastRdnAsName(String dn) {
+               String cn = getLastRdnValue(dn);
+               QName roleName = NamespaceUtils.parsePrefixedName(getNamespaceContext(), cn);
+               return roleName;
+       }
+
+       public static boolean isSystemRole(QName roleName) {
+               return roleName.getNamespaceURI().equals(ArgeoNamespace.ROLE_NAMESPACE_URI);
+       }
+
+       public static String getParent(String dn) {
+               int index = dn.indexOf(',');
+               return dn.substring(index + 1);
+       }
+
+       /** Up two levels. */
+       public static String getContext(String dn) {
+               return getParent(getParent(dn));
+       }
+}
diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/auth/SystemRole.java b/org.argeo.api.cms/src/org/argeo/api/cms/auth/SystemRole.java
new file mode 100644 (file)
index 0000000..9880851
--- /dev/null
@@ -0,0 +1,47 @@
+package org.argeo.api.cms.auth;
+
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.cms.CmsConstants;
+
+/** A programmatic role. */
+public interface SystemRole {
+       QName qName();
+
+       /** Whether this role is implied for this authenticated user. */
+       default boolean implied(Subject subject, String context) {
+               return implied(qName(), subject, context);
+       }
+
+       /** Whether this role is implied for this distinguished name. */
+       default boolean implied(String dn, String context) {
+               String roleContext = RoleNameUtils.getContext(dn);
+               QName roleName = RoleNameUtils.getLastRdnAsName(dn);
+               return roleContext.equalsIgnoreCase(context) && qName().equals(roleName);
+       }
+
+       /**
+        * Whether this role is implied for this authenticated subject. If context is
+        * <code>null</code>, it is not considered; this should be used to build user
+        * interfaces, but not to authorise.
+        */
+       static boolean implied(QName name, Subject subject, String context) {
+               Set<ImpliedByPrincipal> roles = subject.getPrincipals(ImpliedByPrincipal.class);
+               for (ImpliedByPrincipal role : roles) {
+                       if (role.isSystemRole()) {
+                               if (role.getRoleName().equals(name)) {
+                                       // !! if context is not specified, it is considered irrelevant
+                                       if (context == null)
+                                               return true;
+                                       if (role.getContext().equalsIgnoreCase(context)
+                                                       || role.getContext().equals(CmsConstants.NODE_BASEDN))
+                                               return true;
+                               }
+                       }
+               }
+               return false;
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/CommandArgsException.java b/org.argeo.cms/src/org/argeo/api/cli/CommandArgsException.java
new file mode 100644 (file)
index 0000000..935247f
--- /dev/null
@@ -0,0 +1,33 @@
+package org.argeo.api.cli;
+
+/** Exception thrown when the provided arguments are not correct. */
+public class CommandArgsException extends IllegalArgumentException {
+       private static final long serialVersionUID = -7271050747105253935L;
+       private String commandName;
+       private volatile CommandsCli commandsCli;
+
+       public CommandArgsException(Exception cause) {
+               super(cause.getMessage(), cause);
+       }
+
+       public CommandArgsException(String message) {
+               super(message);
+       }
+
+       public String getCommandName() {
+               return commandName;
+       }
+
+       public void setCommandName(String commandName) {
+               this.commandName = commandName;
+       }
+
+       public CommandsCli getCommandsCli() {
+               return commandsCli;
+       }
+
+       public void setCommandsCli(CommandsCli commandsCli) {
+               this.commandsCli = commandsCli;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/CommandRuntimeException.java b/org.argeo.cms/src/org/argeo/api/cli/CommandRuntimeException.java
new file mode 100644 (file)
index 0000000..52c0334
--- /dev/null
@@ -0,0 +1,35 @@
+package org.argeo.api.cli;
+
+import java.util.List;
+
+/** {@link RuntimeException} referring during a command run. */
+public class CommandRuntimeException extends RuntimeException {
+       private static final long serialVersionUID = 5595999301269377128L;
+
+       private final DescribedCommand<?> command;
+       private final List<String> arguments;
+
+       public CommandRuntimeException(Throwable e, DescribedCommand<?> command, List<String> arguments) {
+               this(null, e, command, arguments);
+       }
+
+       public CommandRuntimeException(String message, DescribedCommand<?> command, List<String> arguments) {
+               this(message, null, command, arguments);
+       }
+
+       public CommandRuntimeException(String message, Throwable e, DescribedCommand<?> command, List<String> arguments) {
+               super(message == null ? "(" + command.getClass().getName() + " " + arguments.toString() + ")"
+                               : message + " (" + command.getClass().getName() + " " + arguments.toString() + ")", e);
+               this.command = command;
+               this.arguments = arguments;
+       }
+
+       public DescribedCommand<?> getCommand() {
+               return command;
+       }
+
+       public List<String> getArguments() {
+               return arguments;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/CommandsCli.java b/org.argeo.cms/src/org/argeo/api/cli/CommandsCli.java
new file mode 100644 (file)
index 0000000..5bbfcfa
--- /dev/null
@@ -0,0 +1,157 @@
+package org.argeo.api.cli;
+
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.MissingOptionException;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** Base class for a CLI managing sub commands. */
+public abstract class CommandsCli implements DescribedCommand<Object> {
+       private final String commandName;
+       private Map<String, Function<List<String>, ?>> commands = new TreeMap<>();
+
+       protected final Options options = new Options();
+
+       public CommandsCli(String commandName) {
+               this.commandName = commandName;
+       }
+
+       @Override
+       public Object apply(List<String> args) {
+               String cmd = null;
+               List<String> newArgs = new ArrayList<>();
+               boolean isHelpOption = false;
+               try {
+                       CommandLineParser clParser = new DefaultParser();
+                       CommandLine commonCl = clParser.parse(getOptions(), args.toArray(new String[args.size()]), true);
+                       List<String> leftOvers = commonCl.getArgList();
+                       for (String arg : leftOvers) {
+                               if (arg.equals("--" + HelpCommand.HELP_OPTION.getLongOpt())) {
+                                       isHelpOption = true;
+                                       // TODO break?
+                               }
+
+                               if (!arg.startsWith("-") && cmd == null) {
+                                       cmd = arg;
+                               } else {
+                                       newArgs.add(arg);
+                               }
+                       }
+               } catch (ParseException e) {
+                       CommandArgsException cae = new CommandArgsException(e);
+                       throw cae;
+               }
+
+               Function<List<String>, ?> function = cmd != null ? getCommand(cmd) : getDefaultCommand();
+
+               // --help option
+               if (!(function instanceof CommandsCli))
+                       if (function instanceof DescribedCommand<?> command)
+                               if (isHelpOption) {
+                                       throw new PrintHelpRequestException(cmd, this);
+//                                     StringWriter out = new StringWriter();
+//                                     HelpCommand.printHelp(command, out);
+//                                     System.out.println(out.toString());
+//                                     return null;
+                               }
+
+               if (function == null)
+                       throw new IllegalArgumentException("Uknown command " + cmd);
+               try {
+                       Object value = function.apply(newArgs);
+                       return value != null ? value.toString() : null;
+               } catch (CommandArgsException e) {
+                       if (e.getCommandName() == null) {
+                               e.setCommandName(cmd);
+                               e.setCommandsCli(this);
+                       }
+                       throw e;
+               } catch (IllegalArgumentException e) {
+                       CommandArgsException cae = new CommandArgsException(e);
+                       cae.setCommandName(cmd);
+                       throw cae;
+               }
+       }
+
+       @Override
+       public Options getOptions() {
+               return options;
+       }
+
+       protected void addCommand(String cmd, Function<List<String>, ?> function) {
+               commands.put(cmd, function);
+
+       }
+
+       @Override
+       public String getUsage() {
+               return "[command]";
+       }
+
+       protected void addCommandsCli(CommandsCli commandsCli) {
+               addCommand(commandsCli.getCommandName(), commandsCli);
+               commandsCli.addCommand(HelpCommand.HELP, new HelpCommand(this, commandsCli));
+       }
+
+       public String getCommandName() {
+               return commandName;
+       }
+
+       public Set<String> getSubCommands() {
+               return commands.keySet();
+       }
+
+       public Function<List<String>, ?> getCommand(String command) {
+               return commands.get(command);
+       }
+
+       public HelpCommand getHelpCommand() {
+               return (HelpCommand) getCommand(HelpCommand.HELP);
+       }
+
+       public Function<List<String>, String> getDefaultCommand() {
+               return getHelpCommand();
+       }
+
+       /** In order to implement quickly a main method. */
+       public static void mainImpl(CommandsCli cli, String[] args) {
+               try {
+                       cli.addCommand(HelpCommand.HELP, new HelpCommand(null, cli));
+                       Object output = cli.apply(Arrays.asList(args));
+                       if (output != null)
+                               System.out.println(output);
+                       System.exit(0);
+               } catch (PrintHelpRequestException e) {
+                       StringWriter out = new StringWriter();
+                       HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
+                       System.out.println(out.toString());
+               } catch (CommandArgsException e) {
+                       System.err.println("Wrong arguments " + Arrays.toString(args) + ": " + e.getMessage());
+                       Throwable cause = e.getCause();
+                       if (!(cause instanceof MissingOptionException))
+                               e.printStackTrace();
+                       if (e.getCommandName() != null) {
+                               StringWriter out = new StringWriter();
+                               HelpCommand.printHelp(e.getCommandsCli(), e.getCommandName(), out);
+                               System.err.println(out.toString());
+                       } else {
+                               e.printStackTrace();
+                       }
+                       System.exit(1);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/DescribedCommand.java b/org.argeo.cms/src/org/argeo/api/cli/DescribedCommand.java
new file mode 100644 (file)
index 0000000..51cb2ce
--- /dev/null
@@ -0,0 +1,60 @@
+package org.argeo.api.cli;
+
+import java.io.StringWriter;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+/** A command that can be described. */
+public interface DescribedCommand<T> extends Function<List<String>, T> {
+       default Options getOptions() {
+               return new Options();
+       }
+
+       String getDescription();
+
+       default String getUsage() {
+               return null;
+       }
+
+       default String getExamples() {
+               return null;
+       }
+
+       default CommandLine toCommandLine(List<String> args) {
+               try {
+                       DefaultParser parser = new DefaultParser();
+                       return parser.parse(getOptions(), args.toArray(new String[args.size()]));
+               } catch (ParseException e) {
+                       throw new CommandArgsException(e);
+               }
+       }
+
+       /** In order to implement quickly a main method. */
+       public static void mainImpl(DescribedCommand<?> command, String[] args) {
+               try {
+                       Object output = command.apply(Arrays.asList(args));
+                       System.out.println(output);
+                       System.exit(0);
+               } catch (PrintHelpRequestException e) {
+                       StringWriter out = new StringWriter();
+                       HelpCommand.printHelp(command, out);
+                       System.out.println(out.toString());
+                       System.exit(1);
+               } catch (IllegalArgumentException e) {
+                       StringWriter out = new StringWriter();
+                       HelpCommand.printHelp(command, out);
+                       System.err.println(out.toString());
+                       System.exit(1);
+               } catch (Exception e) {
+                       e.printStackTrace();
+                       System.exit(1);
+               }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/HelpCommand.java b/org.argeo.cms/src/org/argeo/api/cli/HelpCommand.java
new file mode 100644 (file)
index 0000000..cd51d76
--- /dev/null
@@ -0,0 +1,164 @@
+package org.argeo.api.cli;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.List;
+import java.util.function.Function;
+
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+
+/** A special command that can describe {@link DescribedCommand}. */
+public class HelpCommand implements DescribedCommand<String> {
+       /**
+        * System property forcing the root command to this value (typically the name of
+        * a script).
+        */
+       public final static String ROOT_COMMAND_PROPERTY = "org.argeo.api.cli.rootCommand";
+
+       final static String HELP = "help";
+       final static Option HELP_OPTION = Option.builder().longOpt(HELP).desc("print this help").build();
+
+       private CommandsCli commandsCli;
+       private CommandsCli parentCommandsCli;
+
+       // Help formatting
+       private static int helpWidth = 80;
+       private static int helpLeftPad = 4;
+       private static int helpDescPad = 20;
+
+       public HelpCommand(CommandsCli parentCommandsCli, CommandsCli commandsCli) {
+               super();
+               this.parentCommandsCli = parentCommandsCli;
+               this.commandsCli = commandsCli;
+       }
+
+       @Override
+       public String apply(List<String> args) {
+               StringWriter out = new StringWriter();
+
+               if (args.size() == 0) {// overview
+                       printHelp(commandsCli, out);
+               } else {
+                       String cmd = args.get(0);
+                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+                       if (function == null)
+                               return "Command " + cmd + " not found.";
+                       Options options;
+                       String examples;
+                       DescribedCommand<?> command = null;
+                       if (function instanceof DescribedCommand) {
+                               command = (DescribedCommand<?>) function;
+                               options = command.getOptions();
+                               examples = command.getExamples();
+                       } else {
+                               options = new Options();
+                               examples = null;
+                       }
+                       String description = getShortDescription(function);
+                       String commandCall = getCommandUsage(cmd, command);
+                       HelpFormatter formatter = new HelpFormatter();
+                       formatter.printHelp(new PrintWriter(out), helpWidth, commandCall, description, options, helpLeftPad,
+                                       helpDescPad, examples, false);
+               }
+               return out.toString();
+       }
+
+       private static String getShortDescription(Function<List<String>, ?> function) {
+               if (function instanceof DescribedCommand) {
+                       return ((DescribedCommand<?>) function).getDescription();
+               } else {
+                       return function.toString();
+               }
+       }
+
+       public String getCommandUsage(String cmd, DescribedCommand<?> command) {
+               String commandCall = getCommandCall(commandsCli) + " " + cmd;
+               assert command != null;
+               if (command != null && command.getUsage() != null) {
+                       commandCall = commandCall + " " + command.getUsage();
+               }
+               return commandCall;
+       }
+
+       @Override
+       public String getDescription() {
+               return "Shows this help or describes a command";
+       }
+
+       @Override
+       public String getUsage() {
+               return "[command]";
+       }
+
+       public CommandsCli getParentCommandsCli() {
+               return parentCommandsCli;
+       }
+
+       protected String getCommandCall(CommandsCli commandsCli) {
+               HelpCommand hc = commandsCli.getHelpCommand();
+               if (hc.getParentCommandsCli() != null) {
+                       return getCommandCall(hc.getParentCommandsCli()) + " " + commandsCli.getCommandName();
+               } else {
+                       String rootCommand = System.getProperty(ROOT_COMMAND_PROPERTY);
+                       if (rootCommand != null)
+                               return rootCommand;
+                       return commandsCli.getCommandName();
+               }
+       }
+
+       public static void printHelp(DescribedCommand<?> command, StringWriter out) {
+               String usage = "java " + command.getClass().getName()
+                               + (command.getUsage() != null ? " " + command.getUsage() : "");
+               HelpFormatter formatter = new HelpFormatter();
+               Options options = command.getOptions();
+               options.addOption(HelpCommand.HELP_OPTION);
+               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
+                               helpDescPad, command.getExamples(), false);
+
+       }
+
+       public static void printHelp(CommandsCli commandsCli, String commandName, StringWriter out) {
+               if (commandName == null) {
+                       printHelp(commandsCli, out);
+                       return;
+               }
+               DescribedCommand<?> command = (DescribedCommand<?>) commandsCli.getCommand(commandName);
+               String usage = commandsCli.getHelpCommand().getCommandUsage(commandName, command);
+               HelpFormatter formatter = new HelpFormatter();
+               Options options = command.getOptions();
+               options.addOption(HelpCommand.HELP_OPTION);
+               formatter.printHelp(new PrintWriter(out), helpWidth, usage, command.getDescription(), options, helpLeftPad,
+                               helpDescPad, command.getExamples(), false);
+
+       }
+
+       public static void printHelp(CommandsCli commandsCli, StringWriter out) {
+               out.append(commandsCli.getDescription()).append('\n');
+               String leftPad = spaces(helpLeftPad);
+               for (String cmd : commandsCli.getSubCommands()) {
+                       Function<List<String>, ?> function = commandsCli.getCommand(cmd);
+                       assert function != null;
+                       out.append(leftPad);
+                       out.append(cmd);
+                       // TODO deal with long commands
+                       out.append(spaces(helpDescPad - cmd.length()));
+                       out.append(getShortDescription(function));
+                       out.append('\n');
+               }
+       }
+
+       private static String spaces(int count) {
+               // Java 11
+               // return " ".repeat(count);
+               if (count <= 0)
+                       return "";
+               else {
+                       StringBuilder sb = new StringBuilder(count);
+                       for (int i = 0; i < count; i++)
+                               sb.append(' ');
+                       return sb.toString();
+               }
+       }
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/PrintHelpRequestException.java b/org.argeo.cms/src/org/argeo/api/cli/PrintHelpRequestException.java
new file mode 100644 (file)
index 0000000..017386b
--- /dev/null
@@ -0,0 +1,29 @@
+package org.argeo.api.cli;
+
+/** An exception indicating that help should be printed. */
+class PrintHelpRequestException extends RuntimeException {
+
+       private static final long serialVersionUID = -9029122270660656639L;
+
+       private String commandName;
+       private volatile CommandsCli commandsCli;
+
+       public PrintHelpRequestException(String commandName, CommandsCli commandsCli) {
+               super();
+               this.commandName = commandName;
+               this.commandsCli = commandsCli;
+       }
+
+       public static long getSerialversionuid() {
+               return serialVersionUID;
+       }
+
+       public String getCommandName() {
+               return commandName;
+       }
+
+       public CommandsCli getCommandsCli() {
+               return commandsCli;
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/api/cli/package-info.java b/org.argeo.cms/src/org/argeo/api/cli/package-info.java
new file mode 100644 (file)
index 0000000..114fd02
--- /dev/null
@@ -0,0 +1,2 @@
+/** Command line API. */
+package org.argeo.api.cli;
\ No newline at end of file
index ad12a8665840565a75eb619185d08cb82c40ed6f..c879a4d2d7f262145b3c216d486a67959ad8a2c7 100644 (file)
@@ -17,8 +17,10 @@ import org.argeo.api.acr.NamespaceUtils;
 import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.api.cms.CmsSessionId;
+import org.argeo.api.cms.auth.ImpliedByPrincipal;
+import org.argeo.api.cms.auth.RoleNameUtils;
+import org.argeo.api.cms.auth.SystemRole;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.argeo.cms.internal.auth.ImpliedByPrincipal;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.cms.util.CurrentSubject;
 import org.osgi.service.useradmin.Authorization;
diff --git a/org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java b/org.argeo.cms/src/org/argeo/cms/RoleNameUtils.java
deleted file mode 100644 (file)
index 04302c4..0000000
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.argeo.cms;
-
-import static org.argeo.api.acr.RuntimeNamespaceContext.getNamespaceContext;
-
-import javax.xml.namespace.QName;
-
-import org.argeo.api.acr.ArgeoNamespace;
-import org.argeo.api.acr.NamespaceUtils;
-import org.argeo.cms.directory.ldap.LdapNameUtils;
-
-/** Simplifies analysis of system roles. */
-public class RoleNameUtils {
-       public static String getLastRdnValue(String dn) {
-               return LdapNameUtils.getLastRdnValue(dn);
-//             // we don't use LdapName for portability with Android
-//             // TODO make it more robust
-//             String[] parts = dn.split(",");
-//             String[] rdn = parts[0].split("=");
-//             return rdn[1];
-       }
-
-       public static QName getLastRdnAsName(String dn) {
-               String cn = getLastRdnValue(dn);
-               QName roleName = NamespaceUtils.parsePrefixedName(getNamespaceContext(), cn);
-               return roleName;
-       }
-
-       public static boolean isSystemRole(QName roleName) {
-               return roleName.getNamespaceURI().equals(ArgeoNamespace.ROLE_NAMESPACE_URI);
-       }
-
-       public static String getParent(String dn) {
-               int index = dn.indexOf(',');
-               return dn.substring(index + 1);
-       }
-
-       /** Up two levels. */
-       public static String getContext(String dn) {
-               return getParent(getParent(dn));
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/SystemRole.java b/org.argeo.cms/src/org/argeo/cms/SystemRole.java
deleted file mode 100644 (file)
index 9564399..0000000
+++ /dev/null
@@ -1,48 +0,0 @@
-package org.argeo.cms;
-
-import java.util.Set;
-
-import javax.security.auth.Subject;
-import javax.xml.namespace.QName;
-
-import org.argeo.api.cms.CmsConstants;
-import org.argeo.cms.internal.auth.ImpliedByPrincipal;
-
-/** A programmatic role. */
-public interface SystemRole {
-       QName qName();
-
-       /** Whether this role is implied for this authenticated user. */
-       default boolean implied(Subject subject, String context) {
-               return implied(qName(), subject, context);
-       }
-
-       /** Whether this role is implied for this distinguished name. */
-       default boolean implied(String dn, String context) {
-               String roleContext = RoleNameUtils.getContext(dn);
-               QName roleName = RoleNameUtils.getLastRdnAsName(dn);
-               return roleContext.equalsIgnoreCase(context) && qName().equals(roleName);
-       }
-
-       /**
-        * Whether this role is implied for this authenticated subject. If context is
-        * <code>null</code>, it is not considered; this should be used to build user
-        * interfaces, but not to authorise.
-        */
-       static boolean implied(QName name, Subject subject, String context) {
-               Set<ImpliedByPrincipal> roles = subject.getPrincipals(ImpliedByPrincipal.class);
-               for (ImpliedByPrincipal role : roles) {
-                       if (role.isSystemRole()) {
-                               if (role.getRoleName().equals(name)) {
-                                       // !! if context is not specified, it is considered irrelevant
-                                       if (context == null)
-                                               return true;
-                                       if (role.getContext().equalsIgnoreCase(context)
-                                                       || role.getContext().equals(CmsConstants.NODE_BASEDN))
-                                               return true;
-                               }
-                       }
-               }
-               return false;
-       }
-}
index 37992072482e0cf36b1a08ed899de0afcf2f8601..6657e794e14f664ae07ec552b11324255c435723 100644 (file)
@@ -23,8 +23,8 @@ import org.argeo.api.cms.CmsConstants;
 import org.argeo.api.cms.CmsSession;
 import org.argeo.api.cms.CmsSessionId;
 import org.argeo.api.cms.DataAdminPrincipal;
+import org.argeo.api.cms.auth.ImpliedByPrincipal;
 import org.argeo.cms.internal.auth.CmsSessionImpl;
-import org.argeo.cms.internal.auth.ImpliedByPrincipal;
 import org.argeo.cms.internal.auth.RemoteCmsSessionImpl;
 import org.argeo.cms.internal.runtime.CmsContextImpl;
 import org.argeo.cms.osgi.useradmin.AuthenticatingUser;
index 64dbd0f6ef2f8a24ec6f31577f31351a9158ac43..87daa2f95780aa0d0d13e2d12e0a5984c7f34d79 100644 (file)
@@ -4,7 +4,7 @@ import javax.xml.namespace.QName;
 
 import org.argeo.api.acr.ArgeoNamespace;
 import org.argeo.api.acr.ContentName;
-import org.argeo.cms.SystemRole;
+import org.argeo.api.cms.auth.SystemRole;
 
 /** Standard CMS system roles. */
 public enum CmsSystemRole implements SystemRole {
index 5dffcb63aa61550cc7606e837c835c678a2ca5b7..39355c3c415248b16076547092cf2cad98a6729a 100644 (file)
@@ -1,6 +1,6 @@
 package org.argeo.cms.directory.ldap;
 
-import static org.argeo.cms.directory.ldap.LdapNameUtils.toLdapName;
+import static org.argeo.api.acr.ldap.LdapNameUtils.toLdapName;
 
 import java.io.File;
 import java.net.URI;
@@ -27,6 +27,7 @@ import javax.naming.ldap.Rdn;
 import javax.transaction.xa.XAResource;
 
 import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapNameUtils;
 import org.argeo.api.acr.ldap.LdapObj;
 import org.argeo.api.cms.directory.CmsDirectory;
 import org.argeo.api.cms.directory.HierarchyUnit;
index cdc1c9fe68bfbe0de3faf968460efd5f4bd973f2..ff785638d53a211a0eaaea88f04ab2ed7e28e552 100644 (file)
@@ -20,6 +20,7 @@ import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 
 import org.argeo.api.acr.ldap.LdapAttr;
+import org.argeo.api.acr.ldap.LdapNameUtils;
 import org.argeo.api.acr.ldap.LdapObj;
 import org.argeo.api.cms.directory.HierarchyUnit;
 
index b60ee0c68935cc3e08248f1a69902b8d6aebe393..81b20f0a33b2d12c99f40c2e675d9ff435014416 100644 (file)
@@ -5,6 +5,7 @@ import java.util.Locale;
 import javax.naming.ldap.LdapName;
 import javax.naming.ldap.Rdn;
 
+import org.argeo.api.acr.ldap.LdapNameUtils;
 import org.argeo.api.cms.directory.HierarchyUnit;
 
 /** LDIF/LDAP based implementation of {@link HierarchyUnit}. */
diff --git a/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java b/org.argeo.cms/src/org/argeo/cms/directory/ldap/LdapNameUtils.java
deleted file mode 100644 (file)
index 74f23da..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-package org.argeo.cms.directory.ldap;
-
-import javax.naming.InvalidNameException;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-/** Utilities to simplify using {@link LdapName}. */
-public class LdapNameUtils {
-
-       public static LdapName relativeName(LdapName prefix, LdapName dn) {
-               try {
-                       if (!dn.startsWith(prefix))
-                               throw new IllegalArgumentException("Prefix " + prefix + " not consistent with " + dn);
-                       LdapName res = (LdapName) dn.clone();
-                       for (int i = 0; i < prefix.size(); i++) {
-                               res.remove(0);
-                       }
-                       return res;
-               } catch (InvalidNameException e) {
-                       throw new IllegalStateException("Cannot find realtive name", e);
-               }
-       }
-
-       public static LdapName getParent(LdapName dn) {
-               try {
-                       LdapName parent = (LdapName) dn.clone();
-                       parent.remove(parent.size() - 1);
-                       return parent;
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot get parent of " + dn, e);
-               }
-       }
-
-       public static Rdn getParentRdn(LdapName dn) {
-               if (dn.size() < 2)
-                       throw new IllegalArgumentException(dn + " has no parent");
-               Rdn parentRdn = dn.getRdn(dn.size() - 2);
-               return parentRdn;
-       }
-
-       public static LdapName toLdapName(String distinguishedName) {
-               try {
-                       return new LdapName(distinguishedName);
-               } catch (InvalidNameException e) {
-                       throw new IllegalArgumentException("Cannot parse " + distinguishedName + " as LDAP name", e);
-               }
-       }
-
-       public static Rdn getLastRdn(LdapName dn) {
-               return dn.getRdn(dn.size() - 1);
-       }
-
-       public static String getLastRdnAsString(LdapName dn) {
-               return getLastRdn(dn).toString();
-       }
-
-       public static String getLastRdnValue(String dn) {
-               return getLastRdnValue(toLdapName(dn));
-       }
-
-       public static String getLastRdnValue(LdapName dn) {
-               return getLastRdn(dn).getValue().toString();
-       }
-
-       /** singleton */
-       private LdapNameUtils() {
-
-       }
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java
deleted file mode 100644 (file)
index 9e0ebce..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-package org.argeo.cms.internal.auth;
-
-import java.security.Principal;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.xml.namespace.QName;
-
-import org.argeo.cms.RoleNameUtils;
-import org.osgi.service.useradmin.Authorization;
-
-/**
- * A {@link Principal} which has been implied by an {@link Authorization}. If it
- * is empty it means this is an additional identity, otherwise it lists the
- * users (typically the logged in user but possibly empty
- * {@link ImpliedByPrincipal}s) which have implied it. When an additional
- * identity is removed, the related {@link ImpliedByPrincipal}s can thus be
- * removed.
- */
-public final class ImpliedByPrincipal implements Principal {
-       private final String name;
-       private final QName roleName;
-       private final boolean systemRole;
-       private final String context;
-
-       private Set<Principal> causes = new HashSet<Principal>();
-
-       public ImpliedByPrincipal(String name, Principal userPrincipal) {
-               this.name = name;
-               roleName = RoleNameUtils.getLastRdnAsName(name);
-               systemRole = RoleNameUtils.isSystemRole(roleName);
-               context = RoleNameUtils.getContext(name);
-               if (userPrincipal != null)
-                       causes.add(userPrincipal);
-       }
-
-       public String getName() {
-               return name;
-       }
-
-       /*
-        * OBJECT
-        */
-
-       public QName getRoleName() {
-               return roleName;
-       }
-
-       public String getContext() {
-               return context;
-       }
-
-       public boolean isSystemRole() {
-               return systemRole;
-       }
-
-       @Override
-       public int hashCode() {
-               return name.hashCode();
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               if (obj instanceof ImpliedByPrincipal) {
-                       ImpliedByPrincipal that = (ImpliedByPrincipal) obj;
-                       // TODO check members too?
-                       return name.equals(that.name);
-               }
-               return false;
-       }
-
-       @Override
-       public String toString() {
-               return name.toString();
-       }
-}
index e5fb01242bf7c690ca9d5f3cdae294ea47f6bca0..47cf5d3782177aa61362d7dda4aa57f10fc2067b 100644 (file)
@@ -25,6 +25,7 @@ import javax.naming.ldap.Rdn;
 import javax.security.auth.Subject;
 import javax.security.auth.kerberos.KerberosTicket;
 
+import org.argeo.api.acr.ldap.LdapNameUtils;
 import org.argeo.api.cms.directory.CmsRole;
 import org.argeo.api.cms.directory.DirectoryDigestUtils;
 import org.argeo.api.cms.directory.HierarchyUnit;
@@ -33,7 +34,6 @@ import org.argeo.cms.directory.ldap.AbstractLdapDirectory;
 import org.argeo.cms.directory.ldap.LdapDao;
 import org.argeo.cms.directory.ldap.LdapEntry;
 import org.argeo.cms.directory.ldap.LdapEntryWorkingCopy;
-import org.argeo.cms.directory.ldap.LdapNameUtils;
 import org.argeo.cms.directory.ldap.LdifDao;
 import org.argeo.cms.runtime.DirectoryConf;
 import org.argeo.cms.util.CurrentSubject;