/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.com
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU Affero General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License along with this program.
* If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tools;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.PasswordAuthentication;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URL;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.rapidminer.gui.tools.PasswordDialog;
import com.rapidminer.tools.cipher.CipherException;
import com.rapidminer.tools.cipher.CipherTools;
/**
* Global authenticator at which multiple other authenticators can register. Authentication requests
* will be delegated subsequently until an authenticator is found.
*
* @author Simon Fischer
*
*/
public class GlobalAuthenticator extends Authenticator {
private final List<URLAuthenticator> serverAuthenticators = new LinkedList<URLAuthenticator>();
private final List<URLAuthenticator> proxyAuthenticators = new LinkedList<URLAuthenticator>();
private URLAuthenticator socksProxyAuthenticator = null;
private static GlobalAuthenticator THE_INSTANCE = new GlobalAuthenticator();
static {
Authenticator.setDefault(THE_INSTANCE);
refreshProxyAuthenticators();
}
@Deprecated
/**
* This method is deprecated use registerServerAuthenticator instead.
*/
public synchronized static void register(URLAuthenticator authenticator) {
registerServerAuthenticator(authenticator);
}
/**
* This method adds another Authenticator to the GlobalAuthenticator that will be enqueued in
* the list of Authenticators that are tried for URLs that need authentification.
*/
public synchronized static void registerServerAuthenticator(URLAuthenticator authenticator) {
THE_INSTANCE.serverAuthenticators.add(authenticator);
}
/**
* This method adds another Authenticator to the GlobalAuthenticator that will be enqueued in
* the list of Authenticators that are tried for Proxy requests for authentification.
*/
public synchronized static void registerProxyAuthenticator(URLAuthenticator authenticator) {
THE_INSTANCE.proxyAuthenticators.add(authenticator);
}
/**
* This method adds the default ProxyAuthenticators to the GlobalAuthenticator.
*/
public synchronized static void refreshProxyAuthenticators() {
THE_INSTANCE.proxyAuthenticators.clear();
THE_INSTANCE.socksProxyAuthenticator = new SocksProxyAuthenticator(ProxyAuthenticator.SOCKS);
registerProxyAuthenticator(new ProxyAuthenticator(ProxyAuthenticator.HTTP));
registerProxyAuthenticator(new ProxyAuthenticator(ProxyAuthenticator.HTTPS));
registerProxyAuthenticator(new ProxyAuthenticator(ProxyAuthenticator.FTP));
}
@Override
protected synchronized PasswordAuthentication getPasswordAuthentication() {
URL url = getRequestingURL();
try {
if (SocksProxyAuthenticator.SOCKS5.equals(this.getRequestingProtocol())) {
PasswordAuthentication auth = socksProxyAuthenticator.getAuthentication(url);
return auth;
}
switch (getRequestorType()) {
case PROXY:
LogService.getRoot().log(Level.FINE,
"com.rapidminer.tools.GlobalAuthenticator.authentication_requested_proxy",
new Object[] { url, proxyAuthenticators });
for (URLAuthenticator a : proxyAuthenticators) {
PasswordAuthentication auth = a.getAuthentication(url);
if (auth != null) {
return auth;
}
}
// this should not be happen
return PasswordDialog.getPasswordAuthentication(url.toString(), false, false);
case SERVER:
LogService.getRoot().log(Level.FINE, "com.rapidminer.tools.GlobalAuthenticator.authentication_requested",
new Object[] { url, serverAuthenticators });
for (URLAuthenticator a : serverAuthenticators) {
PasswordAuthentication auth = a.getAuthentication(url);
if (auth != null) {
return auth;
}
}
}
return PasswordDialog.getPasswordAuthentication(url.toString(), false, true);
} catch (PasswordInputCanceledException e) {
return null;
}
}
/**
* This method is called to cause the loading of the class and the execution of static blocks.
*/
public static void init() {}
public static GlobalAuthenticator getInstance() {
return THE_INSTANCE;
}
public interface URLAuthenticator {
/**
* This method returns the PasswordAuthentification if this Authenticator is registered for
* the given URL. Otherwise null can be returned.
*/
public PasswordAuthentication getAuthentication(URL url) throws PasswordInputCanceledException;
public String getName();
}
private static class ProxyAuthenticator implements URLAuthenticator {
private static final String PROXY_AUTH = "407";
private static final int STATUS_FIELD = 0;
public static final String SOCKS = "socks";
public static final String HTTP = "http";
public static final String HTTPS = "https";
public static final String FTP = "ftp";
private String protocol;
protected String username = "";
protected String password = "";
public ProxyAuthenticator(String protocol) {
this.protocol = protocol;
}
@Override
public PasswordAuthentication getAuthentication(URL url) throws PasswordInputCanceledException {
return getAuthentication(url, "auth.proxy", false);
}
public PasswordAuthentication getAuthentication(URL url, String i18n, boolean forceRefresh)
throws PasswordInputCanceledException {
if (protocol.equals(SOCKS) || url.getProtocol().equals(protocol)) {
// password is stored encrypted, try to decrypt password
if (password != null && CipherTools.isKeyAvailable()) {
try {
password = CipherTools.decrypt(password);
} catch (CipherException e) {
// password is in plaintext
}
}
if (username == null || username.isEmpty() || password == null) { // empty
// passwords
// possibly
// valid!
String proxyType = protocol.toUpperCase();
String proxyID = I18N.getMessage(I18N.getGUIBundle(), "gui.dialog.auth.proxy.id", proxyType);
String proxyURL = getProxyAddress().toString().replaceAll("/", "");
String authMessage = getAuthMessage();
PasswordAuthentication passwordAuthentication = PasswordDialog.getPasswordAuthentication(proxyID,
proxyURL, forceRefresh, true, i18n, proxyType, proxyURL, authMessage);
if (passwordAuthentication == null) {
return null;
}
username = passwordAuthentication.getUserName();
password = new String(passwordAuthentication.getPassword());
// Verify Settings
passwordAuthentication = verify(url, passwordAuthentication);
return passwordAuthentication;
}
return new PasswordAuthentication(username, password.toCharArray());
}
return null;
}
/**
* Returns a message to make the authentication mechanism transparent to the user
*
* @return The i18n message for the given authentication scheme
*/
private String getAuthMessage() {
String i18n = "gui.dialog.auth.proxy.unknown";
if (GlobalAuthenticator.THE_INSTANCE.getRequestingScheme() != null) {
switch (GlobalAuthenticator.THE_INSTANCE.getRequestingScheme().toLowerCase().trim()) {
case "basic":
i18n = "gui.dialog.auth.proxy.basic";
break;
case "digest":
i18n = "gui.dialog.auth.proxy.digest";
break;
case "ntlm":
i18n = "gui.dialog.auth.proxy.ntlm";
break;
case "spnego":
i18n = "gui.dialog.auth.proxy.negotiate";
break;
case "negotiate":
i18n = "gui.dialog.auth.proxy.negotiate";
break;
case "kerberos":
i18n = "gui.dialog.auth.proxy.kerberos";
break;
default:
// i18n already set
break;
}
}
return I18N.getMessage(I18N.getGUIBundle(), i18n);
}
/**
* The current ProxyAddress
*
* @return The SocketAddress as given by the GlobalAuthenticator
*/
protected SocketAddress getProxyAddress() {
return new InetSocketAddress(GlobalAuthenticator.getInstance().getRequestingHost(),
GlobalAuthenticator.getInstance().getRequestingPort());
}
/**
* Verify the proxy by accessing the url using the given PasswordAuthentication
*
* @param url
* The URL to test
* @param pA
* username+password to test
* @return A valid passwordAuthentication or null
* @throws PasswordInputCanceledException
*/
protected PasswordAuthentication verify(URL url, PasswordAuthentication pA) throws PasswordInputCanceledException {
try {
// make sure to only call foo.bar not foo.bar/destroy/world
URL safeUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), "");
if (safeUrl.openConnection().getHeaderField(STATUS_FIELD).contains(PROXY_AUTH)) {
username = "";
password = "";
pA = getAuthentication(url, "auth.proxy.wrong.credentials", true);
}
} catch (IOException e) {
Logger.getLogger(ProxyAuthenticator.class.getName()).log(Level.SEVERE,
I18N.getMessage(I18N.getErrorBundle(), "proxy.credentials.verify.failure", e));
}
return pA;
}
@Override
public String getName() {
return "Proxy Authenticator";
}
}
private static class SocksProxyAuthenticator extends ProxyAuthenticator {
/**
* SOCKS implementation is screwed up, so we don't have access to the requested URI
*/
public static final String TEST_URL = "http://www.rapidminer.com";
/**
* Magic string for SOCKS proxies
*
* @see {@link SocksSocketImpl#authenticate(byte, InputStream, BufferedOutputStream, long)}
*/
public static final String SOCKS5 = "SOCKS5";
/**
* @param protocol
*/
public SocksProxyAuthenticator(String protocol) {
super(protocol);
}
@Override
/**
* Since socks version 4 does not support authentication, we can assume version 5 and use
* the Proxy object here instead of sun.net.SocksProxy which also supports v4
* <p>
* Warning: The behaviour might change with Java 9 use ProxySelector.getDefault().select()
* lastElement to receive an SocksProxy
* </p>
*/
protected PasswordAuthentication verify(URL url, PasswordAuthentication pA) throws PasswordInputCanceledException {
try {
if (url == null) {
url = new URL(TEST_URL);
}
// make sure to only call foo.bar not foo.bar/destroy/world
URL safeUrl = new URL(url.getProtocol(), url.getHost(), url.getPort(), "");
safeUrl.openConnection(new Proxy(Proxy.Type.SOCKS, getProxyAddress())).connect();
} catch (SocketException se) {
username = "";
password = "";
Logger.getLogger(SocksProxyAuthenticator.class.getName()).log(Level.FINER, se.getMessage());
pA = getAuthentication(url, "auth.proxy.wrong.credentials", true);
} catch (IOException e) {
Logger.getLogger(SocksProxyAuthenticator.class.getName()).log(Level.SEVERE,
I18N.getMessage(I18N.getErrorBundle(), "proxy.credentials.verify.failure", e));
}
return pA;
}
}
}