From eb4324be6ac9cdff15828a21ee7d3f6ca2f19fb9 Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 21 Jun 2022 11:27:36 +0200 Subject: [PATCH] Introduce system roles --- .../cms/e4/monitoring/CmsSessionsView.java | 6 +- .../argeo/cms/e4/users/UserAdminWrapper.java | 2 +- .../src/org/argeo/api/acr/ContentName.java | 4 + .../src/org/argeo/api/acr/CrName.java | 7 +- .../api/acr/RuntimeNamespaceContext.java | 82 +++++++++++++ .../argeo/api/acr/spi/ProvidedSession.java | 60 ++-------- .../src/org/argeo/api/cms/CmsSession.java | 3 +- .../cms/acr/AbstractContentRepository.java | 6 +- .../org/argeo/cms/acr/CmsContentSession.java | 25 ++-- .../src/org/argeo/cms/acr/TypesManager.java | 28 +++-- .../cms/acr/directory/DirectoryContent.java | 2 +- .../acr/directory/HierarchyUnitContent.java | 2 +- .../src/org/argeo/cms/auth/CmsAuthUtils.java | 93 ++++++++------ .../src/org/argeo/cms/auth/CmsRole.java | 28 +++++ .../src/org/argeo/cms/auth/CurrentUser.java | 5 + .../src/org/argeo/cms/auth/RoleNameUtils.java | 25 ++++ .../src/org/argeo/cms/auth/SystemRole.java | 26 ++++ .../cms/internal/auth/CmsSessionImpl.java | 23 ++-- .../cms/internal/auth/CmsUserManagerImpl.java | 8 +- .../cms/internal/auth/ImpliedByPrincipal.java | 113 +++++++++++------- .../cms/internal/runtime/CmsUserAdmin.java | 4 +- .../argeo/cms/security/NodeSecurityUtils.java | 40 ------- .../osgi/useradmin/AbstractUserDirectory.java | 58 +++++---- .../osgi/useradmin/AggregatingUserAdmin.java | 4 +- .../argeo/osgi/useradmin/HierarchyUnit.java | 6 +- .../org/argeo/osgi/useradmin/IpaUtils.java | 2 + .../osgi/useradmin/LdifHierarchyUnit.java | 26 ++-- .../argeo/osgi/useradmin/LdifUserAdmin.java | 2 +- .../argeo/osgi/useradmin/OsUserDirectory.java | 2 +- .../argeo/osgi/useradmin/UserAdminConf.java | 3 + .../argeo/osgi/useradmin/UserDirectory.java | 4 +- 31 files changed, 412 insertions(+), 287 deletions(-) create mode 100644 org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java create mode 100644 org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java create mode 100644 org.argeo.cms/src/org/argeo/cms/auth/RoleNameUtils.java create mode 100644 org.argeo.cms/src/org/argeo/cms/auth/SystemRole.java delete mode 100644 org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java index 8a3605095..95b1eb2cb 100644 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java @@ -8,9 +8,9 @@ import java.util.Collection; import java.util.List; import javax.annotation.PostConstruct; -import javax.naming.ldap.LdapName; import org.argeo.api.cms.CmsSession; +import org.argeo.cms.auth.RoleNameUtils; import org.argeo.eclipse.ui.ColumnViewerComparator; import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; import org.argeo.util.LangUtils; @@ -88,8 +88,8 @@ public class CmsSessionsView { private static final long serialVersionUID = -5234573509093747505L; public String getText(Object element) { - LdapName userDn = ((CmsSession) element).getUserDn(); - return userDn.getRdn(userDn.size() - 1).getValue().toString(); + String userDn = ((CmsSession) element).getUserDn(); + return RoleNameUtils.getLastRdnValue(userDn); } public String getToolTipText(Object element) { diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java index dbbd970a7..934b880cd 100644 --- a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java @@ -91,7 +91,7 @@ public class UserAdminWrapper { Map dns = new HashMap(); for (UserDirectory userDirectory : userDirectories.keySet()) { Boolean readOnly = userDirectory.isReadOnly(); - String baseDn = userDirectory.getGlobalId(); + String baseDn = userDirectory.getContext(); if (onlyWritable && readOnly) continue; diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java b/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java index 1fee92e39..a3f6d9010 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/ContentName.java @@ -41,6 +41,10 @@ public class ContentName extends QName { // private final UUID uuid; + public ContentName(String namespaceURI, String localPart) { + super(namespaceURI, localPart, checkPrefix(RuntimeNamespaceContext.getNamespaceContext(), namespaceURI)); + } + public ContentName(String namespaceURI, String localPart, NamespaceContext nsContext) { super(namespaceURI, localPart, checkPrefix(nsContext, namespaceURI)); } diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java b/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java index c772daca3..f79c47446 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/CrName.java @@ -34,16 +34,15 @@ public enum CrName implements ContentNameSupplier { ; public final static String CR_NAMESPACE_URI = "http://argeo.org/ns/cr"; -// public final static String CR_BASIC_NAMESPACE_URI = CR_NAMESPACE_URI + "/basic"; -// public final static String CR_OWNER_NAMESPACE_URI = CR_NAMESPACE_URI + "/owner"; -// public final static String CR_POSIX_NAMESPACE_URI = CR_NAMESPACE_URI + "/posix"; public final static String CR_DEFAULT_PREFIX = "cr"; public final static String LDAP_NAMESPACE_URI = "http://argeo.org/ns/ldap"; public final static String LDAP_DEFAULT_PREFIX = "ldap"; - + public final static String ROLE_NAMESPACE_URI = "http://argeo.org/ns/role"; + public final static String ROLE_DEFAULT_PREFIX = "role"; + private final ContentName value; CrName() { diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java b/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java new file mode 100644 index 000000000..d228c23e8 --- /dev/null +++ b/org.argeo.api.acr/src/org/argeo/api/acr/RuntimeNamespaceContext.java @@ -0,0 +1,82 @@ +package org.argeo.api.acr; + +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.NavigableMap; +import java.util.TreeMap; + +import javax.xml.namespace.NamespaceContext; + +/** + * Programmatically defined {@link NamespaceContext}, code contributing + * namespaces MUST register here with a single default prefix. + */ +public class RuntimeNamespaceContext implements NamespaceContext { + private NavigableMap prefixes = new TreeMap<>(); + private NavigableMap namespaces = new TreeMap<>(); + + @Override + public String getPrefix(String namespaceURI) { + return NamespaceUtils.getPrefix((ns) -> { + String prefix = namespaces.get(ns); + if (prefix == null) + throw new IllegalStateException("Namespace " + ns + " is not registered."); + return prefix; + }, namespaceURI); + } + + @Override + public String getNamespaceURI(String prefix) { + return NamespaceUtils.getNamespaceURI((p) -> { + String ns = prefixes.get(p); + if (ns == null) + throw new IllegalStateException("Prefix " + p + " is not registered."); + return ns; + }, prefix); + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + return Collections.singleton(getPrefix(namespaceURI)).iterator(); + } + + /* + * STATIC + */ + + private final static RuntimeNamespaceContext INSTANCE = new RuntimeNamespaceContext(); + + static { + register(CrName.CR_NAMESPACE_URI, CrName.CR_DEFAULT_PREFIX); + register(CrName.LDAP_NAMESPACE_URI, CrName.LDAP_DEFAULT_PREFIX); + register(CrName.ROLE_NAMESPACE_URI, CrName.ROLE_DEFAULT_PREFIX); + } + + public static NamespaceContext getNamespaceContext() { + return INSTANCE; + } + + public static Map getPrefixes() { + return Collections.unmodifiableNavigableMap(INSTANCE.prefixes); + } + + public synchronized static void register(String namespaceURI, String prefix) { + NavigableMap prefixes = INSTANCE.prefixes; + NavigableMap namespaces = INSTANCE.namespaces; + if (prefixes.containsKey(prefix)) { + String ns = prefixes.get(prefix); + if (ns.equals(namespaceURI)) + return; // ignore silently + throw new IllegalStateException("Prefix " + prefix + " is already registered with namespace URI " + ns); + } + if (namespaces.containsKey(namespaceURI)) { + String p = namespaces.get(namespaceURI); + if (p.equals(prefix)) + return; // ignore silently + throw new IllegalStateException("Namespace " + namespaceURI + " is already registered with prefix " + p); + } + prefixes.put(prefix, namespaceURI); + namespaces.put(namespaceURI, prefix); + } +} diff --git a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java index ba915ac40..9c4f6e633 100644 --- a/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java +++ b/org.argeo.api.acr/src/org/argeo/api/acr/spi/ProvidedSession.java @@ -6,6 +6,7 @@ import java.util.concurrent.CompletionStage; import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentSession; +import org.argeo.api.acr.RuntimeNamespaceContext; /** A {@link ContentSession} implementation. */ public interface ProvidedSession extends ContentSession { @@ -29,57 +30,16 @@ public interface ProvidedSession extends ContentSession { @Override default String getPrefix(String namespaceURI) { - Iterator prefixes = getPrefixes(namespaceURI); - return prefixes.hasNext() ? prefixes.next() : null; + return RuntimeNamespaceContext.getNamespaceContext().getPrefix(namespaceURI); } -// /** @return the bound namespace or null if not found */ -// String findNamespace(String prefix); -// -// // TODO find the default prefix? -// Set findPrefixes(String namespaceURI); -// -// /** To be overridden for optimisation, as it will be called a lot */ -// default String findPrefix(String namespaceURI) { -// Set prefixes = findPrefixes(namespaceURI); -// if (prefixes.isEmpty()) -// return null; -// return prefixes.iterator().next(); -// } - -// @Override -// default String getNamespaceURI(String prefix) { -// String namespaceURI = NamespaceUtils.getStandardNamespaceURI(prefix); -// if (namespaceURI != null) -// return namespaceURI; -// if (XMLConstants.DEFAULT_NS_PREFIX.equals(prefix)) -// return XMLConstants.NULL_NS_URI; -// namespaceURI = findNamespace(prefix); -// if (namespaceURI != null) -// return namespaceURI; -// return XMLConstants.NULL_NS_URI; -// } -// -// @Override -// default String getPrefix(String namespaceURI) { -// String prefix = NamespaceUtils.getStandardPrefix(namespaceURI); -// if (prefix != null) -// return prefix; -// if (XMLConstants.NULL_NS_URI.equals(namespaceURI)) -// return XMLConstants.DEFAULT_NS_PREFIX; -// return findPrefix(namespaceURI); -// } -// -// @Override -// default Iterator getPrefixes(String namespaceURI) { -// Iterator standard = NamespaceUtils.getStandardPrefixes(namespaceURI); -// if (standard != null) -// return standard; -// if (XMLConstants.NULL_NS_URI.equals(namespaceURI)) -// return Collections.singleton(XMLConstants.DEFAULT_NS_PREFIX).iterator(); -// Set prefixes = findPrefixes(namespaceURI); -// assert prefixes != null; -// return prefixes.iterator(); -// } + @Override + default String getNamespaceURI(String prefix) { + return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix); + } + @Override + default Iterator getPrefixes(String namespaceURI) { + return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI); + } } diff --git a/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java index ea9d10ba2..1d61c6c67 100644 --- a/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java +++ b/org.argeo.api.cms/src/org/argeo/api/cms/CmsSession.java @@ -5,7 +5,6 @@ import java.util.Locale; import java.util.UUID; import java.util.function.Consumer; -import javax.naming.ldap.LdapName; import javax.security.auth.Subject; /** An authenticated user session. */ @@ -18,7 +17,7 @@ public interface CmsSession { String getUserRole(); - LdapName getUserDn(); + String getUserDn(); String getLocalId(); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java index 1481b3a40..2e65e7e69 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/AbstractContentRepository.java @@ -19,6 +19,7 @@ import javax.xml.transform.stream.StreamResult; import org.argeo.api.acr.Content; import org.argeo.api.acr.CrName; +import org.argeo.api.acr.RuntimeNamespaceContext; import org.argeo.api.acr.spi.ContentProvider; import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedRepository; @@ -111,10 +112,11 @@ public abstract class AbstractContentRepository implements ProvidedRepository { document = dBuilder.newDocument(); Element root = document.createElementNS(CrName.CR_NAMESPACE_URI, CrName.ROOT.get().toPrefixedString()); - for (String prefix : typesManager.getPrefixes().keySet()) { + for (String prefix : RuntimeNamespaceContext.getPrefixes().keySet()) { // root.setAttributeNS(XMLConstants.XMLNS_ATTRIBUTE_NS_URI, XMLConstants.XMLNS_ATTRIBUTE + ":" + prefix, // contentTypesManager.getPrefixes().get(prefix)); - DomUtils.addNamespace(root, prefix, typesManager.getPrefixes().get(prefix)); + DomUtils.addNamespace(root, prefix, + RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix)); } document.appendChild(root); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java index 025585634..4e1c3bcfd 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/CmsContentSession.java @@ -17,6 +17,7 @@ import org.argeo.api.acr.Content; import org.argeo.api.acr.ContentSession; import org.argeo.api.acr.CrName; import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.RuntimeNamespaceContext; import org.argeo.api.acr.spi.ContentProvider; import org.argeo.api.acr.spi.ProvidedContent; import org.argeo.api.acr.spi.ProvidedRepository; @@ -113,17 +114,19 @@ class CmsContentSession implements ProvidedSession { * NAMESPACE CONTEXT */ - @Override - public String getNamespaceURI(String prefix) { - return NamespaceUtils.getNamespaceURI((p) -> contentRepository.getTypesManager().getPrefixes().get(p), prefix); - } - - @Override - public Iterator getPrefixes(String namespaceURI) { - return NamespaceUtils.getPrefixes((ns) -> contentRepository.getTypesManager().getPrefixes().entrySet().stream() - .filter(e -> e.getValue().equals(ns)).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), - namespaceURI); - } +// @Override +// public String getNamespaceURI(String prefix) { +// return RuntimeNamespaceContext.getNamespaceContext().getNamespaceURI(prefix); +//// return NamespaceUtils.getNamespaceURI((p) -> contentRepository.getTypesManager().getPrefixes().get(p), prefix); +// } +// +// @Override +// public Iterator getPrefixes(String namespaceURI) { +// return RuntimeNamespaceContext.getNamespaceContext().getPrefixes(namespaceURI); +//// return NamespaceUtils.getPrefixes((ns) -> contentRepository.getTypesManager().getPrefixes().entrySet().stream() +//// .filter(e -> e.getValue().equals(ns)).map(Map.Entry::getKey).collect(Collectors.toUnmodifiableSet()), +//// namespaceURI); +// } @Override public CompletionStage edit(Consumer work) { diff --git a/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java index 731c6d562..29541b9d8 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/TypesManager.java @@ -5,7 +5,6 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; @@ -43,6 +42,7 @@ import org.apache.xerces.xs.XSTerm; import org.apache.xerces.xs.XSTypeDefinition; import org.argeo.api.acr.CrAttributeType; import org.argeo.api.acr.NamespaceUtils; +import org.argeo.api.acr.RuntimeNamespaceContext; import org.argeo.api.cms.CmsLog; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; @@ -51,7 +51,7 @@ import org.xml.sax.SAXParseException; /** Register content types. */ class TypesManager { private final static CmsLog log = CmsLog.getLog(TypesManager.class); - private Map prefixes = new TreeMap<>(); +// private Map prefixes = new TreeMap<>(); // immutable factories private SchemaFactory schemaFactory; @@ -86,20 +86,22 @@ class TypesManager { for (CmsContentTypes cs : CmsContentTypes.values()) { StreamSource source = new StreamSource(cs.getResource().toExternalForm()); sources.add(source); - if (prefixes.containsKey(cs.getDefaultPrefix())) - throw new IllegalStateException("Prefix " + cs.getDefaultPrefix() + " is already mapped with " - + prefixes.get(cs.getDefaultPrefix())); - prefixes.put(cs.getDefaultPrefix(), cs.getNamespace()); +// if (prefixes.containsKey(cs.getDefaultPrefix())) +// throw new IllegalStateException("Prefix " + cs.getDefaultPrefix() + " is already mapped with " +// + prefixes.get(cs.getDefaultPrefix())); +// prefixes.put(cs.getDefaultPrefix(), cs.getNamespace()); + RuntimeNamespaceContext.register(cs.getNamespace(), cs.getDefaultPrefix()); } reload(); } public synchronized void registerTypes(String defaultPrefix, String namespace, String xsdSystemId) { - if (prefixes.containsKey(defaultPrefix)) - throw new IllegalStateException( - "Prefix " + defaultPrefix + " is already mapped with " + prefixes.get(defaultPrefix)); - prefixes.put(defaultPrefix, namespace); +// if (prefixes.containsKey(defaultPrefix)) +// throw new IllegalStateException( +// "Prefix " + defaultPrefix + " is already mapped with " + prefixes.get(defaultPrefix)); +// prefixes.put(defaultPrefix, namespace); + RuntimeNamespaceContext.register(namespace, defaultPrefix); sources.add(new StreamSource(xsdSystemId)); reload(); @@ -459,9 +461,9 @@ class TypesManager { } } - public Map getPrefixes() { - return prefixes; - } +// public Map getPrefixes() { +// return prefixes; +// } public List getSources() { return sources; diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java index dc9aefa80..eef7055df 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/DirectoryContent.java @@ -32,7 +32,7 @@ class DirectoryContent extends AbstractContent { @Override public Iterator iterator() { List res = new ArrayList<>(); - for (Iterator it = directory.getRootHierarchyUnits(false).iterator(); it.hasNext();) { + for (Iterator it = directory.getDirectHierarchyUnits(false).iterator(); it.hasNext();) { res.add(new HierarchyUnitContent(getSession(), provider, it.next())); } return res.iterator(); diff --git a/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java b/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java index 5ec57c51f..78bc72f5d 100644 --- a/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java +++ b/org.argeo.cms/src/org/argeo/cms/acr/directory/HierarchyUnitContent.java @@ -56,7 +56,7 @@ class HierarchyUnitContent extends AbstractContent { @Override public Iterator iterator() { List lst = new ArrayList<>(); - for (HierarchyUnit hu : hierarchyUnit.getDirectHierachyUnits()) + for (HierarchyUnit hu : hierarchyUnit.getDirectHierachyUnits(false)) lst.add(new HierarchyUnitContent(getSession(), provider, hu)); for (Role role : hierarchyUnit.getHierarchyUnitRoles(null, false)) diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java index 1efb93afd..54824e140 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java @@ -1,12 +1,20 @@ package org.argeo.cms.auth; +import static org.argeo.api.cms.CmsConstants.ROLE_ADMIN; +import static org.argeo.api.cms.CmsConstants.ROLE_ANONYMOUS; +import static org.argeo.api.cms.CmsConstants.ROLE_USER; +import static org.argeo.api.cms.CmsConstants.ROLE_USER_ADMIN; + import java.security.Principal; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Locale; import java.util.Set; import java.util.UUID; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; +//import javax.naming.InvalidNameException; +//import javax.naming.ldap.LdapName; import javax.security.auth.Subject; import javax.security.auth.x500.X500Principal; @@ -18,7 +26,6 @@ import org.argeo.cms.internal.auth.CmsSessionImpl; import org.argeo.cms.internal.auth.ImpliedByPrincipal; import org.argeo.cms.internal.http.WebCmsSessionImpl; import org.argeo.cms.internal.runtime.CmsContextImpl; -import org.argeo.cms.security.NodeSecurityUtils; import org.argeo.osgi.useradmin.AuthenticatingUser; import org.osgi.service.http.HttpContext; import org.osgi.service.useradmin.Authorization; @@ -41,6 +48,9 @@ class CmsAuthUtils { final static String SINGLE_USER_LOCAL_ID = "single-user"; + private final static List RESERVED_ROLES = Collections + .unmodifiableList(Arrays.asList(new String[] { ROLE_ADMIN, ROLE_ANONYMOUS, ROLE_USER, ROLE_USER_ADMIN })); + static void addAuthorization(Subject subject, Authorization authorization) { assert subject != null; checkSubjectEmpty(subject); @@ -52,46 +62,46 @@ class CmsAuthUtils { boolean singleUser = authorization instanceof SingleUserAuthorization; Set principals = subject.getPrincipals(); - try { - String authName = authorization.getName(); - - // determine user's principal - final LdapName name; - final Principal userPrincipal; - if (authName == null) { - name = NodeSecurityUtils.ROLE_ANONYMOUS_NAME; - userPrincipal = new AnonymousPrincipal(); - principals.add(userPrincipal); - } else { - name = new LdapName(authName); - NodeSecurityUtils.checkUserName(name); - userPrincipal = new X500Principal(name.toString()); - principals.add(userPrincipal); - - if (singleUser) { - principals.add(new ImpliedByPrincipal(NodeSecurityUtils.ROLE_ADMIN_NAME, userPrincipal)); - principals.add(new DataAdminPrincipal()); - } +// try { + String authName = authorization.getName(); + + // determine user's principal +// final LdapName name; + final Principal userPrincipal; + if (authName == null) { +// name = NodeSecurityUtils.ROLE_ANONYMOUS_NAME; + userPrincipal = new AnonymousPrincipal(); + principals.add(userPrincipal); + } else { +// name = new LdapName(authName); + checkUserName(authName); + userPrincipal = new X500Principal(authName.toString()); + principals.add(userPrincipal); + + if (singleUser) { + principals.add(new ImpliedByPrincipal(CmsConstants.ROLE_ADMIN, userPrincipal)); + principals.add(new DataAdminPrincipal()); } + } - // Add roles provided by authorization - for (String role : authorization.getRoles()) { - LdapName roleName = new LdapName(role); - if (roleName.equals(name)) { - // skip - } else if (roleName.equals(NodeSecurityUtils.ROLE_ANONYMOUS_NAME)) { - // skip - } else { - NodeSecurityUtils.checkImpliedPrincipalName(roleName); - principals.add(new ImpliedByPrincipal(roleName.toString(), userPrincipal)); - if (roleName.equals(NodeSecurityUtils.ROLE_ADMIN_NAME)) - principals.add(new DataAdminPrincipal()); - } + // Add roles provided by authorization + for (String role : authorization.getRoles()) { +// LdapName roleName = new LdapName(role); + if (role.equals(authName)) { + // skip + } else if (role.equals(CmsConstants.ROLE_ANONYMOUS)) { + // skip + } else { +// NodeSecurityUtils.checkImpliedPrincipalName(role); + principals.add(new ImpliedByPrincipal(role, userPrincipal)); + if (role.equals(CmsConstants.ROLE_ADMIN)) + principals.add(new DataAdminPrincipal()); } - - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Cannot commit", e); } + +// } catch (InvalidNameException e) { +// throw new IllegalArgumentException("Cannot commit", e); +// } } private static void checkSubjectEmpty(Subject subject) { @@ -225,6 +235,11 @@ class CmsAuthUtils { return principals.iterator().next(); } + private static void checkUserName(String name) throws IllegalArgumentException { + if (RESERVED_ROLES.contains(name)) + throw new IllegalArgumentException(name + " is a reserved name"); + } + private CmsAuthUtils() { } diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java b/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java new file mode 100644 index 000000000..af64508f4 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/CmsRole.java @@ -0,0 +1,28 @@ +package org.argeo.cms.auth; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.ContentName; +import org.argeo.api.acr.CrName; + +public enum CmsRole implements SystemRole { + userAdmin, // + groupAdmin; + + private final static String QUALIFIER = "cms."; + + private final ContentName name; + + CmsRole() { + name = new ContentName(CrName.ROLE_NAMESPACE_URI, QUALIFIER + name()); + } + + public QName getName() { + return name; + } + + @Override + public String toString() { + return name.toPrefixedString(); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java b/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java index faf5555d0..16ac638c1 100644 --- a/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java +++ b/org.argeo.cms/src/org/argeo/cms/auth/CurrentUser.java @@ -71,6 +71,11 @@ public final class CurrentUser { return roles.contains(role); } + /** Implies this {@link SystemRole} in this context. */ + public final static boolean implies(SystemRole role, String context) { + return role.implied(currentSubject(), context); + } + /** Executes as the current user */ public final static T doAs(PrivilegedAction action) { return Subject.doAs(currentSubject(), action); diff --git a/org.argeo.cms/src/org/argeo/cms/auth/RoleNameUtils.java b/org.argeo.cms/src/org/argeo/cms/auth/RoleNameUtils.java new file mode 100644 index 000000000..bf91f3941 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/RoleNameUtils.java @@ -0,0 +1,25 @@ +package org.argeo.cms.auth; + +public class RoleNameUtils { + + /* + * UTILITIES + */ + public final static String getLastRdnValue(String 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 final static String getParent(String dn) { + int index = dn.indexOf(','); + return dn.substring(index + 1); + } + + /** Up two levels. */ + public final static String getContext(String dn) { + return getParent(getParent(dn)); + } +} diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SystemRole.java b/org.argeo.cms/src/org/argeo/cms/auth/SystemRole.java new file mode 100644 index 000000000..9c686a6c6 --- /dev/null +++ b/org.argeo.cms/src/org/argeo/cms/auth/SystemRole.java @@ -0,0 +1,26 @@ +package org.argeo.cms.auth; + +import java.util.Set; + +import javax.security.auth.Subject; +import javax.xml.namespace.QName; + +import org.argeo.cms.internal.auth.ImpliedByPrincipal; + +public interface SystemRole { + QName getName(); + + default boolean implied(Subject subject, String context) { + Set roles = subject.getPrincipals(ImpliedByPrincipal.class); + for (ImpliedByPrincipal role : roles) { + if (role.isSystemRole()) { + if (role.getRoleName().equals(getName())) { + if (role.getContext().equalsIgnoreCase(context)) + return true; + } + } + } + return false; + } + +} diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java index 164d319f1..2e074e778 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsSessionImpl.java @@ -17,18 +17,16 @@ import java.util.UUID; import java.util.function.Consumer; import javax.crypto.SecretKey; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; import javax.security.auth.Subject; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; import javax.security.auth.x500.X500Principal; import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsConstants; import org.argeo.api.cms.CmsLog; import org.argeo.api.cms.CmsSession; import org.argeo.cms.internal.runtime.CmsContextImpl; -import org.argeo.cms.security.NodeSecurityUtils; import org.osgi.framework.ServiceRegistration; import org.osgi.service.useradmin.Authorization; @@ -43,7 +41,8 @@ public class CmsSessionImpl implements CmsSession, Serializable { private final UUID uuid; private final String localSessionId; private Authorization authorization; - private final LdapName userDn; +// private final LdapName userDn; + private final String userDn; private final boolean anonymous; private final ZonedDateTime creationTime; @@ -73,15 +72,11 @@ public class CmsSessionImpl implements CmsSession, Serializable { // this.initialSubject = initialSubject; this.localSessionId = localSessionId; this.authorization = authorization; - if (authorization.getName() != null) - try { - this.userDn = new LdapName(authorization.getName()); - this.anonymous = false; - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Invalid user name " + authorization.getName(), e); - } - else { - this.userDn = NodeSecurityUtils.ROLE_ANONYMOUS_NAME; + if (authorization.getName() != null) { + this.userDn = authorization.getName(); + this.anonymous = false; + } else { + this.userDn = CmsConstants.ROLE_ANONYMOUS; this.anonymous = true; } this.uuid = uuid; @@ -156,7 +151,7 @@ public class CmsSessionImpl implements CmsSession, Serializable { } @Override - public LdapName getUserDn() { + public String getUserDn() { return userDn; } diff --git a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java index 5d5477e9b..e69cda644 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/CmsUserManagerImpl.java @@ -232,7 +232,7 @@ public class CmsUserManagerImpl implements CmsUserManager { Map dns = new HashMap(); for (UserDirectory userDirectory : userDirectories.keySet()) { Boolean readOnly = userDirectory.isReadOnly(); - String baseDn = userDirectory.getGlobalId(); + String baseDn = userDirectory.getContext(); if (onlyWritable && readOnly) continue; @@ -247,7 +247,7 @@ public class CmsUserManagerImpl implements CmsUserManager { } public Set getUserDirectories() { - TreeSet res = new TreeSet<>((o1, o2) -> o1.getGlobalId().compareTo(o2.getGlobalId())); + TreeSet res = new TreeSet<>((o1, o2) -> o1.getContext().compareTo(o2.getContext())); res.addAll(userDirectories.keySet()); return res; } @@ -445,8 +445,8 @@ public class CmsUserManagerImpl implements CmsUserManager { String name = user.getName(); NavigableMap possible = new TreeMap<>(); for (UserDirectory userDirectory : userDirectories.keySet()) { - if (name.endsWith(userDirectory.getGlobalId())) { - possible.put(userDirectory.getGlobalId(), userDirectory); + if (name.endsWith(userDirectory.getContext())) { + possible.put(userDirectory.getContext(), userDirectory); } } if (possible.size() == 0) 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 index c75360129..73f474637 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/auth/ImpliedByPrincipal.java @@ -1,17 +1,17 @@ package org.argeo.cms.internal.auth; +import static org.argeo.api.acr.RuntimeNamespaceContext.getNamespaceContext; + import java.security.Principal; -import java.util.Collections; -import java.util.Dictionary; -import java.util.Enumeration; import java.util.HashSet; import java.util.Set; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; +import javax.xml.namespace.QName; +import org.argeo.api.acr.CrName; +import org.argeo.api.acr.NamespaceUtils; +import org.argeo.cms.auth.RoleNameUtils; import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Role; /** * A {@link Principal} which has been implied by an {@link Authorization}. If it @@ -21,67 +21,88 @@ import org.osgi.service.useradmin.Role; * identity is removed, the related {@link ImpliedByPrincipal}s can thus be * removed. */ -public final class ImpliedByPrincipal implements Principal, Role { - private final LdapName name; +public final class ImpliedByPrincipal implements Principal { + private final String name; private Set causes = new HashSet(); - private int type = Role.ROLE; + private QName roleName; +// private int type = Role.ROLE; + + private boolean systemRole = false; + private String context; public ImpliedByPrincipal(String name, Principal userPrincipal) { - try { - this.name = new LdapName(name); - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Badly formatted role name", e); + this.name = name; + String cn = RoleNameUtils.getLastRdnValue(name); + roleName = NamespaceUtils.parsePrefixedName(getNamespaceContext(), cn); + if (roleName.getNamespaceURI().equals(CrName.ROLE_NAMESPACE_URI)) { + systemRole = true; } + context = RoleNameUtils.getContext(name); +// try { +// this.name = new LdapName(name); +// } catch (InvalidNameException e) { +// throw new IllegalArgumentException("Badly formatted role name", e); +// } if (userPrincipal != null) causes.add(userPrincipal); } - public ImpliedByPrincipal(LdapName name, Principal userPrincipal) { - this.name = name; - if (userPrincipal != null) - causes.add(userPrincipal); - } +// public ImpliedByPrincipal(LdapName name, Principal userPrincipal) { +// this.name = name; +// if (userPrincipal != null) +// causes.add(userPrincipal); +// } public String getName() { - return name.toString(); - } - - public boolean addMember(Principal user) { - throw new UnsupportedOperationException(); - } - - public boolean removeMember(Principal user) { - throw new UnsupportedOperationException(); - } - - public boolean isMember(Principal member) { - return causes.contains(member); - } - - public Enumeration members() { - return Collections.enumeration(causes); + return name; } /* * USER ADMIN */ +// public boolean addMember(Principal user) { +// throw new UnsupportedOperationException(); +// } +// +// public boolean removeMember(Principal user) { +// throw new UnsupportedOperationException(); +// } +// +// public boolean isMember(Principal member) { +// return causes.contains(member); +// } +// +// public Enumeration members() { +// return Collections.enumeration(causes); +// } +// +// +// /** Type of {@link Role}, if known. */ +// public int getType() { +// return type; +// } +// +// /** Not supported for the time being. */ +// public Dictionary getProperties() { +// throw new UnsupportedOperationException(); +// } - @Override - /** Type of {@link Role}, if known. */ - public int getType() { - return type; + /* + * OBJECT + */ + + public QName getRoleName() { + return roleName; } - @Override - /** Not supported for the time being. */ - public Dictionary getProperties() { - throw new UnsupportedOperationException(); + public String getContext() { + return context; } - /* - * OBJECT - */ + public boolean isSystemRole() { + return systemRole; + } @Override public int hashCode() { diff --git a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java index 49e55f16c..890e28391 100644 --- a/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java +++ b/org.argeo.cms/src/org/argeo/cms/internal/runtime/CmsUserAdmin.java @@ -107,14 +107,14 @@ public class CmsUserAdmin extends AggregatingUserAdmin { } else { throw new IllegalArgumentException("Unsupported scheme " + u.getScheme()); } - String basePath = userDirectory.getGlobalId(); + String basePath = userDirectory.getContext(); addUserDirectory(userDirectory); if (isSystemRolesBaseDn(basePath)) { addStandardSystemRoles(); } if (log.isDebugEnabled()) { - log.debug("User directory " + userDirectory.getGlobalId() + (u != null ? " [" + u.getScheme() + "]" : "") + log.debug("User directory " + userDirectory.getContext() + (u != null ? " [" + u.getScheme() + "]" : "") + " enabled." + (realm != null ? " " + realm + " realm." : "")); } return userDirectory; diff --git a/org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java b/org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java deleted file mode 100644 index fb5394058..000000000 --- a/org.argeo.cms/src/org/argeo/cms/security/NodeSecurityUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.argeo.cms.security; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -import org.argeo.api.cms.CmsConstants; - -public class NodeSecurityUtils { - public final static LdapName ROLE_ADMIN_NAME, ROLE_DATA_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, - ROLE_USER_ADMIN_NAME; - public final static List RESERVED_ROLES; - static { - try { - ROLE_ADMIN_NAME = new LdapName(CmsConstants.ROLE_ADMIN); - ROLE_DATA_ADMIN_NAME = new LdapName(CmsConstants.ROLE_DATA_ADMIN); - ROLE_USER_NAME = new LdapName(CmsConstants.ROLE_USER); - ROLE_USER_ADMIN_NAME = new LdapName(CmsConstants.ROLE_USER_ADMIN); - ROLE_ANONYMOUS_NAME = new LdapName(CmsConstants.ROLE_ANONYMOUS); - RESERVED_ROLES = Collections.unmodifiableList(Arrays.asList( - new LdapName[] { ROLE_ADMIN_NAME, ROLE_ANONYMOUS_NAME, ROLE_USER_NAME, ROLE_USER_ADMIN_NAME })); - } catch (InvalidNameException e) { - throw new Error("Cannot initialize login module class", e); - } - } - - public static void checkUserName(LdapName name) throws IllegalArgumentException { - if (RESERVED_ROLES.contains(name)) - throw new IllegalArgumentException(name + " is a reserved name"); - } - - public static void checkImpliedPrincipalName(LdapName roleName) throws IllegalArgumentException { -// if (ROLE_USER_NAME.equals(roleName) || ROLE_ANONYMOUS_NAME.equals(roleName)) -// throw new IllegalArgumentException(roleName + " cannot be listed as role"); - } - -} diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java index f2e1d2606..e13f56289 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/AbstractUserDirectory.java @@ -50,8 +50,8 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { private final Hashtable properties; private final LdapName baseDn; // private final LdapName userBaseDn, groupBaseDn; - private final Rdn userBaseRdn, groupBaseRdn; - private final String userObjectClass, userBase, groupObjectClass, groupBase; + private final Rdn userBaseRdn, groupBaseRdn, systemRoleBaseRdn; + private final String userObjectClass, groupObjectClass; private final boolean readOnly; private final boolean disabled; @@ -97,15 +97,17 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { forcedPassword = UserAdminConf.forcedPassword.getValue(properties); userObjectClass = UserAdminConf.userObjectClass.getValue(properties); - userBase = UserAdminConf.userBase.getValue(properties); + String userBase = UserAdminConf.userBase.getValue(properties); groupObjectClass = UserAdminConf.groupObjectClass.getValue(properties); - groupBase = UserAdminConf.groupBase.getValue(properties); + String groupBase = UserAdminConf.groupBase.getValue(properties); + String systemRoleBase = UserAdminConf.systemRoleBase.getValue(properties); try { baseDn = new LdapName(UserAdminConf.baseDn.getValue(properties)); userBaseRdn = new Rdn(userBase); // userBaseDn = new LdapName(userBase + "," + baseDn); groupBaseRdn = new Rdn(groupBase); // groupBaseDn = new LdapName(groupBase + "," + baseDn); + systemRoleBaseRdn = new Rdn(systemRoleBase); } catch (InvalidNameException e) { throw new IllegalArgumentException("Badly formated base DN " + UserAdminConf.baseDn.getValue(properties), e); @@ -151,7 +153,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { */ @Override - public String getGlobalId() { + public String getContext() { return getBaseDn().toString(); } @@ -190,20 +192,21 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { try { LdapName name = (LdapName) getBaseDn().clone(); String[] segments = path.split("/"); - String parentSegment = null; + Rdn parentRdn = null; for (String segment : segments) { - String attr = "ou"; - if (parentSegment != null) { - if (getUserBase().equals(parentSegment)) - attr = "uid"; - else if (getGroupBase().equals(parentSegment)) - attr = "cn"; + // TODO make attr names configurable ? + String attr = LdapAttrs.ou.name(); + if (parentRdn != null) { + if (getUserBaseRdn().equals(parentRdn)) + attr = LdapAttrs.uid.name(); + else if (getGroupBaseRdn().equals(parentRdn)) + attr = LdapAttrs.cn.name(); + else if (getSystemRoleBaseRdn().equals(parentRdn)) + attr = LdapAttrs.cn.name(); } Rdn rdn = new Rdn(attr, segment); name.add(rdn); - - // TODO make it more robust using RDNs - parentSegment = rdn.toString(); + parentRdn = rdn; } return name; } catch (InvalidNameException e) { @@ -534,10 +537,6 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { throw new UnsupportedOperationException(); } - void isFunctionalHierarchyUnit(HierarchyUnit hu) { - - } - // @Override // public List getHierarchyUnitRoles(String filter, boolean deep) { // try { @@ -548,7 +547,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { // } @Override - public Iterable getRootHierarchyUnits(boolean functionalOnly) { + public Iterable getDirectHierarchyUnits(boolean functionalOnly) { throw new UnsupportedOperationException(); } @@ -561,6 +560,9 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { } protected DirectoryGroup newGroup(LdapName name, Attributes attrs) { + if (LdapNameUtils.getParentRdn(name).equals(getSystemRoleBaseRdn())) + return new LdifGroup.LdifSystemPermissions(this, name, attrs); + if (hasObjectClass(attrs, LdapObjs.organization)) return new LdifGroup.LdifOrganization(this, name, attrs); else @@ -638,7 +640,7 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { protected int roleType(LdapName dn) { Rdn technicalRdn = LdapNameUtils.getParentRdn(dn); - if (groupBaseRdn.equals(technicalRdn)) + if (getGroupBaseRdn().equals(technicalRdn) || getSystemRoleBaseRdn().equals(technicalRdn)) return Role.GROUP; else if (userBaseRdn.equals(technicalRdn)) return Role.USER; @@ -652,11 +654,6 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { return userObjectClass; } - @Deprecated - String getUserBase() { - return userBase; - } - Rdn getUserBaseRdn() { return userBaseRdn; } @@ -669,15 +666,14 @@ abstract class AbstractUserDirectory implements UserAdmin, UserDirectory { return groupObjectClass; } - @Deprecated - String getGroupBase() { - return groupBase; - } - Rdn getGroupBaseRdn() { return groupBaseRdn; } + Rdn getSystemRoleBaseRdn() { + return systemRoleBaseRdn; + } + LdapName getBaseDn() { return (LdapName) baseDn.clone(); } diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java index 54a6d9e31..ca1fa338b 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/AggregatingUserAdmin.java @@ -160,7 +160,7 @@ public class AggregatingUserAdmin implements UserAdmin { if (!(ud instanceof AbstractUserDirectory)) throw new IllegalArgumentException("Only " + AbstractUserDirectory.class.getName() + " is supported"); AbstractUserDirectory userDirectory = (AbstractUserDirectory) ud; - String basePath = userDirectory.getGlobalId(); + String basePath = userDirectory.getContext(); if (isSystemRolesBaseDn(basePath)) { this.systemRoles = userDirectory; systemRoles.setExternalRoles(this); @@ -272,7 +272,7 @@ public class AggregatingUserAdmin implements UserAdmin { } public Set getUserDirectories() { - TreeSet res = new TreeSet<>((o1, o2) -> o1.getGlobalId().compareTo(o2.getGlobalId())); + TreeSet res = new TreeSet<>((o1, o2) -> o1.getContext().compareTo(o2.getContext())); res.addAll(businessRoles.values()); return res; } diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/HierarchyUnit.java b/org.argeo.util/src/org/argeo/osgi/useradmin/HierarchyUnit.java index ba76617d7..2c21342e3 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/HierarchyUnit.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/HierarchyUnit.java @@ -10,13 +10,11 @@ public interface HierarchyUnit { HierarchyUnit getParent(); - Iterable getDirectHierachyUnits(); - - Iterable getFunctionalHierachyUnits(); + Iterable getDirectHierachyUnits(boolean functionalOnly); boolean isFunctional(); - String getBasePath(); + String getContext(); List getHierarchyUnitRoles(String filter, boolean deep); diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java b/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java index a9bc9417f..076f26949 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/IpaUtils.java @@ -20,6 +20,7 @@ import org.argeo.util.naming.LdapAttrs; public class IpaUtils { public final static String IPA_USER_BASE = "cn=users,cn=accounts"; public final static String IPA_GROUP_BASE = "cn=groups,cn=accounts"; + public final static String IPA_ROLE_BASE = "cn=roles,cn=accounts"; public final static String IPA_SERVICE_BASE = "cn=services,cn=accounts"; private final static String KRB_PRINCIPAL_NAME = LdapAttrs.krbPrincipalName.name().toLowerCase(); @@ -37,6 +38,7 @@ public class IpaUtils { properties.put(UserAdminConf.realm.name(), realm); properties.put(UserAdminConf.userBase.name(), IPA_USER_BASE); properties.put(UserAdminConf.groupBase.name(), IPA_GROUP_BASE); + properties.put(UserAdminConf.systemRoleBase.name(), IPA_ROLE_BASE); properties.put(UserAdminConf.readOnly.name(), Boolean.TRUE.toString()); } diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifHierarchyUnit.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifHierarchyUnit.java index b4813e685..d4259c87d 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifHierarchyUnit.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifHierarchyUnit.java @@ -1,6 +1,7 @@ package org.argeo.osgi.useradmin; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Objects; @@ -31,7 +32,8 @@ class LdifHierarchyUnit implements HierarchyUnit { this.attributes = attributes; Rdn rdn = LdapNameUtils.getLastRdn(dn); - functional = !(directory.getUserBaseRdn().equals(rdn) || directory.getGroupBaseRdn().equals(rdn)); + functional = !(directory.getUserBaseRdn().equals(rdn) || directory.getGroupBaseRdn().equals(rdn) + || directory.getSystemRoleBaseRdn().equals(rdn)); } @Override @@ -40,18 +42,16 @@ class LdifHierarchyUnit implements HierarchyUnit { } @Override - public Iterable getDirectHierachyUnits() { - return children; - } - - @Override - public Iterable getFunctionalHierachyUnits() { + public Iterable getDirectHierachyUnits(boolean functionalOnly) { List res = new ArrayList<>(); - for (HierarchyUnit hu : children) { - if (hu.isFunctional()) - res.add(hu); - } - return res; + if (functionalOnly) + for (HierarchyUnit hu : children) { + if (hu.isFunctional()) + res.add(hu); + } + else + res.addAll(children); + return Collections.unmodifiableList(res); } @Override @@ -71,7 +71,7 @@ class LdifHierarchyUnit implements HierarchyUnit { } @Override - public String getBasePath() { + public String getContext() { return dn.toString(); } diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java index 224be1870..0a925e4eb 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/LdifUserAdmin.java @@ -329,7 +329,7 @@ public class LdifUserAdmin extends AbstractUserDirectory { } @Override - public Iterable getRootHierarchyUnits(boolean functionalOnly) { + public Iterable getDirectHierarchyUnits(boolean functionalOnly) { if (functionalOnly) { List res = new ArrayList<>(); for (HierarchyUnit hu : rootHierarchyUnits) { diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java index 329da9149..b0a52626a 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/OsUserDirectory.java @@ -23,7 +23,7 @@ public class OsUserDirectory extends AbstractUserDirectory { public OsUserDirectory(URI uriArg, Dictionary props) { super(uriArg, props, false); try { - osUserDn = new LdapName(LdapAttrs.uid.name() + "=" + osUsername + "," + getUserBase() + "," + getBaseDn()); + osUserDn = new LdapName(LdapAttrs.uid.name() + "=" + osUsername + "," + getUserBaseRdn() + "," + getBaseDn()); Attributes attributes = new BasicAttributes(); attributes.put(LdapAttrs.uid.name(), osUsername); osUser = newUser(osUserDn, attributes); diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java index 3631de40f..93ca94ca6 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/UserAdminConf.java @@ -34,6 +34,9 @@ public enum UserAdminConf { /** Relative base DN for users */ groupBase("ou=Groups"), + /** Relative base DN for users */ + systemRoleBase("ou=Roles"), + /** Read-only source */ readOnly(null), diff --git a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java index 727d9295d..18bb1a0a6 100644 --- a/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java +++ b/org.argeo.util/src/org/argeo/osgi/useradmin/UserDirectory.java @@ -11,7 +11,7 @@ public interface UserDirectory { * The base of the hierarchy defined by this directory. This could typically be * an LDAP base DN. */ - String getGlobalId(); + String getContext(); String getName(); @@ -35,7 +35,7 @@ public interface UserDirectory { Optional getRealm(); - Iterable getRootHierarchyUnits(boolean functionalOnly); + Iterable getDirectHierarchyUnits(boolean functionalOnly); HierarchyUnit getHierarchyUnit(String path); -- 2.30.2