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 \
--- /dev/null
+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() {
+
+ }
+}
+++ /dev/null
-<?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>
+++ /dev/null
-<?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>
+++ /dev/null
-source.. = src/
-output.. = bin/
-bin.includes = META-INF/,\
- .
+++ /dev/null
-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;
- }
-
-}
+++ /dev/null
-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;
- }
-
-}
+++ /dev/null
-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);
- }
- }
-}
+++ /dev/null
-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);
- }
- }
-
-}
+++ /dev/null
-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();
- }
- }
-}
+++ /dev/null
-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;
- }
-
-}
+++ /dev/null
-/** Command line API. */
-package org.argeo.api.cli;
\ No newline at end of file
+++ /dev/null
-/MANIFEST.MF
Import-Package: \
javax.transaction.xa,\
javax.security.*,\
-org.osgi.service.useradmin,\
*
Export-Package: org.argeo.api.cms.*
\ No newline at end of file
--- /dev/null
+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();
+ }
+}
--- /dev/null
+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));
+ }
+}
--- /dev/null
+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;
+ }
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+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);
+ }
+ }
+}
--- /dev/null
+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);
+ }
+ }
+
+}
--- /dev/null
+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();
+ }
+ }
+}
--- /dev/null
+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;
+ }
+
+}
--- /dev/null
+/** Command line API. */
+package org.argeo.api.cli;
\ No newline at end of file
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;
+++ /dev/null
-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));
- }
-}
+++ /dev/null
-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;
- }
-}
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;
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 {
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;
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;
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;
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}. */
+++ /dev/null
-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() {
-
- }
-}
+++ /dev/null
-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();
- }
-}
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;
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;