log4j.rootLogger=WARN, development
log4j.logger.org.argeo=DEBUG
+log4j.logger.org.apache.jackrabbit.core.RepositoryImpl=DEBUG
#log4j.logger.argeo.stats=DEBUG
## Appenders
package org.argeo.cms;
+import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
}
@Override
- protected final void createContents(Composite parent) {
- try {
- getShell().getDisplay().setData(CmsSession.KEY, this);
-
- createUi(parent);
- } catch (Exception e) {
- throw new CmsException("Cannot create entrypoint contents", e);
- }
+ protected final void createContents(final Composite parent) {
+ getShell().getDisplay().setData(CmsSession.KEY, this);
+ Subject.doAs(subject, new PrivilegedAction<Void>() {
+ @Override
+ public Void run() {
+ try {
+ createUi(parent);
+ } catch (Exception e) {
+ throw new CmsException("Cannot create entrypoint contents",
+ e);
+ }
+ return null;
+ }
+ });
}
/** Create UI */
public void navigateTo(String state) {
exception = null;
String title = setState(state);
- refresh();
+ doRefresh();
if (browserNavigation != null)
browserNavigation.pushState(state, title);
}
@Override
public void authChange() {
- try {
- String currentPath = null;
- if (node != null)
- currentPath = node.getPath();
- JcrUtils.logoutQuietly(session);
+ Subject.doAs(subject, new PrivilegedAction<Void>() {
- session = repository.login(workspace);
- if (currentPath != null)
+ @Override
+ public Void run() {
try {
- node = session.getNode(currentPath);
- } catch (Exception e) {
- try {
- // TODO find a less hacky way to log out
- new ArgeoLoginContext(
- KernelHeader.LOGIN_CONTEXT_ANONYMOUS, subject)
- .logout();
- new ArgeoLoginContext(
- KernelHeader.LOGIN_CONTEXT_ANONYMOUS, subject)
- .login();
- } catch (LoginException eAnonymous) {
- throw new ArgeoException("Cannot reset to anonymous",
- eAnonymous);
- }
+ String currentPath = null;
+ if (node != null)
+ currentPath = node.getPath();
JcrUtils.logoutQuietly(session);
+
session = repository.login(workspace);
- navigateTo("~");
- throw e;
+ if (currentPath != null)
+ try {
+ node = session.getNode(currentPath);
+ } catch (Exception e) {
+ try {
+ // TODO find a less hacky way to log out
+ new ArgeoLoginContext(
+ KernelHeader.LOGIN_CONTEXT_ANONYMOUS,
+ subject).logout();
+ new ArgeoLoginContext(
+ KernelHeader.LOGIN_CONTEXT_ANONYMOUS,
+ subject).login();
+ } catch (LoginException eAnonymous) {
+ throw new ArgeoException(
+ "Cannot reset to anonymous", eAnonymous);
+ }
+ JcrUtils.logoutQuietly(session);
+ session = repository.login(workspace);
+ navigateTo("~");
+ throw e;
+ }
+
+ // refresh UI
+ doRefresh();
+ } catch (RepositoryException e) {
+ throw new CmsException("Cannot perform auth change", e);
}
+ return null;
+ }
- // refresh UI
- refresh();
- } catch (RepositoryException e) {
- throw new CmsException("Cannot perform auth change", e);
- }
+ });
}
@Override
- public void exception(Throwable e) {
- this.exception = e;
+ public void exception(final Throwable e) {
+ AbstractCmsEntryPoint.this.exception = e;
log.error("Unexpected exception in CMS", e);
- refresh();
+ doRefresh();
+ }
+
+ protected void doRefresh() {
+ Subject.doAs(subject, new PrivilegedAction<Void>() {
+ @Override
+ public Void run() {
+ refresh();
+ return null;
+ }
+ });
}
@Override
final static String LOGIN_CONTEXT_SINGLE_USER = "SINGLE_USER";
// RESERVED ROLES
- public final static String ROLE_ADMIN = "ROLE_ADMIN";
- public final static String ROLE_GROUP_ADMIN = "ROLE_GROUP_ADMIN";
- public final static String ROLE_USER_ADMIN = "ROLE_USER_ADMIN";
- public final static String ROLE_USER = "ROLE_USER";
- public final static String ROLE_ANONYMOUS = "ROLE_ANONYMOUS";
+ public final static String ROLE_ADMIN = "cn=admin,ou=system,ou=node";
+ public final static String ROLE_GROUP_ADMIN = "cn=groupAdmin,ou=system,ou=node";
+ public final static String ROLE_USER_ADMIN = "cn=userAdmin,ou=system,ou=node";
+ // Special system groups that cannot be edited:
+ // user U anonymous = everyone
+ public final static String ROLE_USER = "cn=user,ou=system,ou=node";
+ public final static String ROLE_ANONYMOUS = "cn=anonymous,ou=system,ou=node";
// RESERVED USERNAMES
public final static String USERNAME_ADMIN = "root";
public final static String USERNAME_DEMO = "demo";
+ @Deprecated
public final static String USERNAME_ANONYMOUS = "anonymous";
}
--- /dev/null
+package org.argeo.cms.internal.auth;
+
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.cms.CmsException;
+import org.osgi.service.useradmin.Authorization;
+
+/**
+ * A {@link Principal} which has been implied by an {@link Authorization}. If it
+ * is empty it meeans 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
+ * identityx is removed, the related {@link ImpliedByPrincipal}s can thus be
+ * removed.
+ */
+public final class ImpliedByPrincipal implements Group {
+ private final LdapName name;
+ private Set<Principal> causes = new HashSet<Principal>();
+
+ public ImpliedByPrincipal(String name, Principal userPrincipal) {
+ try {
+ this.name = new LdapName(name);
+ } catch (InvalidNameException e) {
+ throw new CmsException("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);
+ }
+
+ @Override
+ public String getName() {
+ return name.toString();
+ }
+
+ @Override
+ public boolean addMember(Principal user) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean removeMember(Principal user) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean isMember(Principal member) {
+ return causes.contains(member);
+ }
+
+ @Override
+ public Enumeration<? extends Principal> members() {
+ return Collections.enumeration(causes);
+ }
+
+ @Override
+ public int hashCode() {
+ return name.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ // if (this == obj)
+ // return true;
+ 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() + ", implied by " + causes;
+ }
+}
--- /dev/null
+package org.argeo.cms.internal.auth;
+
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.security.Principal;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Set;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.CredentialNotFoundException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.KernelHeader;
+import org.argeo.cms.internal.kernel.Activator;
+import org.osgi.framework.BundleContext;
+import org.osgi.service.useradmin.Authorization;
+import org.osgi.service.useradmin.User;
+import org.osgi.service.useradmin.UserAdmin;
+
+public class UserAdminLoginModule implements LoginModule {
+ private Subject subject;
+ private CallbackHandler callbackHandler;
+ private boolean isAnonymous = false;
+
+ private final static LdapName ROLE_USER_NAME, ROLE_ANONYMOUS_NAME;
+ private final static X500Principal ROLE_ANONYMOUS_PRINCIPAL;
+ static {
+ try {
+ ROLE_USER_NAME = new LdapName(KernelHeader.ROLE_USER);
+ ROLE_ANONYMOUS_NAME = new LdapName(KernelHeader.ROLE_ANONYMOUS);
+ ROLE_ANONYMOUS_PRINCIPAL = new X500Principal(
+ ROLE_ANONYMOUS_NAME.toString());
+ } catch (InvalidNameException e) {
+ throw new Error("Cannot initialize login module class", e);
+ }
+ }
+
+ private Authorization authorization;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler,
+ Map<String, ?> sharedState, Map<String, ?> options) {
+ try {
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+ if (options.containsKey("anonymous"))
+ isAnonymous = Boolean.parseBoolean(options.get("anonymous")
+ .toString());
+ // String ldifFile = options.get("ldifFile").toString();
+ // InputStream in = new URL(ldifFile).openStream();
+ // userAdmin = new LdifUserAdmin(in);
+ } catch (Exception e) {
+ throw new CmsException("Cannot initialize login module", e);
+ }
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ // TODO use a callback in order to get the bundle context
+ BundleContext bc = Activator.getBundleContext();
+ UserAdmin userAdmin = bc.getService(bc
+ .getServiceReference(UserAdmin.class));
+ final User user;
+
+ if (!isAnonymous) {
+ // ask for username and password
+ NameCallback nameCallback = new NameCallback("User");
+ PasswordCallback passwordCallback = new PasswordCallback(
+ "Password", false);
+ // handle callbacks
+ try {
+ callbackHandler.handle(new Callback[] { nameCallback,
+ passwordCallback });
+ } catch (Exception e) {
+ throw new CmsException("Cannot handle callbacks", e);
+ }
+
+ // create credentials
+ final String username = nameCallback.getName();
+ if (username == null || username.trim().equals(""))
+ throw new CredentialNotFoundException("No credentials provided");
+
+ char[] password = {};
+ if (passwordCallback.getPassword() != null)
+ password = passwordCallback.getPassword();
+ else
+ throw new CredentialNotFoundException("No credentials provided");
+
+ user = (User) userAdmin.getRole(username);
+ if (user == null)
+ return false;
+
+ byte[] hashedPassword = ("{SHA}" + Base64
+ .encodeBase64String(DigestUtils.sha1(toBytes(password))))
+ .getBytes();
+ if (!user.hasCredential("userpassword", hashedPassword))
+ return false;
+ } else
+ // anonymous
+ user = null;
+ this.authorization = userAdmin.getAuthorization(user);
+ return true;
+ }
+
+ private byte[] toBytes(char[] chars) {
+ CharBuffer charBuffer = CharBuffer.wrap(chars);
+ ByteBuffer byteBuffer = Charset.forName("UTF-8").encode(charBuffer);
+ byte[] bytes = Arrays.copyOfRange(byteBuffer.array(),
+ byteBuffer.position(), byteBuffer.limit());
+ Arrays.fill(charBuffer.array(), '\u0000'); // clear sensitive data
+ Arrays.fill(byteBuffer.array(), (byte) 0); // clear sensitive data
+ return bytes;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ if (authorization != null) {
+ Set<Principal> principals = subject.getPrincipals();
+ try {
+ String authName = authorization.getName();
+
+ // determine user'S principal
+ final LdapName name;
+ final Principal userPrincipal;
+ if (authName == null) {
+ name = ROLE_ANONYMOUS_NAME;
+ userPrincipal = ROLE_ANONYMOUS_PRINCIPAL;
+ principals.add(userPrincipal);
+ } else {
+ name = new LdapName(authName);
+ userPrincipal = new X500Principal(name.toString());
+ principals.add(userPrincipal);
+ principals.add(new ImpliedByPrincipal(ROLE_USER_NAME,
+ userPrincipal));
+ }
+
+ // Add roles provided by authorization
+ for (String role : authorization.getRoles()) {
+ LdapName roleName = new LdapName(role);
+ if (ROLE_USER_NAME.equals(roleName))
+ throw new CmsException(ROLE_USER_NAME
+ + " cannot be listed as role");
+ if (ROLE_ANONYMOUS_NAME.equals(roleName))
+ throw new CmsException(ROLE_ANONYMOUS_NAME
+ + " cannot be listed as role");
+ if (roleName.equals(name)) {
+ // skip
+ } else {
+ principals.add(new ImpliedByPrincipal(roleName
+ .toString(), userPrincipal));
+ }
+ }
+
+ return true;
+ } catch (InvalidNameException e) {
+ throw new CmsException("Cannot commit", e);
+ }
+ } else
+ return false;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ cleanUp();
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ // TODO better deal with successive logout
+ if (subject == null)
+ return true;
+ // TODO make it less brutal
+ subject.getPrincipals().removeAll(
+ subject.getPrincipals(X500Principal.class));
+ subject.getPrincipals().removeAll(
+ subject.getPrincipals(ImpliedByPrincipal.class));
+ cleanUp();
+ return true;
+ }
+
+ private void cleanUp() {
+ subject = null;
+ authorization = null;
+ }
+}
package org.argeo.cms.internal.kernel;
import java.lang.management.ManagementFactory;
+import java.net.URL;
import java.util.HashMap;
import java.util.Map;
private KernelThread kernelThread;
void init() {
+ URL url = getClass().getClassLoader().getResource(
+ KernelConstants.JAAS_CONFIG);
+ System.setProperty("java.security.auth.login.config",
+ url.toExternalForm());
+
ClassLoader currentContextCl = Thread.currentThread()
.getContextClassLoader();
Thread.currentThread().setContextClassLoader(
final static String REPO_SEARCH_CACHE_SIZE = "argeo.node.repo.searchCacheSize";
final static String REPO_MAX_VOLATILE_INDEX_SIZE = "argeo.node.repo.maxVolatileIndexSize";
+ // Node Security
+ /** URI to an LDIF file used as initialization or backend */
+ final static String USERADMIN_URI = "argeo.node.useradmin.uri";
+
final static String[] DEFAULT_CNDS = { "/org/argeo/jcr/argeo.cnd",
"/org/argeo/cms/cms.cnd" };
import org.argeo.cms.internal.useradmin.JcrUserAdmin;
import org.argeo.cms.internal.useradmin.SimpleJcrSecurityModel;
import org.argeo.cms.internal.useradmin.jackrabbit.JackrabbitUserAdminService;
+import org.argeo.osgi.useradmin.LdifUserAdmin;
import org.argeo.security.OsAuthenticationToken;
import org.argeo.security.UserAdminService;
import org.argeo.security.core.InternalAuthentication;
private final InternalAuthenticationProvider internalAuth;
private final AnonymousAuthenticationProvider anonymousAuth;
private final JackrabbitUserAdminService userAdminService;
- private final JcrUserAdmin userAdmin;
+ private final LdifUserAdmin userAdmin;
private ServiceRegistration<AuthenticationManager> authenticationManagerReg;
private ServiceRegistration<UserAdminService> userAdminServiceReg;
public NodeSecurity(BundleContext bundleContext, JackrabbitNode node)
throws RepositoryException {
- URL url = getClass().getClassLoader().getResource(
- KernelConstants.JAAS_CONFIG);
- System.setProperty("java.security.auth.login.config",
- url.toExternalForm());
-
this.bundleContext = bundleContext;
osAuth = new OsAuthenticationProvider();
userAdminService.setSecurityModel(new SimpleJcrSecurityModel());
userAdminService.init();
- userAdmin = new JcrUserAdmin(bundleContext, node);
- userAdmin.setUserAdminService(userAdminService);
+ String userAdminUri = KernelUtils
+ .getFrameworkProp(KernelConstants.USERADMIN_URI);
+ if (userAdminUri == null)
+ userAdminUri = getClass().getResource("demo.ldif").toString();
+ userAdmin = new LdifUserAdmin(userAdminUri);
}
public void publish() {
--- /dev/null
+dn: dc=example,dc=org
+objectClass: domain
+objectClass: extensibleObject
+objectClass: top
+dc: example
+
+dn: ou=groups,dc=example,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: groups
+
+dn: ou=users,dc=example,dc=org
+objectClass: organizationalUnit
+objectClass: top
+ou: users
+
+dn: uid=demo,ou=users,dc=example,dc=org
+objectClass: organizationalPerson
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: top
+cn: Demo User
+description: Demo user
+givenname: Demo
+mail: demo@localhost
+sn: User
+uid: demo
+userpassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
+
+dn: uid=root,ou=users,dc=example,dc=org
+objectClass: person
+objectClass: inetOrgPerson
+objectClass: organizationalPerson
+objectClass: top
+cn: Super User
+description: Superuser
+givenname: Super
+mail: root@localhost
+sn: User
+uid: root
+userpassword:: e1NIQX1pZVNWNTVRYytlUU9hWURSU2hhL0Fqek5USkU9
+
+dn: cn=admin,ou=system,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: admin
+member: uid=root,ou=users,dc=example,dc=org
+
+dn: cn=userAdmin,ou=system,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: userAdmin
+member: cn=admin,ou=system,ou=node
+member: uid=demo,ou=users,dc=example,dc=org
+
+dn: cn=groupAdmin,ou=system,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: groupAdmin
+member: cn=admin,ou=system,ou=node
+
+dn: cn=editor,ou=cms,ou=node
+objectClass: groupOfNames
+objectClass: top
+cn: admin
+member: cn=admin,ou=system,ou=node
+member: uid=demo,ou=users,dc=example,dc=org
+
USER {
+ org.argeo.cms.internal.auth.UserAdminLoginModule requisite;
+};
+
+OLD_USER {
org.argeo.cms.internal.auth.EndUserLoginModule requisite;
org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
};
ANONYMOUS {
+ org.argeo.cms.internal.auth.UserAdminLoginModule requisite anonymous=true;
+};
+
+OLD_ANONYMOUS {
org.argeo.cms.internal.auth.AnonymousLoginModule requisite;
org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
};
SYSTEM {
+ org.argeo.cms.internal.auth.SystemLoginModule requisite;
+ org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
+ org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
+
+OLD_SYSTEM {
org.argeo.cms.internal.auth.SystemLoginModule requisite;
org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
};
org.argeo.cms.internal.auth.SingleUserLoginModule requisite;
org.springframework.security.authentication.jaas.SecurityContextLoginModule requisite;
};
+
+Jackrabbit {
+ org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
+
</SecurityManager>
<AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager">
</AccessManager>
- <LoginModule class="org.argeo.security.jackrabbit.ArgeoLoginModule">
- </LoginModule>
+<!-- <LoginModule class="org.argeo.security.jackrabbit.ArgeoLoginModule"> -->
+<!-- </LoginModule> -->
</Security>
</Repository>
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.cms.util;
+
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.acl.Group;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.ArgeoException;
+import org.argeo.cms.CmsSession;
+
+/**
+ * Retrieves information about the current user. Not an API, can change without
+ * notice.
+ */
+class CurrentUserUtils {
+ public final static String getUsername() {
+ Subject subject = getSubject();
+ if (subject == null)
+ return null;
+ Principal principal = subject.getPrincipals(X500Principal.class)
+ .iterator().next();
+ return principal.getName();
+
+ }
+
+ public final static Set<String> roles() {
+ Set<String> roles = Collections.synchronizedSet(new HashSet<String>());
+ // roles.add("ROLE_USER");
+ Subject subject = getSubject();
+ X500Principal userPrincipal = subject
+ .getPrincipals(X500Principal.class).iterator().next();
+ roles.add(userPrincipal.getName());
+ for (Principal group : subject.getPrincipals(Group.class)) {
+ roles.add(group.getName());
+ }
+ return roles;
+ }
+
+ public final static Subject getSubject() {
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ if (subject == null) {
+ subject = CmsSession.current.get().getSubject();
+ if (subject == null)
+ throw new ArgeoException("Not authenticated.");
+ }
+ return subject;
+ }
+}
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
/** The site-related user menu */
public class UserMenu extends Shell implements CmsStyles, CallbackHandler {
super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP);
setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU);
- Authentication authentication = SecurityContextHolder.getContext()
- .getAuthentication();
- if (authentication == null)
- throw new CmsException("No authentication available");
+ // Authentication authentication = SecurityContextHolder.getContext()
+ // .getAuthentication();
+ // if (authentication == null)
+ // throw new CmsException("No authentication available");
- String username = authentication.getName();
- if (username.equals(KernelHeader.USERNAME_ANONYMOUS)) {
+ String username = CurrentUserUtils.getUsername();
+ if (username.equalsIgnoreCase(KernelHeader.ROLE_ANONYMOUS)) {
username = null;
anonymousUi();
} else {
});
}
- protected String getUsername() {
- String username = SecurityContextHolder.getContext()
- .getAuthentication().getName();
- return username;
- }
+ // protected String getUsername() {
+ // // String username = SecurityContextHolder.getContext()
+ // // .getAuthentication().getName();
+ // return CurrentUserUtils.getUsername();
+ // }
/** To be overridden */
protected void specificUserUi(Composite parent) {
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
-import org.springframework.security.core.context.SecurityContextHolder;
/** Open the user menu when clicked */
public class UserMenuLink extends MenuLink {
@Override
public Control createUi(Composite parent, Node context) {
- String username = SecurityContextHolder.getContext()
- .getAuthentication().getName();
- if (username.equals(KernelHeader.USERNAME_ANONYMOUS))
+ // String username = SecurityContextHolder.getContext()
+ // .getAuthentication().getName();
+ String username = CurrentUserUtils.getUsername();
+ if (username.equalsIgnoreCase(KernelHeader.ROLE_ANONYMOUS))
setLabel(CmsMsg.login.lead());
else
setLabel(username);
<!-- Reduce visibility of JCR Browser perspective to users that are in ROLE_ADMIN -->
<extension
point="org.eclipse.ui.activities">
+ <!--
<activity
description="Only for admins"
id="org.argeo.eclipse.ui.workbench.adminActivity"
</with>
</enabledWhen>
</activity>
+ -->
<activityPatternBinding
pattern="org.argeo.eclipse.ui.workbench/org.argeo.eclipse.ui.workbench.osgiPerspective"
isEqualityPattern="true"
- activityId="org.argeo.eclipse.ui.workbench.adminActivity">
+ activityId="org.argeo.security.ui.adminActivity">
<!-- activityId="org.argeo.security.ui.adminActivity" -->
</activityPatternBinding>
<activityPatternBinding
pattern="org.argeo.eclipse.ui.workbench/org.argeo.eclipse.ui.workbench.jcrBrowserPerspective"
isEqualityPattern="true"
- activityId="org.argeo.eclipse.ui.workbench.adminActivity">
+ activityId="org.argeo.security.ui.adminActivity">
</activityPatternBinding>
</extension>
</plugin>
\ No newline at end of file
@Override
public String getName() {
+ if (user == null)
+ return null;
return user.getName();
}
@Override
public String[] getRoles() {
List<Role> allRoles = getAllRoles();
- String[] res = new String[allRoles.size() + 1];
- res[0] = user.getName();
+ if (user != null)
+ allRoles.add(0, user);
+ String[] res = new String[allRoles.size()];
for (int i = 0; i < allRoles.size(); i++)
- res[i + 1] = allRoles.get(i).getName();
+ res[i] = allRoles.get(i).getName();
return res;
}
List<Role> getAllRoles() {
List<Role> allRoles = new ArrayList<Role>();
- collectRoles(user, allRoles);
+ if (user != null)
+ collectRoles(user, allRoles);
+ else
+ collectAnonymousRoles(allRoles);
return allRoles;
}
}
}
+ private void collectAnonymousRoles(List<Role> allRoles) {
+ // TODO gather anonymous roles
+ }
+
}
package org.argeo.osgi.useradmin;
import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.util.SortedMap;
import java.util.TreeMap;
import org.osgi.service.useradmin.User;
import org.osgi.service.useradmin.UserAdmin;
+/** User admin implementation using LDIF file(s) as backend. */
public class LdifUserAdmin implements UserAdmin {
SortedMap<LdapName, LdifUser> users = new TreeMap<LdapName, LdifUser>();
SortedMap<LdapName, LdifGroup> groups = new TreeMap<LdapName, LdifGroup>();
+ private final boolean isReadOnly;
+ private final URI uri;
+
+ public LdifUserAdmin(String uri) {
+ this(uri, true);
+ }
+
+ public LdifUserAdmin(String uri, boolean isReadOnly) {
+ this.isReadOnly = isReadOnly;
+ try {
+ this.uri = new URI(uri);
+ } catch (URISyntaxException e) {
+ throw new ArgeoUserAdminException("Invalid URI " + uri, e);
+ }
+
+ if (!isReadOnly && !this.uri.getScheme().equals("file:"))
+ throw new UnsupportedOperationException(this.uri.getScheme()
+ + "not supported read-write.");
+
+ try {
+ load(this.uri.toURL().openStream());
+ } catch (Exception e) {
+ throw new ArgeoUserAdminException("Cannot open URL " + this.uri, e);
+ }
+ }
+
public LdifUserAdmin(InputStream in) {
+ load(in);
+ isReadOnly = true;
+ this.uri = null;
+ }
+
+ protected void load(InputStream in) {
try {
LdifParser ldifParser = new LdifParser();
SortedMap<LdapName, Attributes> allEntries = ldifParser.read(in);
}
} catch (Exception e) {
throw new ArgeoUserAdminException(
- "Cannot initialise user admin service from LDIF", e);
+ "Cannot load user admin service from LDIF", e);
}
}
+ public void destroy() {
+ users.clear();
+ users = null;
+ groups.clear();
+ groups = null;
+ }
+
@Override
public Role getRole(String name) {
LdapName key;
throw new UnsupportedOperationException();
}
+ public boolean getIsReadOnly() {
+ return isReadOnly;
+ }
+
}
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="src" path="src" />
- <classpathentry kind="con"
- path="org.eclipse.pde.core.requiredPlugins" />
- <classpathentry kind="con"
- path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7" />
- <classpathentry kind="output" path="bin" />
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="ext/test"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
+ <classpathentry kind="output" path="bin"/>
</classpath>
-source.. = src/
+source.. = src/,\
+ ext/test/
+
+additional.bundles = org.junit,\
+ org.apache.jackrabbit.core,\
+ javax.jcr,\
+ org.apache.jackrabbit.api,\
+ org.apache.jackrabbit.data,\
+ org.apache.jackrabbit.jcr.commons,\
+ org.apache.jackrabbit.spi,\
+ org.apache.jackrabbit.spi.commons,\
+ org.slf4j.api,\
+ org.slf4j.commons.logging,\
+ org.slf4j.log4j12,\
+ org.apache.log4j,\
+ org.apache.commons.collections,\
+ EDU.oswego.cs.dl.util.concurrent,\
+ org.apache.lucene,\
+ org.apache.tika.core,\
+ org.apache.tika.parsers,\
+ org.apache.commons.dbcp,\
+ org.apache.commons.pool,\
+ org.argeo.server.jcr
+
--- /dev/null
+log4j.rootLogger=WARN, console
+
+## Levels
+log4j.logger.org.argeo=DEBUG
+log4j.logger.org.apache.jackrabbit=OFF
+log4j.logger.org.apache.jackrabbit.core.security=DEBUG
+log4j.logger.org.apache.jackrabbit.core.DefaultSecurityManager=DEBUG
+
+## Appenders
+# console is set to be a ConsoleAppender.
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+
+# console uses PatternLayout.
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+#log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c%n
+#log4j.appender.console.layout.ConversionPattern=%m%n
+log4j.appender.console.layout.ConversionPattern=%d{ABSOLUTE} %m (%F:%L) [%t] %p %n
--- /dev/null
+package org.argeo.security.jackrabbit;
+
+import java.net.URL;
+
+import javax.jcr.Repository;
+import javax.jcr.Session;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.jackrabbit.unit.AbstractJackrabbitTestCase;
+
+public class JackrabbitAuthTest extends AbstractJackrabbitTestCase {
+ private final Log log = LogFactory.getLog(JackrabbitAuthTest.class);
+
+ public void testLogin() throws Exception {
+ // Subject subject = new Subject();
+ // LoginContext loginContext = new LoginContext("UNIX",subject);
+ // loginContext.login();
+
+ Repository repository = getRepository();
+ Session session = repository.login();
+ log.debug(session.getUserID());
+ }
+
+ @Override
+ protected Repository createRepository() throws Exception {
+ URL url = getClass().getResource("test_jaas.config");
+ System.setProperty("java.security.auth.login.config", url.toString());
+ return super.createRepository();
+ }
+
+ @Override
+ protected void clearRepository(Repository repository) throws Exception {
+ System.setProperty("java.security.auth.login.config", "");
+ }
+
+ @Override
+ protected String getRepositoryConfigResource() {
+ return "/org/argeo/security/jackrabbit/repository-memory-test.xml";
+ }
+
+}
--- /dev/null
+<?xml version="1.0"?>
+<!--
+
+ Copyright (C) 2007-2012 Argeo GmbH
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+ "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+<Repository>
+ <!-- File system and datastore -->
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+ <!-- Workspace templates -->
+ <Workspaces rootPath="${rep.home}/workspaces"
+ defaultWorkspace="main" configRootPath="/workspaces" />
+ <Workspace name="${wsp.name}">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="blobFSBlockSize" value="1" />
+ </PersistenceManager>
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/repository/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+ </Workspace>
+
+ <!-- Versioning -->
+ <Versioning rootPath="${rep.home}/version">
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ <PersistenceManager
+ class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+ <param name="blobFSBlockSize" value="1" />
+ </PersistenceManager>
+ </Versioning>
+
+ <!-- Indexing -->
+ <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+ <param name="path" value="${rep.home}/repository/index" />
+ <param name="directoryManagerClass"
+ value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+ <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+ </SearchIndex>
+
+ <!-- Security -->
+ <Security appName="Jackrabbit">
+ <SecurityManager class="org.argeo.security.jackrabbit.ArgeoSecurityManager"
+ workspaceName="security"/>
+ <AccessManager class="org.argeo.security.jackrabbit.ArgeoAccessManager"/>
+ </Security>
+</Repository>
\ No newline at end of file
--- /dev/null
+Jackrabbit {
+ org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
+};
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
import org.apache.jackrabbit.api.security.user.UserManager;
import org.apache.jackrabbit.core.DefaultSecurityManager;
import org.apache.jackrabbit.core.security.AccessManager;
import org.apache.jackrabbit.core.security.SecurityConstants;
import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
/** Integrates Spring Security and Jackrabbit Security users and roles. */
public class ArgeoSecurityManager extends DefaultSecurityManager {
@Override
public String getUserID(Subject subject, String workspaceName)
throws RepositoryException {
- Authentication authentication = SecurityContextHolder.getContext()
- .getAuthentication();
- if (authentication != null)
- return authentication.getName();
- else
+ Set<X500Principal> userPrincipal = subject
+ .getPrincipals(X500Principal.class);
+ if (userPrincipal.isEmpty())
return super.getUserID(subject, workspaceName);
+ if (userPrincipal.size() > 1)
+ throw new RuntimeException("Multiple user principals "
+ + userPrincipal);
+ return userPrincipal.iterator().next().getName();
+ // Authentication authentication = SecurityContextHolder.getContext()
+ // .getAuthentication();
+ // if (authentication != null)
+ // return authentication.getName();
+ // else
+ // return super.getUserID(subject, workspaceName);
}
@Override
--- /dev/null
+package org.argeo.security.jackrabbit;
+
+import java.security.Principal;
+import java.util.Map;
+import java.util.Set;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+
+import org.apache.jackrabbit.core.security.AnonymousPrincipal;
+import org.apache.jackrabbit.core.security.principal.AdminPrincipal;
+
+public class SystemJackrabbitLoginModule implements LoginModule {
+
+ private Subject subject;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler,
+ Map<String, ?> sharedState, Map<String, ?> options) {
+ this.subject = subject;
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ Set<Principal> principals = subject.getPrincipals();
+ if (principals.isEmpty()) {// system
+ subject.getPrincipals().add(new AdminPrincipal("admin"));
+ return true;
+ }
+ boolean isAdmin = false;
+ boolean isAnonymous = false;
+ // FIXME make it more generic
+ for (Principal principal : principals) {
+ if (principal.getName().equalsIgnoreCase(
+ "cn=admin,ou=system,ou=node"))
+ isAdmin = true;
+ else if (principal.getName().equalsIgnoreCase(
+ "cn=anonymous,ou=system,ou=node"))
+ isAnonymous = true;
+ }
+
+ if (isAnonymous && isAdmin)
+ throw new LoginException("Cannot be admin and anonymous");
+
+ // Add special Jackrabbit roles
+ if (isAdmin)
+ principals.add(new AdminPrincipal("admin"));
+ if (isAnonymous)// anonymous
+ principals.add(new AnonymousPrincipal());
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ subject.getPrincipals().removeAll(
+ subject.getPrincipals(AdminPrincipal.class));
+ return true;
+ }
+
+}
<enabledWhen>
<with variable="roles">
<iterate ifEmpty="false" operator="or">
- <equals value="ROLE_ANONYMOUS" />
+ <equals value="cn=anonymous,ou=system,ou=node" />
</iterate>
</with>
</enabledWhen>
<not>
<with variable="roles">
<iterate ifEmpty="false" operator="or">
- <equals value="ROLE_ANONYMOUS" />
+ <equals value="cn=anonymous,ou=system,ou=node" />
</iterate>
</with>
</not>
import javax.security.auth.login.CredentialNotFoundException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
+import javax.security.auth.x500.X500Principal;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.PlatformUI;
import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.context.SecurityContextHolder;
/**
* RAP entry point with login capabilities. Once the user has been
throw new ArgeoException("Cannot initialize login context", e1);
}
- tryLogin: while (subject.getPrincipals(Authentication.class).size() == 0) {
+ tryLogin: while (subject.getPrincipals(X500Principal.class).size() == 0) {
try {
loginContext.login();
- if (subject.getPrincipals(Authentication.class).size() == 0)
+ if (subject.getPrincipals(X500Principal.class).size() == 0)
throw new ArgeoException("Login succeeded but no auth");// fatal
// add security context to session
}
}
- final String username = subject.getPrincipals(Authentication.class)
+ final String username = subject.getPrincipals(X500Principal.class)
.iterator().next().getName();
// Logout callback when the display is disposed
display.disposeExec(new Runnable() {
private void fullLogout(LoginContext loginContext, String username) {
try {
loginContext.logout();
- SecurityContextHolder.clearContext();
+ // SecurityContextHolder.clearContext();
// HttpServletRequest httpRequest = RWT.getRequest();
// HttpSession httpSession = httpRequest.getSession();
<enabledWhen>
<with variable="roles">
<iterate ifEmpty="false" operator="or">
- <equals value="ROLE_USER" />
+ <equals value="cn=user,ou=system,ou=node" />
</iterate>
</with>
</enabledWhen>
<enabledWhen>
<with variable="roles">
<iterate ifEmpty="false" operator="or">
- <equals value="ROLE_ADMIN" />
+ <equals value="cn=admin,ou=system,ou=node" />
</iterate>
</with>
</enabledWhen>
<enabledWhen>
<with variable="roles">
<iterate ifEmpty="false" operator="or">
- <equals value="ROLE_USER_ADMIN" />
+ <equals value="cn=userAdmin,ou=system,ou=node" />
</iterate>
</with>
</enabledWhen>
<enabledWhen>
<with variable="roles">
<iterate ifEmpty="false" operator="or">
- <equals value="ROLE_GROUP_ADMIN" />
+ <equals value="cn=groupAdmin,ou=system,ou=node" />
</iterate>
</with>
</enabledWhen>
<not>
<with variable="roles">
<iterate ifEmpty="false" operator="or">
- <equals value="ROLE_ADMIN" />
+ <equals value="cn=admin,ou=system,ou=node" />
</iterate>
</with>
</not>
*/
package org.argeo.security.ui.internal;
+import java.security.AccessController;
+import java.security.Principal;
+import java.security.acl.Group;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.context.SecurityContextHolder;
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.ArgeoException;
/**
* Retrieves information about the current user. Not an API, can change without
* notice.
*/
public class CurrentUser {
- // public final static String getUsername() {
- // Subject subject = getSubject();
- // if (subject == null)
- // return null;
- // Principal principal = subject.getPrincipals().iterator().next();
- // return principal.getName();
- //
- // }
-
public final static String getUsername() {
- return getAuthentication().getName();
+ Subject subject = getSubject();
+ if (subject == null)
+ return null;
+ Principal principal = subject.getPrincipals(X500Principal.class)
+ .iterator().next();
+ return principal.getName();
+
}
public final static Set<String> roles() {
Set<String> roles = Collections.synchronizedSet(new HashSet<String>());
- Authentication authentication = getAuthentication();
- for (GrantedAuthority ga : authentication.getAuthorities()) {
- roles.add(ga.getAuthority());
+ // roles.add("ROLE_USER");
+ Subject subject = getSubject();
+ X500Principal userPrincipal = subject
+ .getPrincipals(X500Principal.class).iterator().next();
+ roles.add(userPrincipal.getName());
+ for (Principal group : subject.getPrincipals(Group.class)) {
+ roles.add(group.getName());
}
- return Collections.unmodifiableSet(roles);
+ return roles;
}
- public final static Authentication getAuthentication() {
- return SecurityContextHolder.getContext().getAuthentication();
- }
+ // public final static String getUsername() {
+ // return getAuthentication().getName();
+ // }
+
+ // public final static Set<String> roles() {
+ // Set<String> roles = Collections.synchronizedSet(new HashSet<String>());
+ // Authentication authentication = getAuthentication();
+ // for (GrantedAuthority ga : authentication.getAuthorities()) {
+ // roles.add(ga.getAuthority());
+ // }
+ // return Collections.unmodifiableSet(roles);
+ // }
+ //
+ // public final static Authentication getAuthentication() {
+ // return SecurityContextHolder.getContext().getAuthentication();
+ // }
// public final static Authentication getAuthentication() {
// Set<Authentication> authens = getSubject().getPrincipals(
// throw new ArgeoException("No authentication found");
// }
- // public final static Subject getSubject() {
- // Subject subject = Subject.getSubject(AccessController.getContext());
- // if (subject == null)
- // throw new ArgeoException("Not authenticated.");
- // return subject;
- // }
+ public final static Subject getSubject() {
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ if (subject == null)
+ throw new ArgeoException("Not authenticated.");
+ return subject;
+ }
}
public void createPartControl(Composite parent) {
parent.setLayout(new GridLayout(2, false));
- Authentication authentication = CurrentUser.getAuthentication();
- EclipseUiUtils.createGridLL(parent, "Name", authentication
- .getPrincipal().toString());
+// Authentication authentication = CurrentUser.getAuthentication();
+// EclipseUiUtils.createGridLL(parent, "Name", authentication
+// .getPrincipal().toString());
EclipseUiUtils.createGridLL(parent, "User ID",
CurrentUser.getUsername());
org.apache.lucene,\
org.apache.tika.core,\
org.apache.tika.parsers,\
- org.argeo.security.jackrabbit,\
org.apache.commons.dbcp,\
org.apache.commons.pool
// getHomeDir());
RepositoryConfig repositoryConfig = RepositoryConfig.create(
AbstractJackrabbitTestCase.class
- .getResourceAsStream("repository-memory.xml"),
+ .getResourceAsStream(getRepositoryConfigResource()),
getHomeDir().getAbsolutePath());
RepositoryImpl repositoryImpl = RepositoryImpl.create(repositoryConfig);
return repositoryImpl;
}
+ protected String getRepositoryConfigResource() {
+ return "repository-memory.xml";
+ }
+
@Override
protected void clearRepository(Repository repository) throws Exception {
RepositoryImpl repositoryImpl = (RepositoryImpl) repository;