final static String SHARED_STATE_SPNEGO_TOKEN = "org.argeo.cms.auth.spnegoToken";
final static String SHARED_STATE_SPNEGO_OUT_TOKEN = "org.argeo.cms.auth.spnegoOutToken";
final static String SHARED_STATE_CERTIFICATE_CHAIN = "org.argeo.cms.auth.certificateChain";
+ final static String SHARED_STATE_REMOTE_ADDR = "org.argeo.cms.auth.remote.addr";
+ final static String SHARED_STATE_REMOTE_PORT = "org.argeo.cms.auth.remote.port";
static void addAuthorization(Subject subject, Authorization authorization) {
assert subject != null;
} else {
authorization = (Authorization) request.getAttribute(HttpContext.AUTHORIZATION);
if (authorization == null) {// search by session ID
- // TODO implement ident
-// IdentClient identClient = new IdentClient(request.getRemoteAddr(), "changeit");
-// String identUsername = identClient.getUsername(request.getLocalPort(), request.getRemotePort());
-// log.debug("Ident username: " + identUsername);
-
HttpSession httpSession = request.getSession(false);
if (httpSession == null) {
// TODO make sure this is always safe
--- /dev/null
+package org.argeo.cms.auth;
+
+import java.io.IOException;
+import java.util.Map;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+import javax.security.auth.spi.LoginModule;
+import javax.servlet.http.HttpServletRequest;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.internal.kernel.Activator;
+import org.argeo.ident.IdentClient;
+
+public class IdentLoginModule implements LoginModule {
+ private final static Log log = LogFactory.getLog(IdentLoginModule.class);
+
+ private Subject subject = null;
+ private CallbackHandler callbackHandler = null;
+ private Map<String, Object> sharedState = null;
+
+ @Override
+ public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String, ?> sharedState,
+ Map<String, ?> options) {
+ this.subject = subject;
+ this.callbackHandler = callbackHandler;
+ this.sharedState = (Map<String, Object>) sharedState;
+ }
+
+ @Override
+ public boolean login() throws LoginException {
+ if (callbackHandler == null)
+ return false;
+ HttpRequestCallback httpCallback = new HttpRequestCallback();
+ try {
+ callbackHandler.handle(new Callback[] { httpCallback });
+ } catch (IOException e) {
+ throw new LoginException("Cannot handle http callback: " + e.getMessage());
+ } catch (UnsupportedCallbackException e) {
+ return false;
+ }
+ HttpServletRequest request = httpCallback.getRequest();
+ IdentClient identClient = Activator.getIdentClient(request.getRemoteAddr());
+ if (identClient == null)
+ return false;
+ String identUsername;
+ try {
+ identUsername = identClient.getUsername(request.getLocalPort(), request.getRemotePort());
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ if (identUsername != null) {
+ if (log.isDebugEnabled())
+ log.debug("Ident username: " + identUsername);
+ sharedState.put(CmsAuthUtils.SHARED_STATE_NAME, identUsername);
+ sharedState.put(CmsAuthUtils.SHARED_STATE_REMOTE_ADDR, request.getRemoteAddr());
+ sharedState.put(CmsAuthUtils.SHARED_STATE_REMOTE_PORT, request.getRemotePort());
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean commit() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean abort() throws LoginException {
+ return true;
+ }
+
+ @Override
+ public boolean logout() throws LoginException {
+ return true;
+ }
+
+}
final String username;
final char[] password;
Object certificateChain = null;
+ boolean preauth = false;
if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)
&& sharedState.containsKey(CmsAuthUtils.SHARED_STATE_PWD)) {
// NB: required by Basic http auth
username = certDn;
certificateChain = sharedState.get(CmsAuthUtils.SHARED_STATE_CERTIFICATE_CHAIN);
password = null;
+ } else if (sharedState.containsKey(CmsAuthUtils.SHARED_STATE_NAME)
+ && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_REMOTE_ADDR)
+ && sharedState.containsKey(CmsAuthUtils.SHARED_STATE_REMOTE_PORT)) {// ident
+ username = (String) sharedState.get(CmsAuthUtils.SHARED_STATE_NAME);
+ password = null;
+ preauth = true;
} else if (singleUser) {
username = OsUserUtils.getOsUsername();
password = null;
// is provided
} else if (singleUser) {
// TODO verify IP address?
+ } else if (preauth) {
+ // ident
} else {
throw new CredentialNotFoundException("No credentials provided");
}
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.argeo.cms.CmsException;
+import org.argeo.ident.IdentClient;
import org.argeo.node.ArgeoLogger;
import org.argeo.node.NodeConstants;
import org.argeo.node.NodeDeployment;
return KernelUtils.getFrameworkProp(NodeConstants.HTTP_PROXY_SSL_DN);
}
+ public static IdentClient getIdentClient(String remoteAddr) {
+ if (!IdentClient.isDefaultAuthdPassphraseFileAvailable())
+ return null;
+ // TODO make passphrase more configurable
+ return new IdentClient(remoteAddr);
+ }
+
private static NodeUserAdmin getNodeUserAdmin() {
NodeUserAdmin res;
try {
USER {
org.argeo.cms.auth.HttpSessionLoginModule sufficient;
+ org.argeo.cms.auth.IdentLoginModule optional;
org.argeo.cms.auth.UserAdminLoginModule sufficient;
};
package org.argeo.ident;
import java.io.BufferedReader;
+import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.net.ConnectException;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
import java.util.StringTokenizer;
/**
* @see RFC 1413 https://tools.ietf.org/html/rfc1413
*/
public class IdentClient {
- private String host = "localhost";
- private int port = 113;
+ public final static int DEFAULT_IDENT_PORT = 113;
+ public final static String AUTHD_PASSPHRASE_PATH = "/etc/ident.key";
+ final static String NO_USER = "NO-USER";
+
+ private final String host;
+ private final int port;
private OpenSslDecryptor openSslDecryptor = new OpenSslDecryptor();
- private String identPassphrase = "changeit";
+ private String identPassphrase = null;
+
+ public IdentClient(String host) {
+ this(host, readPassphrase(AUTHD_PASSPHRASE_PATH), DEFAULT_IDENT_PORT);
+ }
+
+ public IdentClient(String host, Path passPhrasePath) {
+ this(host, readPassphrase(passPhrasePath), DEFAULT_IDENT_PORT);
+ }
public IdentClient(String host, String identPassphrase) {
- this(host, identPassphrase, 113);
+ this(host, identPassphrase, DEFAULT_IDENT_PORT);
}
public IdentClient(String host, String identPassphrase, int port) {
this.port = port;
}
+ /** @return the username or null if none */
public String getUsername(int serverPort, int clientPort) {
String answer;
try (Socket socket = new Socket(host, port)) {
out.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
answer = reader.readLine();
+ } catch (ConnectException e) {
+ System.err
+ .println("Ident client is configured but no ident server available on " + host + " (port " + port + ")");
+ return null;
} catch (Exception e) {
- throw new RuntimeException("Cannot read from ident server on " + host + ":" + port, e);
+ throw new RuntimeException("Cannot read from ident server on " + host + " (port " + port + ")", e);
}
StringTokenizer st = new StringTokenizer(answer, " :\n");
String username = null;
while (st.hasMoreTokens())
username = st.nextToken();
- if (username.startsWith("[")) {
+
+ if (username.equals(NO_USER))
+ return null;
+
+ if (identPassphrase != null && username.startsWith("[")) {
String encrypted = username.substring(1, username.length() - 1);
username = openSslDecryptor.decryptAuthd(encrypted, identPassphrase).trim();
}
this.openSslDecryptor = openSslDecryptor;
}
+ public static String readPassphrase(String filePath) {
+ return readPassphrase(Paths.get(filePath));
+ }
+
+ /** @return the first line of the file. */
+ public static String readPassphrase(Path path) {
+ if (!isPathAvailable(path))
+ return null;
+ List<String> lines;
+ try {
+ lines = Files.readAllLines(path);
+ } catch (IOException e) {
+ throw new IllegalStateException("Cannot read " + path, e);
+ }
+ if (lines.size() == 0)
+ return null;
+ String passphrase = lines.get(0);
+ return passphrase;
+ }
+
+ public static boolean isDefaultAuthdPassphraseFileAvailable() {
+ return isPathAvailable(Paths.get(AUTHD_PASSPHRASE_PATH));
+ }
+
+ public static boolean isPathAvailable(Path path) {
+ if (!Files.exists(path))
+ return false;
+ if (!Files.isReadable(path))
+ return false;
+ return true;
+ }
+
public static void main(String[] args) {
IdentClient identClient = new IdentClient("127.0.0.1", "changeit");
String username = identClient.getUsername(7070, 55958);