Improve IPA integration
authorMathieu Baudier <mbaudier@argeo.org>
Sun, 5 Feb 2017 17:11:27 +0000 (18:11 +0100)
committerMathieu Baudier <mbaudier@argeo.org>
Sun, 5 Feb 2017 17:11:27 +0000 (18:11 +0100)
26 files changed:
org.argeo.cms.ui.workbench.rap/plugin.xml
org.argeo.cms.ui.workbench.rap/src/org/argeo/cms/ui/workbench/rap/SpnegoWorkbenchLogin.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/util/LoginEntryPoint.java
org.argeo.cms/ext/test/org/argeo/cms/security/RunHttpSpnego.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/auth/CmsAuthUtils.java
org.argeo.cms/src/org/argeo/cms/auth/HttpRequestCallback.java
org.argeo.cms/src/org/argeo/cms/auth/HttpSessionLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/IpaLoginModule.java
org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/Activator.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsSecurity.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/DataHttp.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/DeployConfig.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInit.java [new file with mode: 0644]
org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInitProperties.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/KernelUtils.java
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeDeployConfig.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java [deleted file]
org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas-ipa.cfg
org.argeo.cms/src/org/argeo/cms/internal/kernel/jaas.cfg
org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java [new file with mode: 0644]
org.argeo.enterprise/src/org/argeo/naming/LdapAttrs.java
org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java [new file with mode: 0644]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java [new file with mode: 0644]
org.argeo.enterprise/src/org/argeo/osgi/useradmin/UserAdminConf.java
org.argeo.node.api/src/org/argeo/node/NodeConstants.java

index 6dc4ba0ef3e3a088852e6b29176b5e3166265979..880563bd3ebefd789d17cbb0f2c78ca0659dbcbd 100644 (file)
             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"
diff --git a/org.argeo.cms.ui.workbench.rap/src/org/argeo/cms/ui/workbench/rap/SpnegoWorkbenchLogin.java b/org.argeo.cms.ui.workbench.rap/src/org/argeo/cms/ui/workbench/rap/SpnegoWorkbenchLogin.java
new file mode 100644 (file)
index 0000000..efb2518
--- /dev/null
@@ -0,0 +1,99 @@
+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();
+       }
+
+}
index 6b26b37288a1ff02124ac910b0e42bc75882ba18..1f24da2f4327d3d9d27c550cd73aa059fafd390f 100644 (file)
@@ -5,6 +5,7 @@ import java.util.Locale;
 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;
@@ -23,6 +24,8 @@ import org.eclipse.swt.widgets.Composite;
 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;
@@ -40,6 +43,21 @@ public class LoginEntryPoint implements EntryPoint, CmsView {
                } 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();
diff --git a/org.argeo.cms/ext/test/org/argeo/cms/security/RunHttpSpnego.java b/org.argeo.cms/ext/test/org/argeo/cms/security/RunHttpSpnego.java
new file mode 100644 (file)
index 0000000..f090ac4
--- /dev/null
@@ -0,0 +1,32 @@
+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);
+    }
+}
index 760544b8189f38b57154976726f034cc51619af4..1562d5cd0f8cad6d345e861977ad40a8d174f9f6 100644 (file)
@@ -40,6 +40,11 @@ class CmsAuthUtils {
        /** 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);
index dbc2aeee98c21a9c720fa0aa33d2bf7858bde7ed..d87e70474175b1b5b94369dbfaf018e2c9936227 100644 (file)
@@ -13,13 +13,4 @@ public class HttpRequestCallback implements Callback {
        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;
-       // }
-
 }
index eac68036d51c2862bec9db79bfbb08c05cc94c7a..b450401ff015bd5aaf268f9b000b065f5e87296f 100644 (file)
@@ -1,8 +1,10 @@
 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;
@@ -12,10 +14,10 @@ import javax.security.auth.login.LoginException;
 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;
@@ -87,6 +89,8 @@ public class HttpSessionLoginModule implements LoginModule {
 
                }
                sharedState.put(CmsAuthUtils.SHARED_STATE_HTTP_REQUEST, request);
+               extractHttpAuth(request);
+               extractClientCertificate(request);
                if (authorization == null)
                        return false;
                sharedState.put(CmsAuthUtils.SHARED_STATE_AUTHORIZATION, authorization);
@@ -171,4 +175,44 @@ public class HttpSessionLoginModule implements LoginModule {
                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;
+       }
+
 }
index b5d836c94c28813b0922de6afc78d08175f33d46..0cbdc7d5b64a18c274271b5ec4a2987699b2fa06 100644 (file)
@@ -4,7 +4,6 @@ import java.security.PrivilegedAction;
 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;
@@ -14,7 +13,7 @@ import javax.security.auth.spi.LoginModule;
 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;
@@ -57,7 +56,7 @@ public class IpaLoginModule implements LoginModule {
                        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>() {
 
@@ -79,21 +78,6 @@ public class IpaLoginModule implements LoginModule {
                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 {
diff --git a/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java b/org.argeo.cms/src/org/argeo/cms/auth/SpnegoLoginModule.java
new file mode 100644 (file)
index 0000000..ef2872e
--- /dev/null
@@ -0,0 +1,118 @@
+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;
+
+       }
+}
index 24c2f6bccc7d1c7e9d8e004f94d56623e92513dc..73c530c82d63d1e0ec31e32cb88da7b47a80157a 100644 (file)
@@ -1,15 +1,12 @@
 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;
@@ -19,6 +16,7 @@ import org.argeo.node.NodeDeployment;
 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;
@@ -35,6 +33,7 @@ public class Activator implements BundleActivator {
        private static Activator instance;
 
        private BundleContext bc;
+       private CmsSecurity nodeSecurity;
        private LogReaderService logReaderService;
        // private ConfigurationAdmin configurationAdmin;
 
@@ -51,7 +50,7 @@ public class Activator implements BundleActivator {
                // this.configurationAdmin = getService(ConfigurationAdmin.class);
 
                try {
-                       initSecurity();// must be first
+                       nodeSecurity = new CmsSecurity();
                        initArgeoLogger();
                        initNode();
                } catch (Exception e) {
@@ -60,16 +59,6 @@ public class Activator implements BundleActivator {
                }
        }
 
-       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);
@@ -122,6 +111,14 @@ public class Activator implements BundleActivator {
                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();
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsSecurity.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/CmsSecurity.java
new file mode 100644 (file)
index 0000000..7e1834a
--- /dev/null
@@ -0,0 +1,318 @@
+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);
+       // }
+}
index 93a37ed98e58b286c8d8ce425442c3a960c0ae6a..c9b44aa8e64e15d5c640cd9ad5338eae5d12d92e 100644 (file)
@@ -34,6 +34,12 @@ import org.argeo.cms.auth.HttpRequestCallback;
 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;
@@ -79,6 +85,7 @@ class DataHttp implements KernelConstants {
                try {
                        registerWebdavServlet(alias, repository);
                        registerRemotingServlet(alias, repository);
+                       registerFilesServlet(alias, repository);
                        if (log.isDebugEnabled())
                                log.debug("Registered servlets for repository '" + alias + "'");
                } catch (Exception e) {
@@ -90,6 +97,7 @@ class DataHttp implements KernelConstants {
                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) {
@@ -106,6 +114,15 @@ class DataHttp implements KernelConstants {
                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);
@@ -128,6 +145,10 @@ class DataHttp implements KernelConstants {
                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)
@@ -142,12 +163,23 @@ class DataHttp implements KernelConstants {
                }
        }
 
-       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);
@@ -180,6 +212,44 @@ class DataHttp implements KernelConstants {
                                        } 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);
+                                       }
+
                                }
                        }
                }
@@ -217,6 +287,7 @@ class DataHttp implements KernelConstants {
                @Override
                public boolean handleSecurity(final HttpServletRequest request, HttpServletResponse response)
                                throws IOException {
+
                        if (log.isTraceEnabled())
                                KernelUtils.logRequestHeaders(log, request);
                        LoginContext lc;
@@ -225,7 +296,7 @@ class DataHttp implements KernelConstants {
                                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);
@@ -264,6 +335,51 @@ class DataHttp implements KernelConstants {
 
        }
 
+       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;
 
@@ -291,7 +407,7 @@ class DataHttp implements KernelConstants {
                                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);
@@ -302,7 +418,7 @@ class DataHttp implements KernelConstants {
                                                throw new CmsException("Could not login", e1);
                                        }
                                } else {
-                                       requestBasicAuth(request, response);
+                                       askForWwwAuth(request, response);
                                        lc = null;
                                }
                        } catch (LoginException e) {
index 61e840e3226d1c5c513585a41b1f2ba49915edea..43afa3d3b2b93ddf7e1e65880c35cfcf3e111b72 100644 (file)
@@ -34,14 +34,14 @@ class DeployConfig implements ConfigurationListener {
        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);
@@ -55,15 +55,15 @@ class DeployConfig implements ConfigurationListener {
        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
@@ -245,4 +245,8 @@ class DeployConfig implements ConfigurationListener {
                        return null;
        }
 
+       static boolean isInitialized() {
+               return Files.exists(deployConfigPath);
+       }
+
 }
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInit.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInit.java
new file mode 100644 (file)
index 0000000..aa9cf7f
--- /dev/null
@@ -0,0 +1,188 @@
+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);
+                       }
+       }
+
+}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInitProperties.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/FirstInitProperties.java
deleted file mode 100644 (file)
index 621fe6f..0000000
+++ /dev/null
@@ -1,180 +0,0 @@
-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);
-                       }
-       }
-
-}
index 8f736d0a391b7d7d1a7695a1123d285d3c4571b7..e3bbdb79629d1cd8ed8a9d7837dce0b859a6fc3c 100644 (file)
@@ -2,6 +2,7 @@ package org.argeo.cms.internal.kernel;
 
 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;
@@ -11,6 +12,7 @@ import java.util.Dictionary;
 import java.util.Enumeration;
 import java.util.Hashtable;
 import java.util.Properties;
+import java.util.TreeMap;
 import java.util.TreeSet;
 
 import javax.jcr.Repository;
@@ -148,6 +150,14 @@ class KernelUtils implements KernelConstants {
                // 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);
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeDeployConfig.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeDeployConfig.java
deleted file mode 100644 (file)
index 39ca0b3..0000000
+++ /dev/null
@@ -1,100 +0,0 @@
-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);
-       }
-
-}
diff --git a/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java b/org.argeo.cms/src/org/argeo/cms/internal/kernel/NodeSecurity.java
deleted file mode 100644 (file)
index 6d216c6..0000000
+++ /dev/null
@@ -1,163 +0,0 @@
-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);
-       // }
-}
index 690bfc198c258c9bc0b6f7ad203f06c9dfba301a..38fe3705f26b2f4c4b942dd47d9b1adf50f2a733 100644 (file)
@@ -1,17 +1,29 @@
 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;
 };
@@ -19,3 +31,4 @@ KEYRING {
 Jackrabbit {
    org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite;
 };
+
index fdce43b6a7ac0727d1a30a568afa0dd010df38a8..83d36d927695cb9a7b7226e6eddcd39ee0043da8 100644 (file)
@@ -3,23 +3,14 @@ USER {
     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;
 };
diff --git a/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java b/org.argeo.enterprise/src/org/argeo/naming/DnsBrowser.java
new file mode 100644 (file)
index 0000000..7aadd64
--- /dev/null
@@ -0,0 +1,184 @@
+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
index 29f6e005f5933beca663f0b7cdf4cf3c3cf3a1c3..d5324974ba3f87e8e06b9c6bf07044d896e9c8b1 100644 (file)
@@ -1,11 +1,13 @@
 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"),
        /** */
@@ -263,7 +265,10 @@ public enum LdapAttrs implements SpecifiedName{
        /** */
        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";
 
diff --git a/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java b/org.argeo.enterprise/src/org/argeo/naming/SrvRecord.java
new file mode 100644 (file)
index 0000000..d351588
--- /dev/null
@@ -0,0 +1,52 @@
+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;
+       }
+}
diff --git a/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java b/org.argeo.enterprise/src/org/argeo/osgi/useradmin/IpaUtils.java
new file mode 100644 (file)
index 0000000..1d198c6
--- /dev/null
@@ -0,0 +1,54 @@
+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() {
+
+       }
+}
index eef527b00167e94b4a6e4b1a4fd900076f241f5c..bbd9ad8192cf49d847a976dd42da2a848a313e52 100644 (file)
@@ -1,5 +1,6 @@
 package org.argeo.osgi.useradmin;
 
+import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
@@ -13,7 +14,11 @@ import java.util.List;
 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. */
@@ -40,6 +45,7 @@ public enum UserAdminConf {
        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;
@@ -117,6 +123,10 @@ public enum UserAdminConf {
                        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"))
@@ -133,6 +143,7 @@ public enum UserAdminConf {
                                                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());
@@ -161,6 +172,30 @@ public enum UserAdminConf {
                }
        }
 
+       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)
index 42676f5fac6b06283168ed8ec790cb368e5387e8..bfd979c7f144c0fe237c41bd3e8113faa8ddc9f1 100644 (file)
@@ -67,22 +67,28 @@ public interface NodeConstants {
        /*
         * 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
@@ -119,8 +125,8 @@ public interface NodeConstants {
        // @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