// Copyright (c) 2014, SAS Institute Inc., Cary, NC, USA, All Rights Reserved
package com.sas.unravl.auth;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
/**
* Provides authentication credentials for an UnRAVL script.
* <p>
* The credentials may come from the <code>"auth"</code> element itself, via the
* properties <code>"login"</code> and <code>"password"</code>. Environment
* substitution is applied to these strings, so you can set the credentials in
* unRAVL variables or pass them as system properties.
* </p>
* <p>
* If the authentication element does not contain credentials, search for them
* by host in <code>.netrc</code> file (or <code>._netrc</code> on Windows).
* That file is read from the current directory, or if not found there, from the
* home directory (<code>.~/.netrc</code> or <code>.%USERPROFILE%\\.netrc</code>
* ). The file contains lines in the following format:
* </p>
*
* <pre>
* machine <em>qualified.hostname</em> login <em>userid-for-host</em> password <em>password-for-host</em>
*
* machine <em>qualified.hostname</em> login <em>userid-for-host</em> password <em>password-for-host</em> port <em>port-number</em>
* </pre>
* <p>
* Key/Value pairs may be in any order. Lines may use <code>user</code> instead
* of <code>login</code>; lines may use <code>host</code> instead of
* <code>machine</code>.
* </p>
* <p>
* Passwords with whitespace in them must be quoted with double quotes. Thus,
* passwords may not contain double quote characters.
*
* @author DavidBiesack@sas.com
*/
public class NetrcCredentialsProvider extends AbstractCredentialsProvider {
/**
* Create a Credentials instance for use with the UnRAVL runtime
*/
public NetrcCredentialsProvider() {
}
// matches: identifier unquoted-text-without-spaces
// matches: identifier "quoted-text with possible spaces"
private static final Pattern KEY_VALUE = Pattern
.compile("\\s*(\\w+)\\s+(\"([^\"]*)\"|([^\\s]+))");
// pattern match groups:
private static final int KEY_GROUP = 1;
private static final int QUOTED_VAL_GROUP = 3;
private static final int UNQUOTED_VAL_GROUP = 4;
static final Logger logger = Logger
.getLogger(NetrcCredentialsProvider.class);
/*
* (non-Javadoc)
*
* @see
* com.sas.unravl.auth.CredentialsProvider#getCredentials(java.lang.String,
* java.lang.String, java.lang.String, boolean)
*/
@Override
public HostCredentials getHostCredentials(String hostPort, String login,
String password, boolean mock) throws FileNotFoundException,
IOException {
if (mock)
return mockCredentials();
String clientId = credentialValue(auth, "clientId");
String clientSecret = credentialValue(auth, "clientSecret");
String accessToken = credentialValue(auth, "accessToken");
// Locate the netrc config file that contains credentials for hosts
File netrc = new File(".netrc"); // look in current dir first
if (!netrc.exists())
netrc = new File("_netrc"); // possible Windows file name, in
// current dir
File home = new File(System.getProperty("user.home"));
if (!netrc.exists())
netrc = new File(home, ".netrc");
if (!netrc.exists())
netrc = new File(home, "_netrc");
if (!netrc.exists())
return null;
// split host:port into its parts. :port is optional
String host = host(hostPort);
String port = port(hostPort);
// Note: We can read and cache all the credentials, but that
// is a security issue; we should probably encrypt the cached content.
// Also, if we do cache the file content, we would still have to check
// the modification time stamp of the file, in case it has been updated
// since we cached the content.
// Also, the loop below would need to read all rows, not stop when a
// match is found. So we don't cache and simply read the file each time
// it is needed
try (BufferedReader reader = new BufferedReader(new FileReader(netrc))) {
for (String line = reader.readLine(); line != null; line = reader
.readLine()) {
if (line.trim().startsWith("#"))
continue;
Matcher m = KEY_VALUE.matcher(line);
String lHost = null, lPort = null, lLogin = null, lPassword = null, lClientId = null, lClientSecret = null, lAccessToken = null;
while (m.find()) {
String key = m.group(KEY_GROUP).toLowerCase();
String val = m.group(QUOTED_VAL_GROUP);
if (val == null)
val = m.group(UNQUOTED_VAL_GROUP);
switch (key) {
case "login":
case "user":
lLogin = val;
break;
case "host":
case "machine":
lHost = val;
break;
case "port":
lPort = val;
break;
case "password":
lPassword = val;
break;
case "clientid":
lClientId = val;
break;
case "clientsecret":
lClientSecret = val;
break;
case "accesstoken":
lAccessToken = val;
break;
default:
logger.warn("Ignoring unknown key " + key
+ " in netrc file");
}
}
if (host.equalsIgnoreCase(lHost)
&& Objects.equals(port, lPort)
&& (clientId == null || (Objects.equals(clientId,
lClientId)))) {
if (login == null || lLogin.equals(login)) {
if ((or(clientId, lClientId) != null && or(clientSecret, lClientSecret) != null)
|| or(accessToken, lAccessToken) != null)
return credentials(or(login, lLogin), or(password, lPassword),
or(clientId, lClientId),
or(clientSecret, lClientSecret),
or(accessToken, lAccessToken));
else if (or(password, lPassword) != null)
return credentials(or(login, lLogin), or(password, lPassword));
}
}
}
}
if ((clientId != null && clientSecret != null) || accessToken != null)
return credentials(login, password, clientId, clientSecret,
accessToken);
else if (login != null && password != null)
return credentials(login, password);
return null;
}
private String or(String s1, String s2) {
return s1 == null ? s2 : s1;
}
}