path="/public"
brandingId="org.argeo.cms.ui.workbench.rap.defaultBranding">
</entrypoint>
+ <entrypoint
+ brandingId="org.argeo.cms.ui.workbench.rap.defaultBranding"
+ class="org.argeo.cms.ui.workbench.rap.SpnegoWorkbenchLogin"
+ id="org.argeo.cms.ui.workbench.rap.loginEntryPoint"
+ path="/login">
+ </entrypoint>
<!-- <entrypoint
id="org.argeo.cms.ui.workbench.rap.secureEntryPoint"
class="org.argeo.security.ui.rap.RapWorkbenchLogin"
--- /dev/null
+package org.argeo.cms.ui.workbench.rap;
+
+import java.security.PrivilegedAction;
+import java.util.Locale;
+
+import javax.security.auth.Subject;
+import javax.security.auth.x500.X500Principal;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.argeo.cms.CmsMsg;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.util.CmsUtils;
+import org.argeo.cms.util.LoginEntryPoint;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Display;
+import org.eclipse.ui.PlatformUI;
+
+public class SpnegoWorkbenchLogin extends LoginEntryPoint {
+ // private final static Log log =
+ // LogFactory.getLog(RapWorkbenchLogin.class);
+
+ /** Override to provide an application specific workbench advisor */
+ protected RapWorkbenchAdvisor createRapWorkbenchAdvisor(String username) {
+ return new RapWorkbenchAdvisor(username);
+ }
+
+ @Override
+ public int createUI() {
+ HttpServletRequest request = RWT.getRequest();
+ String authorization = request.getHeader(HEADER_AUTHORIZATION);
+ if (authorization == null || !authorization.startsWith("Negotiate")) {
+ HttpServletResponse response = RWT.getResponse();
+ response.setStatus(401);
+ response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate");
+ response.setDateHeader("Date", System.currentTimeMillis());
+ response.setDateHeader("Expires", System.currentTimeMillis() + (24 * 60 * 60 * 1000));
+ response.setHeader("Accept-Ranges", "bytes");
+ response.setHeader("Connection", "Keep-Alive");
+ response.setHeader("Keep-Alive", "timeout=5, max=97");
+ // response.setContentType("text/html; charset=UTF-8");
+ }
+
+ int returnCode;
+ returnCode = super.createUI();
+ return returnCode;
+ }
+
+ @Override
+ protected int postLogin() {
+ Subject subject = getLoginContext().getSubject();
+ final Display display = Display.getCurrent();
+ if (subject.getPrincipals(X500Principal.class).isEmpty()) {
+ RWT.getClient().getService(JavaScriptExecutor.class).execute("location.reload()");
+ }
+ //
+ // RUN THE WORKBENCH
+ //
+ Integer returnCode = null;
+ try {
+ returnCode = Subject.doAs(subject, new PrivilegedAction<Integer>() {
+ public Integer run() {
+ int result = createAndRunWorkbench(display, CurrentUser.getUsername(subject));
+ return new Integer(result);
+ }
+ });
+ // explicit workbench closing
+ logout();
+ } finally {
+ display.dispose();
+ }
+ return returnCode;
+ }
+
+ protected int createAndRunWorkbench(Display display, String username) {
+ RapWorkbenchAdvisor workbenchAdvisor = createRapWorkbenchAdvisor(username);
+ return PlatformUI.createAndRunWorkbench(display, workbenchAdvisor);
+ }
+
+ @Override
+ protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale,
+ SelectionListener loginSelectionListener) {
+ Button loginButton = new Button(credentialsBlock, SWT.PUSH);
+ loginButton.setText(CmsMsg.login.lead(selectedLocale));
+ loginButton.setLayoutData(CmsUtils.fillWidth());
+ loginButton.addSelectionListener(loginSelectionListener);
+ }
+
+ @Override
+ protected Display createDisplay() {
+ return PlatformUI.createDisplay();
+ }
+
+}
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
import org.argeo.cms.CmsException;
import org.argeo.cms.auth.CurrentUser;
import org.eclipse.swt.widgets.Display;
public class LoginEntryPoint implements EntryPoint, CmsView {
+ protected final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate";
+ protected final static String HEADER_AUTHORIZATION = "Authorization";
// private final static Log log = LogFactory.getLog(WorkbenchLogin.class);
// private final Subject subject = new Subject();
private LoginContext loginContext;
} catch (LoginException e) {
loginShell.createUi();
loginShell.open();
+
+// HttpServletRequest request = RWT.getRequest();
+// String authorization = request.getHeader(HEADER_AUTHORIZATION);
+// if (authorization == null || !authorization.startsWith("Negotiate")) {
+// HttpServletResponse response = RWT.getResponse();
+// response.setStatus(401);
+// response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate");
+// response.setDateHeader("Date", System.currentTimeMillis());
+// response.setDateHeader("Expires", System.currentTimeMillis() + (24 * 60 * 60 * 1000));
+// response.setHeader("Accept-Ranges", "bytes");
+// response.setHeader("Connection", "Keep-Alive");
+// response.setHeader("Keep-Alive", "timeout=5, max=97");
+// // response.setContentType("text/html; charset=UTF-8");
+// }
+
while (!loginShell.getShell().isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
--- /dev/null
+package org.argeo.cms.security;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.Authenticator;
+import java.net.PasswordAuthentication;
+import java.net.URL;
+
+public class RunHttpSpnego {
+
+ static final String kuser = "mbaudier@ARGEO.EU"; // your account name
+ static final String kpass = "test"; // retrieve password for your account
+
+ static class MyAuthenticator extends Authenticator {
+ public PasswordAuthentication getPasswordAuthentication() {
+ // I haven't checked getRequestingScheme() here, since for NTLM
+ // and Negotiate, the usrname and password are all the same.
+ System.err.println("Feeding username and password for " + getRequestingScheme());
+ return (new PasswordAuthentication(kuser, kpass.toCharArray()));
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ Authenticator.setDefault(new MyAuthenticator());
+ URL url = new URL(args[0]);
+ InputStream ins = url.openConnection().getInputStream();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(ins));
+ String str;
+ while((str = reader.readLine()) != null)
+ System.out.println(str);
+ }
+}
/** From com.sun.security.auth.module.*LoginModule */
static final String SHARED_STATE_PWD = "javax.security.auth.login.password";
+ static final String SHARED_STATE_SPNEGO_TOKEN = "org.argeo.cms.auth.spnegoToken";
+ static final String SHARED_STATE_SPNEGO_OUT_TOKEN = "org.argeo.cms.auth.spnegoOutToken";
+
+ static final String HEADER_AUTHORIZATION = "Authorization";
+
static void addAuthentication(Subject subject, Authorization authorization) {
assert subject != null;
checkSubjectEmpty(subject);
public void setRequest(HttpServletRequest request) {
this.request = request;
}
- // private X509Certificate extractCertificate(HttpServletRequest req) {
- // X509Certificate[] certs = (X509Certificate[]) req
- // .getAttribute("javax.servlet.request.X509Certificate");
- // if (null != certs && certs.length > 0) {
- // return certs[0];
- // }
- // return null;
- // }
-
}
package org.argeo.cms.auth;
import java.io.IOException;
+import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Map;
+import java.util.StringTokenizer;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.spi.LoginModule;
import javax.servlet.http.HttpServletRequest;
+import org.apache.commons.codec.binary.Base64;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.cms.CmsException;
-import org.argeo.cms.internal.kernel.WebCmsSessionImpl;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
}
sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
+ extractHttpAuth(request);
+ extractClientCertificate(request);
if (authorization == null)
return false;
sharedState.put(CmsAuthUtils.SHARED_STATE_AUTHORIZATION, authorization);
return CmsAuthUtils.logoutSession(bc, subject);
}
+ private void extractHttpAuth(final HttpServletRequest httpRequest) {
+ String authHeader = httpRequest.getHeader(CmsAuthUtils.HEADER_AUTHORIZATION);
+ if (authHeader != null) {
+ StringTokenizer st = new StringTokenizer(authHeader);
+ if (st.hasMoreTokens()) {
+ String basic = st.nextToken();
+ if (basic.equalsIgnoreCase("Basic")) {
+ try {
+ // TODO manipulate char[]
+ String credentials = new String(Base64.decodeBase64(st.nextToken()), "UTF-8");
+ // log.debug("Credentials: " + credentials);
+ int p = credentials.indexOf(":");
+ if (p != -1) {
+ final String login = credentials.substring(0, p).trim();
+ final char[] password = credentials.substring(p + 1).trim().toCharArray();
+ sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, login);
+ sharedState.put(CmsAuthUtils.SHARED_STATE_PWD, password);
+ } else {
+ throw new CmsException("Invalid authentication token");
+ }
+ } catch (Exception e) {
+ throw new CmsException("Couldn't retrieve authentication", e);
+ }
+ } else if (basic.equalsIgnoreCase("Negotiate")) {
+ String spnegoToken = st.nextToken();
+ byte[] authToken = Base64.decodeBase64(spnegoToken);
+ sharedState.put(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN, authToken);
+ }
+ }
+ }
+ }
+
+ private X509Certificate[] extractClientCertificate(HttpServletRequest req) {
+ X509Certificate[] certs = (X509Certificate[]) req.getAttribute("javax.servlet.request.X509Certificate");
+ if (null != certs && certs.length > 0) {
+ return certs;
+ }
+ return null;
+ }
+
}
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.CallbackHandler;
import javax.servlet.http.HttpServletRequest;
import org.argeo.cms.CmsException;
-import org.argeo.naming.LdapAttrs;
+import org.argeo.osgi.useradmin.IpaUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.useradmin.Authorization;
authorization = userAdmin.getAuthorization(null);
} else {
KerberosPrincipal kerberosPrincipal = kerberosPrincipals.iterator().next();
- LdapName dn = kerberosToIpa(kerberosPrincipal);
+ LdapName dn = IpaUtils.kerberosToDn(kerberosPrincipal.getName());
AuthenticatingUser authenticatingUser = new AuthenticatingUser(dn);
authorization = Subject.doAs(subject, new PrivilegedAction<Authorization>() {
return true;
}
- private LdapName kerberosToIpa(KerberosPrincipal kerberosPrincipal) {
- String[] kname = kerberosPrincipal.getName().split("@");
- String username = kname[0];
- String[] dcs = kname[1].split("\\.");
- StringBuilder sb = new StringBuilder();
- for (String dc : dcs) {
- sb.append(',').append(LdapAttrs.dc.name()).append('=').append(dc.toLowerCase());
- }
- String dn = LdapAttrs.uid + "=" + username + ",cn=users,cn=accounts" + sb;
- try {
- return new LdapName(dn);
- } catch (InvalidNameException e) {
- throw new CmsException("Badly formatted name for " + kerberosPrincipal + ": " + dn);
- }
- }
@Override
public boolean abort() throws LoginException {
--- /dev/null
+package org.argeo.cms.auth;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+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.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.internal.kernel.Activator;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+
+public class SpnegoLoginModule implements LoginModule {
+ private final static Log log = LogFactory.getLog(SpnegoLoginModule.class);
+
+ private Subject subject;
+ private Map<String, Object> sharedState = null;
+
+ private GSSContext gssContext = null;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
+ Map<String, ?> options) {
+ this.subject = subject;
+ this.sharedState = (Map<String, Object>) sharedState;
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ byte[] spnegoToken = (byte[]) sharedState.get(CmsAuthUtils.SHARED_STATE_SPNEGO_TOKEN);
+ if (spnegoToken == null)
+ return false;
+ gssContext = checkToken(spnegoToken);
+ if (gssContext == null)
+ return false;
+ try {
+ String clientName = gssContext.getSrcName().toString();
+ String role = clientName.substring(clientName.indexOf('@') + 1);
+
+ log.debug("SpnegoUserRealm: established a security context");
+ log.debug("Client Principal is: " + gssContext.getSrcName());
+ log.debug("Server Principal is: " + gssContext.getTargName());
+ log.debug("Client Default Role: " + role);
+ } catch (GSSException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ // TODO log in
+
+ return false;
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ if (gssContext == null)
+ return false;
+
+ try {
+ Class<?> gssUtilsClass = Class.forName("com.sun.security.jgss.GSSUtil");
+ Method createSubjectMethod = gssUtilsClass.getMethod("createSubject", GSSName.class, GSSCredential.class);
+ Subject gssSubject = (Subject) createSubjectMethod.invoke(null, gssContext.getSrcName(),
+ gssContext.getDelegCred());
+ subject.getPrincipals().addAll(gssSubject.getPrincipals());
+ subject.getPrivateCredentials().addAll(gssSubject.getPrivateCredentials());
+ return true;
+ } catch (Exception e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ return false;
+ }
+
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ private GSSContext checkToken(byte[] authToken) {
+ GSSManager manager = GSSManager.getInstance();
+ try {
+ GSSContext gContext = manager.createContext(Activator.getAcceptorCredentials());
+
+ if (gContext == null) {
+ log.debug("SpnegoUserRealm: failed to establish GSSContext");
+ } else {
+ if (gContext.isEstablished())
+ return gContext;
+ byte[] outToken = gContext.acceptSecContext(authToken, 0, authToken.length);
+ if (outToken != null)
+ sharedState.put(CmsAuthUtils.SHARED_STATE_SPNEGO_OUT_TOKEN, outToken);
+ if (gContext.isEstablished())
+ return gContext;
+ }
+
+ } catch (GSSException gsse) {
+ log.warn(gsse, gsse);
+ }
+ return null;
+
+ }
+}
package org.argeo.cms.internal.kernel;
import java.io.IOException;
-import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Dictionary;
import java.util.List;
import java.util.Locale;
-import javax.security.auth.login.Configuration;
-
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.cms.CmsException;
import org.argeo.node.NodeInstance;
import org.argeo.node.NodeState;
import org.argeo.util.LangUtils;
+import org.ietf.jgss.GSSCredential;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
private static Activator instance;
private BundleContext bc;
+ private CmsSecurity nodeSecurity;
private LogReaderService logReaderService;
// private ConfigurationAdmin configurationAdmin;
// this.configurationAdmin = getService(ConfigurationAdmin.class);
try {
- initSecurity();// must be first
+ nodeSecurity = new CmsSecurity();
initArgeoLogger();
initNode();
} catch (Exception e) {
}
}
- private void initSecurity() {
- if (System.getProperty(KernelConstants.JAAS_CONFIG_PROP) == null) {
- URL url = getClass().getClassLoader().getResource(KernelConstants.JAAS_CONFIG);
- // URL url =
- // getClass().getClassLoader().getResource(KernelConstants.JAAS_CONFIG_IPA);
- System.setProperty(KernelConstants.JAAS_CONFIG_PROP, url.toExternalForm());
- }
- Configuration.getConfiguration();
- }
-
private void initArgeoLogger() {
logger = new NodeLogger(logReaderService);
bc.registerService(ArgeoLogger.class, logger, null);
return instance.nodeState;
}
+ public static GSSCredential getAcceptorCredentials() {
+ return getCmsSecurity().getServerCredentials();
+ }
+
+ static CmsSecurity getCmsSecurity() {
+ return instance.nodeSecurity;
+ }
+
public String[] getLocales() {
// TODO optimize?
List<Locale> locales = getNodeState().getLocales();
--- /dev/null
+package org.argeo.cms.internal.kernel;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.PrivilegedExceptionAction;
+import java.util.Iterator;
+
+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.UnsupportedCallbackException;
+import javax.security.auth.kerberos.KerberosPrincipal;
+import javax.security.auth.login.Configuration;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsException;
+import org.argeo.naming.DnsBrowser;
+import org.argeo.node.NodeConstants;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
+
+/** Low-level kernel security */
+class CmsSecurity implements KernelConstants {
+ private final static Log log = LogFactory.getLog(CmsSecurity.class);
+ // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
+ private final static Oid KERBEROS_OID;
+ static {
+ try {
+ KERBEROS_OID = new Oid("1.3.6.1.5.5.2");
+ } catch (GSSException e) {
+ throw new IllegalStateException("Cannot create Kerberos OID", e);
+ }
+ }
+
+ public final static int DEPLOYED = 30;
+ public final static int STANDALONE = 20;
+ public final static int DEV = 10;
+ public final static int UNKNOWN = 0;
+
+ private String hostname;
+
+ private final int securityLevel;
+ private Subject nodeSubject;
+
+ // IPA
+ private String kerberosDomain;
+ private String service = null;
+ private GSSCredential acceptorCredentials;
+
+ private Path nodeKeyTab = KernelUtils.getOsgiInstancePath("node/krb5.keytab");
+ private File keyStoreFile;
+
+ public CmsSecurity() {
+ if (!DeployConfig.isInitialized()) // first init
+ FirstInit.prepareInstanceArea();
+
+ securityLevel = evaluateSecurityLevel();
+ // Configure JAAS first
+ if (System.getProperty(JAAS_CONFIG_PROP) == null) {
+ String jaasConfig = securityLevel < DEPLOYED ? JAAS_CONFIG : JAAS_CONFIG_IPA;
+ URL url = getClass().getClassLoader().getResource(jaasConfig);
+ System.setProperty(JAAS_CONFIG_PROP, url.toExternalForm());
+ }
+ // explicitly load JAAS configuration
+ Configuration.getConfiguration();
+ nodeSubject = logInKernel();
+
+ // firstInit = !new File(getOsgiInstanceDir(), DIR_NODE).exists();
+
+ // this.keyStoreFile = new File(KernelUtils.getOsgiInstanceDir(),
+ // "node.p12");
+ // createKeyStoreIfNeeded();
+ }
+
+ private int evaluateSecurityLevel() {
+ int res = UNKNOWN;
+ try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+ InetAddress localhost = InetAddress.getLocalHost();
+ hostname = localhost.getHostName();
+ String dnsZone = hostname.substring(hostname.indexOf('.') + 1);
+ String ipfromDns = dnsBrowser.getRecord(hostname, localhost instanceof Inet6Address ? "AAAA" : "A");
+ boolean consistentIp = localhost.getHostAddress().equals(ipfromDns);
+ kerberosDomain = dnsBrowser.getRecord("_kerberos." + dnsZone, "TXT");
+ if (consistentIp && kerberosDomain != null && Files.exists(nodeKeyTab)) {
+ res = DEPLOYED;
+ } else {
+ res = STANDALONE;
+ kerberosDomain = null;
+ // FIXME make state more robust
+ }
+ } catch (UnknownHostException e) {
+ hostname = "localhost";
+ log.warn("Cannot determine hostname, using " + hostname + ":" + e.getMessage());
+ res = STANDALONE;
+ } catch (Exception e) {
+ log.warn("Exception when evaluating security level, setting it to DEV", e);
+ res = DEV;
+ }
+
+ if (res == UNKNOWN)
+ throw new CmsException("Undefined security level");
+ return res;
+ }
+
+ private Subject logInKernel() {
+ final Subject nodeSubject = new Subject();
+
+ CallbackHandler callbackHandler;
+ if (Files.exists(nodeKeyTab)) {
+ service = NodeConstants.NODE_SERVICE;
+ callbackHandler = new CallbackHandler() {
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks)
+ if (callback instanceof NameCallback)
+ ((NameCallback) callback).setName(getKerberosServicePrincipal());
+
+ }
+ };
+ try {
+ LoginContext kernelLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_NODE, nodeSubject,
+ callbackHandler);
+ kernelLc.login();
+ } catch (LoginException e) {
+ throw new CmsException("Cannot log in kernel", e);
+ }
+ } else {
+ callbackHandler = null;
+ // try {
+ // callbackHandler = (CallbackHandler)
+ // Class.forName("com.sun.security.auth.callback.TextCallbackHandler")
+ // .newInstance();
+ // } catch (ReflectiveOperationException e) {
+ // throw new CmsException("Cannot create text callback handler", e);
+ // }
+ try {
+ LoginContext kernelLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_SINGLE_USER, nodeSubject);
+ kernelLc.login();
+ } catch (LoginException e) {
+ throw new CmsException("Cannot log in kernel", e);
+ }
+ }
+
+ if (securityLevel >= DEPLOYED) {
+ acceptorCredentials = logInAsAcceptor(nodeSubject);
+ }
+ return nodeSubject;
+ }
+
+ private String getKerberosServicePrincipal() {
+ if (hostname == null || "locahost".equals(hostname) || kerberosDomain == null || service == null)
+ throw new IllegalStateException("Cannot determine kerberos principal");
+ return service + "/" + hostname + "@" + kerberosDomain;
+ }
+
+ private GSSCredential logInAsAcceptor(Subject nodeSubject) {
+ // GSS
+ Iterator<KerberosPrincipal> krb5It = nodeSubject.getPrincipals(KerberosPrincipal.class).iterator();
+ if (!krb5It.hasNext())
+ return null;
+ KerberosPrincipal krb5Principal = null;
+ while (krb5It.hasNext()) {
+ KerberosPrincipal principal = krb5It.next();
+ if (service == null && krb5Principal == null)// first as default
+ krb5Principal = principal;
+ if (service != null && principal.getName().equals(getKerberosServicePrincipal()))
+ krb5Principal = principal;
+ }
+
+ if (krb5Principal == null)
+ return null;
+
+ GSSManager manager = GSSManager.getInstance();
+ try {
+ GSSName gssName = manager.createName(krb5Principal.getName(), null);
+ GSSCredential serverCredentials = Subject.doAs(nodeSubject, new PrivilegedExceptionAction<GSSCredential>() {
+
+ @Override
+ public GSSCredential run() throws GSSException {
+ return manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME, KERBEROS_OID,
+ GSSCredential.ACCEPT_ONLY);
+
+ }
+
+ });
+ if (log.isDebugEnabled())
+ log.debug("GSS acceptor configured for " + krb5Principal);
+ return serverCredentials;
+ } catch (Exception gsse) {
+ throw new CmsException("Cannot create acceptor credentials for " + krb5Principal, gsse);
+ }
+ }
+ //
+ // private Subject logInHardenedKernel() {
+ // final Subject kernelSubject = new Subject();
+ // createKeyStoreIfNeeded();
+ //
+ // CallbackHandler cbHandler = new CallbackHandler() {
+ //
+ // @Override
+ // public void handle(Callback[] callbacks) throws IOException,
+ // UnsupportedCallbackException {
+ // // alias
+ //// ((NameCallback) callbacks[1]).setName(AuthConstants.ROLE_KERNEL);
+ // // store pwd
+ // ((PasswordCallback) callbacks[2]).setPassword("changeit".toCharArray());
+ // // key pwd
+ // ((PasswordCallback) callbacks[3]).setPassword("changeit".toCharArray());
+ // }
+ // };
+ // try {
+ // LoginContext kernelLc = new
+ // LoginContext(KernelConstants.LOGIN_CONTEXT_HARDENED_KERNEL,
+ // kernelSubject,
+ // cbHandler);
+ // kernelLc.login();
+ // } catch (LoginException e) {
+ // throw new CmsException("Cannot log in kernel", e);
+ // }
+ // return kernelSubject;
+ // }
+
+ void destroy() {
+ // Logout kernel
+ try {
+ LoginContext kernelLc = new LoginContext(NodeConstants.LOGIN_CONTEXT_NODE, nodeSubject);
+ kernelLc.logout();
+ } catch (LoginException e) {
+ throw new CmsException("Cannot log out kernel", e);
+ }
+
+ // Security.removeProvider(SECURITY_PROVIDER);
+ }
+
+ File getNodeKeyStore() {
+ return keyStoreFile;
+ }
+
+ public synchronized int getSecurityLevel() {
+ return securityLevel;
+ }
+
+ public String getKerberosDomain() {
+ return kerberosDomain;
+ }
+
+ public Subject getNodeSubject() {
+ return nodeSubject;
+ }
+
+ public GSSCredential getServerCredentials() {
+ return acceptorCredentials;
+ }
+
+ // public void setSecurityLevel(int newValue) {
+ // if (newValue != STANDALONE || newValue != DEV)
+ // throw new CmsException("Invalid value for security level " + newValue);
+ // if (newValue >= securityLevel)
+ // throw new CmsException(
+ // "Impossible to increase security level (from " + securityLevel + " to " +
+ // newValue + ")");
+ // securityLevel = newValue;
+ // }
+
+ // private void createKeyStoreIfNeeded() {
+ // // for (Provider provider : Security.getProviders())
+ // // System.out.println(provider.getName());
+ //
+ // char[] ksPwd = "changeit".toCharArray();
+ // char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length);
+ // if (!keyStoreFile.exists()) {
+ // try {
+ // keyStoreFile.getParentFile().mkdirs();
+ // KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd);
+ // // PkiUtils.generateSelfSignedCertificate(keyStore, new
+ // // X500Principal(AuthConstants.ROLE_KERNEL), 1024,
+ // // keyPwd);
+ // PkiUtils.saveKeyStore(keyStoreFile, ksPwd, keyStore);
+ // if (log.isDebugEnabled())
+ // log.debug("Created keystore " + keyStoreFile);
+ // } catch (Exception e) {
+ // if (keyStoreFile.length() == 0)
+ // keyStoreFile.delete();
+ // log.error("Cannot create keystore " + keyStoreFile, e);
+ // }
+ // }
+ // }
+
+ // private final static String SECURITY_PROVIDER = "BC";// Bouncy Castle
+ // private final static Log log;
+ // static {
+ // log = LogFactory.getLog(NodeSecurity.class);
+ // // Make Bouncy Castle the default provider
+ // Provider provider = new BouncyCastleProvider();
+ // int position = Security.insertProviderAt(provider, 1);
+ // if (position == -1)
+ // log.error("Provider " + provider.getName()
+ // + " already installed and could not be set as default");
+ // Provider defaultProvider = Security.getProviders()[0];
+ // if (!defaultProvider.getName().equals(SECURITY_PROVIDER))
+ // log.error("Provider name is " + defaultProvider.getName()
+ // + " but it should be " + SECURITY_PROVIDER);
+ // }
+}
import org.argeo.cms.auth.HttpRequestCallbackHandler;
import org.argeo.jcr.JcrUtils;
import org.argeo.node.NodeConstants;
+import org.ietf.jgss.GSSContext;
+import org.ietf.jgss.GSSCredential;
+import org.ietf.jgss.GSSException;
+import org.ietf.jgss.GSSManager;
+import org.ietf.jgss.GSSName;
+import org.ietf.jgss.Oid;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceReference;
try {
registerWebdavServlet(alias, repository);
registerRemotingServlet(alias, repository);
+ registerFilesServlet(alias, repository);
if (log.isDebugEnabled())
log.debug("Registered servlets for repository '" + alias + "'");
} catch (Exception e) {
try {
httpService.unregister(webdavPath(alias));
httpService.unregister(remotingPath(alias));
+ httpService.unregister(filesPath(alias));
if (log.isDebugEnabled())
log.debug("Unregistered servlets for repository '" + alias + "'");
} catch (Exception e) {
httpService.registerServlet(path, webdavServlet, ip, new DataHttpContext());
}
+ void registerFilesServlet(String alias, Repository repository) throws NamespaceException, ServletException {
+ WebdavServlet filesServlet = new WebdavServlet(repository, new OpenInViewSessionProvider(alias));
+ String path = filesPath(alias);
+ Properties ip = new Properties();
+ ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_CONFIG, WEBDAV_CONFIG);
+ ip.setProperty(WebdavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, path);
+ httpService.registerServlet(path, filesServlet, ip, new FilesHttpContext());
+ }
+
void registerRemotingServlet(String alias, Repository repository) throws NamespaceException, ServletException {
RemotingServlet remotingServlet = new RemotingServlet(repository, new OpenInViewSessionProvider(alias));
String path = remotingPath(alias);
return NodeConstants.PATH_JCR + "/" + alias;
}
+ private String filesPath(String alias) {
+ return NodeConstants.PATH_FILES + "/" + alias;
+ }
+
private Subject subjectFromRequest(HttpServletRequest request) {
Authorization authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
if (authorization == null)
}
}
- private void requestBasicAuth(HttpServletRequest request, HttpServletResponse response) {
+ private void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) {
response.setStatus(401);
- response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\"" + httpAuthRealm + "\"");
+ response.setHeader(HEADER_WWW_AUTHENTICATE, "basic realm=\"" +
+ httpAuthRealm + "\"");
+
+ // SPNEGO
+// response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate");
+// response.setDateHeader("Date", System.currentTimeMillis());
+// response.setDateHeader("Expires", System.currentTimeMillis() + (24 * 60 * 60 * 1000));
+// response.setHeader("Accept-Ranges", "bytes");
+// response.setHeader("Connection", "Keep-Alive");
+// response.setHeader("Keep-Alive", "timeout=5, max=97");
+// response.setContentType("text/html; charset=UTF-8");
+
}
- private CallbackHandler basicAuth(final HttpServletRequest httpRequest) {
+ private CallbackHandler extractHttpAuth(final HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
String authHeader = httpRequest.getHeader(HEADER_AUTHORIZATION);
if (authHeader != null) {
StringTokenizer st = new StringTokenizer(authHeader);
} catch (Exception e) {
throw new CmsException("Couldn't retrieve authentication", e);
}
+ } else if (basic.equalsIgnoreCase("Negotiate")) {
+ // FIXME generalise
+ String _targetName = "HTTP/mostar.desktop.argeo.pro";
+ String spnegoToken = st.nextToken();
+ byte[] authToken = Base64.decodeBase64(spnegoToken);
+ GSSManager manager = GSSManager.getInstance();
+ try {
+ Oid krb5Oid = new Oid("1.3.6.1.5.5.2"); // http://java.sun.com/javase/6/docs/technotes/guides/security/jgss/jgss-features.html
+ GSSName gssName = manager.createName(_targetName, null);
+ GSSCredential serverCreds = manager.createCredential(gssName, GSSCredential.INDEFINITE_LIFETIME,
+ krb5Oid, GSSCredential.ACCEPT_ONLY);
+ GSSContext gContext = manager.createContext(serverCreds);
+
+ if (gContext == null) {
+ log.debug("SpnegoUserRealm: failed to establish GSSContext");
+ } else {
+ while (!gContext.isEstablished()) {
+ byte[] outToken = gContext.acceptSecContext(authToken, 0, authToken.length);
+ String outTokenStr = Base64.encodeBase64String(outToken);
+ httpResponse.setHeader("WWW-Authenticate","Negotiate "+ outTokenStr);
+ }
+ if (gContext.isEstablished()) {
+ String clientName = gContext.getSrcName().toString();
+ String role = clientName.substring(clientName.indexOf('@') + 1);
+
+ log.debug("SpnegoUserRealm: established a security context");
+ log.debug("Client Principal is: " + gContext.getSrcName());
+ log.debug("Server Principal is: " + gContext.getTargName());
+ log.debug("Client Default Role: " + role);
+
+ // TODO log in
+ }
+ }
+
+ } catch (GSSException gsse) {
+ log.warn(gsse,gsse);
+ }
+
}
}
}
@Override
public boolean handleSecurity(final HttpServletRequest request, HttpServletResponse response)
throws IOException {
+
if (log.isTraceEnabled())
KernelUtils.logRequestHeaders(log, request);
LoginContext lc;
lc.login();
// return true;
} catch (LoginException e) {
- CallbackHandler token = basicAuth(request);
+ CallbackHandler token = extractHttpAuth(request,response);
if (token != null) {
try {
lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token);
}
+ private class FilesHttpContext implements HttpContext {
+ @Override
+ public boolean handleSecurity(final HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+
+ if (log.isTraceEnabled())
+ KernelUtils.logRequestHeaders(log, request);
+ LoginContext lc;
+ try {
+ lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request));
+ lc.login();
+ // return true;
+ } catch (LoginException e) {
+ CallbackHandler token = extractHttpAuth(request,response);
+ if (token != null) {
+ try {
+ lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token);
+ lc.login();
+ // Note: this is impossible to reliably clear the
+ // authorization header when access from a browser.
+ } catch (LoginException e1) {
+ throw new CmsException("Could not login", e1);
+ }
+ } else {
+ askForWwwAuth(request, response);
+ lc = null;
+ return false;
+ }
+ }
+ request.setAttribute(NodeConstants.LOGIN_CONTEXT_USER, lc);
+ return true;
+ }
+
+ @Override
+ public URL getResource(String name) {
+ return KernelUtils.getBundleContext(DataHttp.class).getBundle().getResource(name);
+ }
+
+ @Override
+ public String getMimeType(String name) {
+ return null;
+ }
+
+ }
+
private class RemotingHttpContext implements HttpContext {
// private final boolean anonymous;
lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new HttpRequestCallbackHandler(request));
lc.login();
} catch (CredentialNotFoundException e) {
- CallbackHandler token = basicAuth(request);
+ CallbackHandler token = extractHttpAuth(request,response);
if (token != null) {
try {
lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, token);
throw new CmsException("Could not login", e1);
}
} else {
- requestBasicAuth(request, response);
+ askForWwwAuth(request, response);
lc = null;
}
} catch (LoginException e) {
private final Log log = LogFactory.getLog(getClass());
private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext();
- private Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH);
+ private static Path deployConfigPath = KernelUtils.getOsgiInstancePath(KernelConstants.DEPLOY_CONFIG_PATH);
private SortedMap<LdapName, Attributes> deployConfigs = new TreeMap<>();
- public DeployConfig(ConfigurationAdmin configurationAdmin,boolean isClean) {
+ public DeployConfig(ConfigurationAdmin configurationAdmin, boolean isClean) {
// ConfigurationAdmin configurationAdmin =
// bc.getService(bc.getServiceReference(ConfigurationAdmin.class));
try {
- if (!Files.exists(deployConfigPath)) { // first init
+ if (!isInitialized()) { // first init
firstInit();
}
init(configurationAdmin, isClean);
private void firstInit() throws IOException {
Files.createDirectories(deployConfigPath.getParent());
- FirstInitProperties firstInit = new FirstInitProperties();
- firstInit.prepareInstanceArea();
+ FirstInit firstInit = new FirstInit();
+ // firstInit.prepareInstanceArea();
- if (!Files.exists(deployConfigPath))// could have juste been copied
- Files.createFile(deployConfigPath);
-
- try (InputStream in = Files.newInputStream(deployConfigPath)) {
- deployConfigs = new LdifParser().read(in);
- }
+ if (!Files.exists(deployConfigPath))
+ deployConfigs = new TreeMap<>();
+ else// config file could have juste been copied by preparation
+ try (InputStream in = Files.newInputStream(deployConfigPath)) {
+ deployConfigs = new LdifParser().read(in);
+ }
// node repository
Dictionary<String, Object> nodeConfig = firstInit
return null;
}
+ static boolean isInitialized() {
+ return Files.exists(deployConfigPath);
+ }
+
}
--- /dev/null
+package org.argeo.cms.internal.kernel;
+
+import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
+
+import java.io.File;
+import java.io.FileFilter;
+import java.io.IOException;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsException;
+import org.argeo.node.NodeConstants;
+import org.argeo.osgi.useradmin.UserAdminConf;
+import org.eclipse.equinox.http.jetty.JettyConstants;
+
+/**
+ * Interprets framework properties in order to generate the initial deploy
+ * configuration.
+ */
+class FirstInit {
+ private final static Log log = LogFactory.getLog(FirstInit.class);
+
+ public FirstInit() {
+ log.info("## FIRST INIT ##");
+ }
+
+ /** Override the provided config with the framework properties */
+ Dictionary<String, Object> getNodeRepositoryConfig(Dictionary<String, Object> provided) {
+ Dictionary<String, Object> props = provided != null ? provided : new Hashtable<String, Object>();
+ for (RepoConf repoConf : RepoConf.values()) {
+ Object value = getFrameworkProp(NodeConstants.NODE_REPO_PROP_PREFIX + repoConf.name());
+ if (value != null)
+ props.put(repoConf.name(), value);
+ }
+ props.put(NodeConstants.CN, NodeConstants.NODE);
+ // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, NodeConstants.NODE);
+ return props;
+ }
+
+ /** Override the provided config with the framework properties */
+ Dictionary<String, Object> getHttpServerConfig(Dictionary<String, Object> provided) {
+ String httpPort = getFrameworkProp("org.osgi.service.http.port");
+ String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
+ /// TODO make it more generic
+ String httpHost = getFrameworkProp("org.eclipse.equinox.http.jetty.http.host");
+
+ final Hashtable<String, Object> props = new Hashtable<String, Object>();
+ // try {
+ if (httpPort != null || httpsPort != null) {
+ if (httpPort != null) {
+ props.put(JettyConstants.HTTP_PORT, httpPort);
+ props.put(JettyConstants.HTTP_ENABLED, true);
+ }
+ if (httpsPort != null) {
+ props.put(JettyConstants.HTTPS_PORT, httpsPort);
+ props.put(JettyConstants.HTTPS_ENABLED, true);
+ props.put(JettyConstants.SSL_KEYSTORETYPE, "PKCS12");
+ props.put(JettyConstants.SSL_KEYSTORE, "../../ssl/server.p12");
+ // jettyProps.put(JettyConstants.SSL_KEYSTORE,
+ // nodeSecurity.getHttpServerKeyStore().getCanonicalPath());
+ props.put(JettyConstants.SSL_PASSWORD, "changeit");
+ props.put(JettyConstants.SSL_WANTCLIENTAUTH, true);
+ }
+ if (httpHost != null) {
+ props.put(JettyConstants.HTTP_HOST, httpHost);
+ }
+ props.put(NodeConstants.CN, NodeConstants.DEFAULT);
+ }
+ return props;
+ }
+
+ List<Dictionary<String, Object>> getUserDirectoryConfigs() {
+ List<Dictionary<String, Object>> res = new ArrayList<>();
+ File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile();
+ List<String> uris = new ArrayList<>();
+
+ // node roles
+ String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI);
+ String baseNodeRoleDn = NodeConstants.ROLES_BASEDN;
+ if (nodeRolesUri == null) {
+ File nodeRolesFile = new File(nodeBaseDir, baseNodeRoleDn + ".ldif");
+ if (!nodeRolesFile.exists())
+ try {
+ FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(baseNodeRoleDn + ".ldif"),
+ nodeRolesFile);
+ } catch (IOException e) {
+ throw new CmsException("Cannot copy demo resource", e);
+ }
+ nodeRolesUri = nodeRolesFile.toURI().toString();
+ }
+ uris.add(nodeRolesUri);
+
+ // Business roles
+ String userAdminUris = getFrameworkProp(NodeConstants.USERADMIN_URIS);
+ if (userAdminUris == null) {
+ String kerberosDomain = Activator.getCmsSecurity().getKerberosDomain();
+ if (kerberosDomain != null) {
+ userAdminUris = "ipa:///" + kerberosDomain;
+ } else {
+ String demoBaseDn = "dc=example,dc=com";
+ File businessRolesFile = new File(nodeBaseDir, demoBaseDn + ".ldif");
+ if (!businessRolesFile.exists())
+ try {
+ FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(demoBaseDn + ".ldif"),
+ businessRolesFile);
+ } catch (IOException e) {
+ throw new CmsException("Cannot copy demo resource", e);
+ }
+ userAdminUris = businessRolesFile.toURI().toString();
+ log.warn("## DEV Using dummy base DN " + demoBaseDn);
+ // TODO downgrade security level
+ }
+ }
+ for (String userAdminUri : userAdminUris.split(" "))
+ uris.add(userAdminUri);
+
+ // Interprets URIs
+ for (String uri : uris) {
+ URI u;
+ try {
+ u = new URI(uri);
+ if (u.getPath() == null)
+ throw new CmsException("URI " + uri + " must have a path in order to determine base DN");
+ if (u.getScheme() == null) {
+ if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
+ u = new File(uri).getCanonicalFile().toURI();
+ else if (!uri.contains("/")) {
+ u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
+ // u = new URI(nodeBaseDir.toURI() + uri);
+ } else
+ throw new CmsException("Cannot interpret " + uri + " as an uri");
+ } else if (u.getScheme().equals("file")) {
+ u = new File(u).getCanonicalFile().toURI();
+ }
+ } catch (Exception e) {
+ throw new CmsException("Cannot interpret " + uri + " as an uri", e);
+ }
+ Dictionary<String, Object> properties = UserAdminConf.uriAsProperties(u.toString());
+ res.add(properties);
+ }
+
+ return res;
+ }
+
+ /**
+ * Called before node initialisation, in order populate OSGi instance are
+ * with some files (typically LDIF, etc).
+ */
+ static void prepareInstanceArea() {
+ String nodeInit = getFrameworkProp(NodeConstants.NODE_INIT);
+ if (nodeInit == null)
+ nodeInit = "../../init";
+ if (nodeInit.startsWith("http")) {
+ // remoteFirstInit(nodeInit);
+ return;
+ }
+
+ // TODO use java.nio.file
+ File initDir;
+ if (nodeInit.startsWith("."))
+ initDir = KernelUtils.getExecutionDir(nodeInit);
+ else
+ initDir = new File(nodeInit);
+ // TODO also uncompress archives
+ if (initDir.exists())
+ try {
+ FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() {
+
+ @Override
+ public boolean accept(File pathname) {
+ if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
+ return false;
+ return true;
+ }
+ });
+ log.info("CMS initialized from " + initDir.getCanonicalPath());
+ } catch (IOException e) {
+ throw new CmsException("Cannot initialize from " + initDir, e);
+ }
+ }
+
+}
+++ /dev/null
-package org.argeo.cms.internal.kernel;
-
-import static org.argeo.cms.internal.kernel.KernelUtils.getFrameworkProp;
-
-import java.io.File;
-import java.io.FileFilter;
-import java.io.IOException;
-import java.net.URI;
-import java.util.ArrayList;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.List;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.CmsException;
-import org.argeo.node.NodeConstants;
-import org.argeo.osgi.useradmin.UserAdminConf;
-import org.eclipse.equinox.http.jetty.JettyConstants;
-
-/**
- * Interprets framework properties in order to generate the initial deploy
- * configuration.
- */
-class FirstInitProperties {
- private final static Log log = LogFactory.getLog(FirstInitProperties.class);
-
- public FirstInitProperties() {
- log.info("## FIRST INIT ##");
- }
-
- /** Override the provided config with the framework properties */
- Dictionary<String, Object> getNodeRepositoryConfig(Dictionary<String, Object> provided) {
- Dictionary<String, Object> props = provided != null ? provided : new Hashtable<String, Object>();
- for (RepoConf repoConf : RepoConf.values()) {
- Object value = getFrameworkProp(NodeConstants.NODE_REPO_PROP_PREFIX + repoConf.name());
- if (value != null)
- props.put(repoConf.name(), value);
- }
- props.put(NodeConstants.CN, NodeConstants.NODE);
-// props.put(NodeConstants.JCR_REPOSITORY_ALIAS, NodeConstants.NODE);
- return props;
- }
-
- /** Override the provided config with the framework properties */
- Dictionary<String, Object> getHttpServerConfig(Dictionary<String, Object> provided) {
- String httpPort = getFrameworkProp("org.osgi.service.http.port");
- String httpsPort = getFrameworkProp("org.osgi.service.http.port.secure");
- /// TODO make it more generic
- String httpHost = getFrameworkProp("org.eclipse.equinox.http.jetty.http.host");
-
- final Hashtable<String, Object> props = new Hashtable<String, Object>();
- // try {
- if (httpPort != null || httpsPort != null) {
- if (httpPort != null) {
- props.put(JettyConstants.HTTP_PORT, httpPort);
- props.put(JettyConstants.HTTP_ENABLED, true);
- }
- if (httpsPort != null) {
- props.put(JettyConstants.HTTPS_PORT, httpsPort);
- props.put(JettyConstants.HTTPS_ENABLED, true);
- props.put(JettyConstants.SSL_KEYSTORETYPE, "PKCS12");
- // jettyProps.put(JettyConstants.SSL_KEYSTORE,
- // nodeSecurity.getHttpServerKeyStore().getCanonicalPath());
- props.put(JettyConstants.SSL_PASSWORD, "changeit");
- props.put(JettyConstants.SSL_WANTCLIENTAUTH, true);
- }
- if (httpHost != null) {
- props.put(JettyConstants.HTTP_HOST, httpHost);
- }
- props.put(NodeConstants.CN, NodeConstants.DEFAULT);
- }
- return props;
- }
-
- List<Dictionary<String, Object>> getUserDirectoryConfigs() {
- List<Dictionary<String, Object>> res = new ArrayList<>();
- File nodeBaseDir = KernelUtils.getOsgiInstancePath(KernelConstants.DIR_NODE).toFile();
- List<String> uris = new ArrayList<>();
-
- // node roles
- String nodeRolesUri = getFrameworkProp(NodeConstants.ROLES_URI);
- String baseNodeRoleDn = NodeConstants.ROLES_BASEDN;
- if (nodeRolesUri == null) {
- File nodeRolesFile = new File(nodeBaseDir, baseNodeRoleDn + ".ldif");
- if (!nodeRolesFile.exists())
- try {
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(baseNodeRoleDn + ".ldif"),
- nodeRolesFile);
- } catch (IOException e) {
- throw new CmsException("Cannot copy demo resource", e);
- }
- nodeRolesUri = nodeRolesFile.toURI().toString();
- }
- uris.add(nodeRolesUri);
-
- // Business roles
- String userAdminUris = getFrameworkProp(NodeConstants.USERADMIN_URIS);
- if (userAdminUris == null) {
- String demoBaseDn = "dc=example,dc=com";
- File businessRolesFile = new File(nodeBaseDir, demoBaseDn + ".ldif");
- if (!businessRolesFile.exists())
- try {
- FileUtils.copyInputStreamToFile(getClass().getResourceAsStream(demoBaseDn + ".ldif"),
- businessRolesFile);
- } catch (IOException e) {
- throw new CmsException("Cannot copy demo resource", e);
- }
- userAdminUris = businessRolesFile.toURI().toString();
- }
- for (String userAdminUri : userAdminUris.split(" "))
- uris.add(userAdminUri);
-
- // Interprets URIs
- for (String uri : uris) {
- URI u;
- try {
- u = new URI(uri);
- if (u.getPath() == null)
- throw new CmsException("URI " + uri + " must have a path in order to determine base DN");
- if (u.getScheme() == null) {
- if (uri.startsWith("/") || uri.startsWith("./") || uri.startsWith("../"))
- u = new File(uri).getCanonicalFile().toURI();
- else if (!uri.contains("/")) {
- u = KernelUtils.getOsgiInstanceUri(KernelConstants.DIR_NODE + '/' + uri);
- // u = new URI(nodeBaseDir.toURI() + uri);
- } else
- throw new CmsException("Cannot interpret " + uri + " as an uri");
- } else if (u.getScheme().equals("file")) {
- u = new File(u).getCanonicalFile().toURI();
- }
- } catch (Exception e) {
- throw new CmsException("Cannot interpret " + uri + " as an uri", e);
- }
- Dictionary<String, Object> properties = UserAdminConf.uriAsProperties(u.toString());
- res.add(properties);
- }
-
- return res;
- }
-
- /**
- * Called before node initialisation, in order populate OSGi instance are
- * with some files (typically LDIF, etc).
- */
- void prepareInstanceArea() {
- String nodeInit = getFrameworkProp(NodeConstants.NODE_INIT);
- if (nodeInit == null)
- nodeInit = "../../init";
- if (nodeInit.startsWith("http")) {
- // remoteFirstInit(nodeInit);
- return;
- }
-
- // TODO use java.nio.file
- File initDir;
- if (nodeInit.startsWith("."))
- initDir = KernelUtils.getExecutionDir(nodeInit);
- else
- initDir = new File(nodeInit);
- // TODO also uncompress archives
- if (initDir.exists())
- try {
- FileUtils.copyDirectory(initDir, KernelUtils.getOsgiInstanceDir(), new FileFilter() {
-
- @Override
- public boolean accept(File pathname) {
- if (pathname.getName().equals(".svn") || pathname.getName().equals(".git"))
- return false;
- return true;
- }
- });
- log.info("CMS initialized from " + initDir.getCanonicalPath());
- } catch (IOException e) {
- throw new CmsException("Cannot initialize from " + initDir, e);
- }
- }
-
-}
import java.io.File;
import java.io.IOException;
+import java.io.PrintStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
+import java.util.TreeMap;
import java.util.TreeSet;
import javax.jcr.Repository;
// for (String key : keys)
// log.debug(key + "=" + bc.getProperty(key));
}
+
+ static void printSystemProperties(PrintStream out){
+ TreeMap<String, String> display = new TreeMap<>();
+ for (Object key : System.getProperties().keySet())
+ display.put(key.toString(), System.getProperty(key.toString()));
+ for (String key : display.keySet())
+ out.println(key + "=" + display.get(key));
+ }
static Session openAdminSession(Repository repository) {
return openAdminSession(repository, null);
+++ /dev/null
-package org.argeo.cms.internal.kernel;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Writer;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Dictionary;
-import java.util.Hashtable;
-import java.util.SortedMap;
-import java.util.function.Function;
-
-import javax.naming.InvalidNameException;
-import javax.naming.directory.Attributes;
-import javax.naming.ldap.LdapName;
-
-import org.argeo.cms.CmsException;
-import org.argeo.naming.AttributesDictionary;
-import org.argeo.naming.LdifParser;
-import org.argeo.naming.LdifWriter;
-import org.argeo.node.NodeConstants;
-
-@Deprecated
-class NodeDeployConfig {
- private final String BASE = "ou=deploy,ou=node";
- private final Path path;
- private final Function<String, String> getter;
-
- private final SortedMap<LdapName, Attributes> configurations;
-
- public NodeDeployConfig(Function<String, String> getter) {
- String osgiConfigurationArea = getter.apply(KernelUtils.OSGI_CONFIGURATION_AREA);
- try {
- this.path = Paths.get(new URI(osgiConfigurationArea));
- } catch (URISyntaxException e) {
- throw new IllegalArgumentException("Cannot parse " + getter.apply(KernelUtils.OSGI_CONFIGURATION_AREA), e);
- }
- this.getter = getter;
-
- if (!Files.exists(path))
- try (Writer writer = Files.newBufferedWriter(path)) {
- Files.createFile(path);
- LdifWriter ldifWriter = new LdifWriter(writer);
- } catch (IOException e) {
- throw new CmsException("Cannot create " + path, e);
- }
-
- try (InputStream in = Files.newInputStream(path)) {
- configurations = new LdifParser().read(in);
- } catch (IOException e) {
- throw new CmsException("Cannot read " + path, e);
- }
- }
-
- public Dictionary<String, Object> getConfiguration(String servicePid) {
- LdapName dn;
- try {
- dn = new LdapName("ou=" + servicePid + "," + BASE);
- } catch (InvalidNameException e) {
- throw new IllegalArgumentException("Cannot parse DN", e);
- }
- if (configurations.containsKey(dn))
- return new AttributesDictionary(configurations.get(dn));
- else
- return null;
- }
-
- static Dictionary<String, Object> getStatePropertiesFromEnvironment(Function<String, String> getter) {
- Hashtable<String, Object> props = new Hashtable<>();
- // i18n
- copyFrameworkProp(getter, NodeConstants.I18N_DEFAULT_LOCALE, props);
- copyFrameworkProp(getter, NodeConstants.I18N_LOCALES, props);
- // user admin
- copyFrameworkProp(getter, NodeConstants.ROLES_URI, props);
- copyFrameworkProp(getter, NodeConstants.USERADMIN_URIS, props);
- // data
- for (RepoConf repoConf : RepoConf.values())
- copyFrameworkProp(getter, NodeConstants.NODE_REPO_PROP_PREFIX + repoConf.name(), props);
- // TODO add other environment sources
- return props;
- }
-
- static Dictionary<String, Object> getUserAdminPropertiesFromEnvironment(Function<String, String> getter) {
- Hashtable<String, Object> props = new Hashtable<>();
- copyFrameworkProp(getter, NodeConstants.ROLES_URI, props);
- copyFrameworkProp(getter, NodeConstants.USERADMIN_URIS, props);
- return props;
- }
-
- private static void copyFrameworkProp(Function<String, String> getter, String key,
- Dictionary<String, Object> props) {
- String value = getter.apply(key);
- if (value != null)
- props.put(key, value);
- }
-
-}
+++ /dev/null
-package org.argeo.cms.internal.kernel;
-
-import static org.argeo.cms.internal.kernel.KernelUtils.getOsgiInstanceDir;
-
-import java.io.File;
-import java.net.URL;
-import java.security.KeyStore;
-import java.util.Arrays;
-
-import javax.security.auth.Subject;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.cms.CmsException;
-
-/** Low-level kernel security */
-@Deprecated
-class NodeSecurity implements KernelConstants {
- private final static Log log = LogFactory.getLog(NodeSecurity.class);
-
- public final static int HARDENED = 3;
- public final static int STAGING = 2;
- public final static int DEV = 1;
-
- private final boolean firstInit;
-
- private Subject kernelSubject;
- private int securityLevel = STAGING;
-
- private final File keyStoreFile;
-
- public NodeSecurity() {
- // Configure JAAS first
- URL url = getClass().getClassLoader().getResource(KernelConstants.JAAS_CONFIG);
- System.setProperty("java.security.auth.login.config", url.toExternalForm());
- // log.debug("JASS config: " + url.toExternalForm());
- // disable Jetty autostart
- // System.setProperty("org.eclipse.equinox.http.jetty.autostart",
- // "false");
-
- firstInit = !new File(getOsgiInstanceDir(), DIR_NODE).exists();
-
- this.keyStoreFile = new File(KernelUtils.getOsgiInstanceDir(), "node.p12");
- createKeyStoreIfNeeded();
-// if (keyStoreFile.exists())
-// this.kernelSubject = logInHardenedKernel();
-// else
-// this.kernelSubject = logInKernel();
- }
-
-// private Subject logInKernel() {
-// final Subject kernelSubject = new Subject();
-// try {
-// LoginContext kernelLc = new LoginContext(KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject);
-// kernelLc.login();
-// } catch (LoginException e) {
-// throw new CmsException("Cannot log in kernel", e);
-// }
-// return kernelSubject;
-// }
-//
-// private Subject logInHardenedKernel() {
-// final Subject kernelSubject = new Subject();
-// createKeyStoreIfNeeded();
-//
-// CallbackHandler cbHandler = new CallbackHandler() {
-//
-// @Override
-// public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
-// // alias
-//// ((NameCallback) callbacks[1]).setName(AuthConstants.ROLE_KERNEL);
-// // store pwd
-// ((PasswordCallback) callbacks[2]).setPassword("changeit".toCharArray());
-// // key pwd
-// ((PasswordCallback) callbacks[3]).setPassword("changeit".toCharArray());
-// }
-// };
-// try {
-// LoginContext kernelLc = new LoginContext(KernelConstants.LOGIN_CONTEXT_HARDENED_KERNEL, kernelSubject,
-// cbHandler);
-// kernelLc.login();
-// } catch (LoginException e) {
-// throw new CmsException("Cannot log in kernel", e);
-// }
-// return kernelSubject;
-// }
-
-// void destroy() {
-// // Logout kernel
-// try {
-// LoginContext kernelLc = new LoginContext(KernelConstants.LOGIN_CONTEXT_KERNEL, kernelSubject);
-// kernelLc.logout();
-// } catch (LoginException e) {
-// throw new CmsException("Cannot log out kernel", e);
-// }
-//
-// // Security.removeProvider(SECURITY_PROVIDER);
-// }
-
- public Subject getKernelSubject() {
- return kernelSubject;
- }
-
- public synchronized int getSecurityLevel() {
- return securityLevel;
- }
-
- public boolean isFirstInit() {
- return firstInit;
- }
-
- public void setSecurityLevel(int newValue) {
- if (newValue != STAGING || newValue != DEV)
- throw new CmsException("Invalid value for security level " + newValue);
- if (newValue >= securityLevel)
- throw new CmsException(
- "Impossible to increase security level (from " + securityLevel + " to " + newValue + ")");
- securityLevel = newValue;
- }
-
- private void createKeyStoreIfNeeded() {
- // for (Provider provider : Security.getProviders())
- // System.out.println(provider.getName());
-
- char[] ksPwd = "changeit".toCharArray();
- char[] keyPwd = Arrays.copyOf(ksPwd, ksPwd.length);
- if (!keyStoreFile.exists()) {
- try {
- keyStoreFile.getParentFile().mkdirs();
- KeyStore keyStore = PkiUtils.getKeyStore(keyStoreFile, ksPwd);
-// PkiUtils.generateSelfSignedCertificate(keyStore, new X500Principal(AuthConstants.ROLE_KERNEL), 1024,
-// keyPwd);
- PkiUtils.saveKeyStore(keyStoreFile, ksPwd, keyStore);
- if (log.isDebugEnabled())
- log.debug("Created keystore " + keyStoreFile);
- } catch (Exception e) {
- if (keyStoreFile.length() == 0)
- keyStoreFile.delete();
- log.error("Cannot create keystore " + keyStoreFile, e);
- }
- }
- }
-
- File getHttpServerKeyStore() {
- return keyStoreFile;
- }
-
- // private final static String SECURITY_PROVIDER = "BC";// Bouncy Castle
- // private final static Log log;
- // static {
- // log = LogFactory.getLog(NodeSecurity.class);
- // // Make Bouncy Castle the default provider
- // Provider provider = new BouncyCastleProvider();
- // int position = Security.insertProviderAt(provider, 1);
- // if (position == -1)
- // log.error("Provider " + provider.getName()
- // + " already installed and could not be set as default");
- // Provider defaultProvider = Security.getProviders()[0];
- // if (!defaultProvider.getName().equals(SECURITY_PROVIDER))
- // log.error("Provider name is " + defaultProvider.getName()
- // + " but it should be " + SECURITY_PROVIDER);
- // }
-}
USER {
org.argeo.cms.auth.HttpSessionLoginModule sufficient;
- com.sun.security.auth.module.Krb5LoginModule optional clearPass=true;
+ org.argeo.cms.auth.SpnegoLoginModule optional;
+ com.sun.security.auth.module.Krb5LoginModule optional;
org.argeo.cms.auth.IpaLoginModule requisite;
};
-ANONYMOUS {
- org.argeo.cms.auth.UserAdminLoginModule requisite anonymous=true;
+DATA_ADMIN {
+ org.argeo.cms.auth.DataAdminLoginModule requisite;
};
-DATA_ADMIN {
+NODE {
+ com.sun.security.auth.module.Krb5LoginModule optional
+ keyTab="${osgi.instance.area}node/krb5.keytab"
+ useKeyTab=true
+ storeKey=true;
org.argeo.cms.auth.DataAdminLoginModule requisite;
};
+SINGLE_USER {
+ com.sun.security.auth.module.Krb5LoginModule optional
+ storeKey=true
+ debug=true;
+ org.argeo.cms.auth.SingleUserLoginModule requisite;
+};
+
KEYRING {
org.argeo.cms.auth.KeyringLoginModule required;
};
Jackrabbit {
org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
};
+
org.argeo.cms.auth.UserAdminLoginModule requisite;
};
-ANONYMOUS {
- org.argeo.cms.auth.UserAdminLoginModule requisite anonymous=true;
-};
-
DATA_ADMIN {
org.argeo.cms.auth.DataAdminLoginModule requisite;
};
-SYSTEM {
+NODE {
org.argeo.cms.auth.DataAdminLoginModule requisite;
};
-HARDENED_KERNEL {
- com.sun.security.auth.module.UnixLoginModule requisite;
- com.sun.security.auth.module.KeyStoreLoginModule requisite keyStoreURL="${osgi.instance.area}/node.p12" keyStoreType=PKCS12;
-};
-
KEYRING {
org.argeo.cms.auth.KeyringLoginModule required;
};
--- /dev/null
+package org.argeo.naming;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.Collections;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.naming.Binding;
+import javax.naming.NamingEnumeration;
+import javax.naming.NamingException;
+import javax.naming.directory.Attribute;
+import javax.naming.directory.Attributes;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.InitialDirContext;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class DnsBrowser implements Closeable {
+ private final static Log log = LogFactory.getLog(DnsBrowser.class);
+
+ private final DirContext initialCtx;
+
+ public DnsBrowser() throws NamingException {
+ this(null);
+ }
+
+ public DnsBrowser(String dnsServerUrls) throws NamingException {
+ Hashtable<String, Object> env = new Hashtable<>();
+ env.put("java.naming.factory.initial", "com.sun.jndi.dns.DnsContextFactory");
+ if (dnsServerUrls != null)
+ env.put("java.naming.provider.url", dnsServerUrls);
+ initialCtx = new InitialDirContext(env);
+ }
+
+ public Map<String, List<String>> getAllRecords(String name) throws NamingException {
+ Map<String, List<String>> res = new TreeMap<>();
+ Attributes attrs = initialCtx.getAttributes(name);
+ NamingEnumeration<String> ids = attrs.getIDs();
+ while (ids.hasMore()) {
+ String recordType = ids.next();
+ List<String> lst = new ArrayList<String>();
+ res.put(recordType, lst);
+ Attribute attr = attrs.get(recordType);
+ addValues(attr, lst);
+ }
+ return Collections.unmodifiableMap(res);
+ }
+
+ /**
+ * Return a single record (typically A, AAAA, etc. or null if not available.
+ * Will fail if multiple records.
+ */
+ public String getRecord(String name, String recordType) throws NamingException {
+ Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
+ if (attrs.size() == 0)
+ return null;
+ Attribute attr = attrs.get(recordType);
+ if (attr.size() > 1)
+ throw new IllegalArgumentException("Multiple record type " + recordType);
+ assert attr.size() != 0;
+ Object value = attr.get();
+ assert value != null;
+ return value.toString();
+ }
+
+ /**
+ * Return records of a given type.
+ */
+ public List<String> getRecords(String name, String recordType) throws NamingException {
+ List<String> res = new ArrayList<String>();
+ Attributes attrs = initialCtx.getAttributes(name, new String[] { recordType });
+ Attribute attr = attrs.get(recordType);
+ addValues(attr, res);
+ return res;
+ }
+
+ /** Ordered, with preferred first. */
+ public List<String> getSrvRecordsAsHosts(String name) throws NamingException {
+ List<String> raw = getRecords(name, "SRV");
+ if (raw.size() == 0)
+ return null;
+ SortedSet<SrvRecord> res = new TreeSet<>();
+ for (int i = 0; i < raw.size(); i++) {
+ String record = raw.get(i);
+ String[] arr = record.split(" ");
+ Integer priority = Integer.parseInt(arr[0]);
+ Integer weight = Integer.parseInt(arr[1]);
+ Integer port = Integer.parseInt(arr[2]);
+ String hostname = arr[3];
+ SrvRecord order = new SrvRecord(priority, weight, port, hostname);
+ res.add(order);
+ }
+ List<String> lst = new ArrayList<>();
+ for (SrvRecord order : res) {
+ lst.add(order.toHost());
+ }
+ return Collections.unmodifiableList(lst);
+ }
+
+ private void addValues(Attribute attr, List<String> lst) throws NamingException {
+ NamingEnumeration<?> values = attr.getAll();
+ while (values.hasMore()) {
+ Object value = values.next();
+ if (value != null) {
+ if (value instanceof byte[]) {
+ String str = Base64.getEncoder().encodeToString((byte[]) value);
+ lst.add(str);
+ } else
+ lst.add(value.toString());
+ }
+ }
+
+ }
+
+ public List<String> listEntries(String name) throws NamingException {
+ List<String> res = new ArrayList<String>();
+ NamingEnumeration<Binding> ne = initialCtx.listBindings(name);
+ while (ne.hasMore()) {
+ Binding b = ne.next();
+ res.add(b.getName());
+ }
+ return Collections.unmodifiableList(res);
+ }
+
+ @Override
+ public void close() throws IOException {
+ destroy();
+ }
+
+ public void destroy() {
+ try {
+ initialCtx.close();
+ } catch (NamingException e) {
+ log.error("Cannot close context", e);
+ }
+ }
+
+ public static void main(String[] args) {
+ if (args.length == 0) {
+ printUsage(System.err);
+ System.exit(1);
+ }
+ try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+ String hostname = args[0];
+ String recordType = args.length > 1 ? args[1] : "A";
+ if (recordType.equals("*")) {
+ Map<String, List<String>> records = dnsBrowser.getAllRecords(hostname);
+ for (String type : records.keySet()) {
+ for (String record : records.get(type)) {
+ String typeLabel;
+ if ("44".equals(type))
+ typeLabel = "SSHFP";
+ else if ("46".equals(type))
+ typeLabel = "RRSIG";
+ else if ("48".equals(type))
+ typeLabel = "DNSKEY";
+ else
+ typeLabel = type;
+ System.out.println(typeLabel + "\t" + record);
+ }
+ }
+ } else {
+ System.out.println(dnsBrowser.getRecord(hostname, recordType));
+ }
+
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void printUsage(PrintStream out) {
+ out.println("java org.argeo.naming.DnsBrowser <hostname> [<record type> | *]");
+ }
+
+}
\ No newline at end of file
package org.argeo.naming;
/**
- * Standard LDAP attributes as per
- * <a href="https://www.ldap.com/ldap-oid-reference">https://www.ldap.com/ldap-
- * oid-reference</a>
+ * Standard LDAP attributes as per:<br>
+ * - <a href= "https://www.ldap.com/ldap-oid-reference">Standard LDAP</a><br>
+ * - <a href=
+ * "https://github.com/krb5/krb5/blob/master/src/plugins/kdb/ldap/libkdb_ldap/kerberos.schema">Kerberos
+ * LDAP (partial)</a>
*/
-public enum LdapAttrs implements SpecifiedName{
+public enum LdapAttrs implements SpecifiedName {
/** */
uid("0.9.2342.19200300.100.1.1", "RFC 4519"),
/** */
/** */
userPKCS12("2.16.840.1.113730.3.1.216", "RFC 2798"),
/** */
- displayName("2.16.840.1.113730.3.1.241", "RFC 2798");
+ displayName("2.16.840.1.113730.3.1.241", "RFC 2798"),
+
+ // KERBEROS (partial
+ krbPrincipalName("2.16.840.1.113719.1.301.6.8.1", "Novell Kerberos Schema Definitions");
public final static String DN = "dn";
--- /dev/null
+package org.argeo.naming;
+
+class SrvRecord implements Comparable<SrvRecord> {
+ private final Integer priority;
+ private final Integer weight;
+ private final Integer port;
+ private final String hostname;
+
+ public SrvRecord(Integer priority, Integer weight, Integer port, String hostname) {
+ this.priority = priority;
+ this.weight = weight;
+ this.port = port;
+ this.hostname = hostname;
+ }
+
+ @Override
+ public int compareTo(SrvRecord other) {
+ // https: // en.wikipedia.org/wiki/SRV_record
+ if (priority != other.priority)
+ return priority - other.priority;
+ if (weight != other.weight)
+ return other.weight - other.weight;
+ String host = toHost();
+ String otherHost = other.toHost();
+ if (host.length() == otherHost.length())
+ return toHost().compareTo(other.toHost());
+ else
+ return host.length() - otherHost.length();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof SrvRecord) {
+ SrvRecord other = (SrvRecord) obj;
+ return priority == other.priority && weight == other.weight && port == other.port
+ && hostname.equals(other.hostname);
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return priority + " " + weight;
+ }
+
+ public String toHost() {
+ String hostStr = hostname;
+ if (hostname.charAt(hostname.length() - 1) == '.')
+ hostStr = hostname.substring(0, hostname.length() - 1);
+ return hostStr + ":" + port;
+ }
+}
--- /dev/null
+package org.argeo.osgi.useradmin;
+
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.argeo.naming.LdapAttrs;
+
+/** Free IPA specific conventions. */
+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_SERVICE_BASE = "cn=services,cn=accounts";
+
+ private final static String KRB_PRINCIPAL_NAME = LdapAttrs.krbPrincipalName.name().toLowerCase();
+
+ public final static String IPA_USER_DIRECTORY_CONFIG = UserAdminConf.userBase + "=" + IPA_USER_BASE + "&"
+ + UserAdminConf.groupBase + "=" + IPA_GROUP_BASE + "&" + UserAdminConf.readOnly + "=true";
+
+ static String domainToUserDirectoryConfigPath(String domain) {
+ return domainToBaseDn(domain) + "?" + IPA_USER_DIRECTORY_CONFIG;
+ }
+
+ public static String domainToBaseDn(String domain) {
+ String[] dcs = domain.split("\\.");
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < dcs.length; i++) {
+ if (i != 0)
+ sb.append(',');
+ String dc = dcs[i];
+ sb.append(LdapAttrs.dc.name()).append('=').append(dc.toLowerCase());
+ }
+ return sb.toString();
+ }
+
+ public static LdapName kerberosToDn(String kerberosName) {
+ String[] kname = kerberosName.split("@");
+ String username = kname[0];
+ String baseDn = domainToBaseDn(kname[1]);
+ String dn;
+ if (!username.contains("/"))
+ dn = LdapAttrs.uid + "=" + username + "," + IPA_USER_BASE + "," + baseDn;
+ else
+ dn = KRB_PRINCIPAL_NAME + "=" + kerberosName + "," + IPA_SERVICE_BASE + "," + baseDn;
+ try {
+ return new LdapName(dn);
+ } catch (InvalidNameException e) {
+ throw new IllegalArgumentException("Badly formatted name for " + kerberosName + ": " + dn);
+ }
+ }
+
+ private IpaUtils() {
+
+ }
+}
package org.argeo.osgi.useradmin;
+import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import javax.naming.Context;
+import javax.naming.NamingException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.naming.DnsBrowser;
import org.osgi.framework.Constants;
/** Properties used to configure user admins. */
readOnly(null);
public final static String FACTORY_PID = "org.argeo.osgi.useradmin.config";
+ private final static Log log = LogFactory.getLog(UserAdminConf.class);
/** The default value. */
private Object def;
Hashtable<String, Object> res = new Hashtable<String, Object>();
URI u = new URI(uriStr);
String scheme = u.getScheme();
+ if (scheme.equals("ipa")) {
+ u = convertIpaConfig(u);
+ scheme = u.getScheme();
+ }
String path = u.getPath();
String bDn = path.substring(path.lastIndexOf('/') + 1, path.length());
if (bDn.endsWith(".ldif"))
credentials = userInfo.length > 1 ? userInfo[1] : null;
}
} else if (scheme.equals("file")) {
+ } else if (scheme.equals("ipa")) {
} else
throw new UserDirectoryException("Unsupported scheme " + scheme);
Map<String, List<String>> query = splitQuery(u.getQuery());
}
}
+ private static URI convertIpaConfig(URI uri) {
+ assert uri.getPath() != null;
+ assert uri.getPath().length() > 1;
+ String kerberosDomain = uri.getPath().substring(1);
+ try (DnsBrowser dnsBrowser = new DnsBrowser()) {
+ String ldapHostsStr = uri.getHost();
+ if (ldapHostsStr == null || ldapHostsStr.trim().equals("")) {
+ List<String> ldapHosts = dnsBrowser.getSrvRecordsAsHosts("_ldap._tcp." + kerberosDomain.toLowerCase());
+ if (ldapHosts == null || ldapHosts.size() == 0) {
+ throw new UserDirectoryException("Cannot configure LDAP for IPA " + uri);
+ } else {
+ ldapHostsStr = ldapHosts.get(0);
+ }
+ }
+ URI convertedUri = new URI(
+ "ldap://" + ldapHostsStr + "/" + IpaUtils.domainToUserDirectoryConfigPath(kerberosDomain));
+ if (log.isDebugEnabled())
+ log.debug("Converted " + uri + " to " + convertedUri);
+ return convertedUri;
+ } catch (NamingException | IOException | URISyntaxException e) {
+ throw new UserDirectoryException("cannot convert IPA uri " + uri, e);
+ }
+ }
+
private static Map<String, List<String>> splitQuery(String query) throws UnsupportedEncodingException {
final Map<String, List<String>> query_pairs = new LinkedHashMap<String, List<String>>();
if (query == null)
/*
* LOGIN CONTEXTS
*/
+ String LOGIN_CONTEXT_NODE = "NODE";
String LOGIN_CONTEXT_USER = "USER";
-// String LOGIN_CONTEXT_ANONYMOUS = "ANONYMOUS";
String LOGIN_CONTEXT_DATA_ADMIN = "DATA_ADMIN";
String LOGIN_CONTEXT_SINGLE_USER = "SINGLE_USER";
-
+
/*
* PATHS
*/
String PATH_DATA = "/data";
String PATH_JCR = "/jcr";
- String PATH_JCR_PUB = "/pub";
-
+ String PATH_FILES = "/files";
+ // String PATH_JCR_PUB = "/pub";
+
/*
* FILE SYSTEMS
*/
- String SCHEME_NODE = "node";
+ String SCHEME_NODE = NODE;
+
+ /*
+ * KERBEROS
+ */
+ String NODE_SERVICE = NODE;
/*
* FRAMEWORK PROPERTIES
// @Deprecated
// String ALIAS_NODE = "node";
/** Key for a JCR repository URI */
-// @Deprecated
-// String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri";
+ // @Deprecated
+ // String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri";
// parameters (typically for call to a RepositoryFactory)
/** Key for a JCR repository alias */
// @Deprecated