blob: f15d192705edc4a1f4703372ef7be68ae19a2b14 [file] [log] [blame]
/*
* Copyright (c) 2010 The Eclipse Foundation and others.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Eike Stepper - initial API and implementation
* Yatta Solutions - Generalization and move to USS API
*/
package org.eclipse.userstorage.oauth;
import org.eclipse.userstorage.internal.util.StringUtil;
import org.eclipse.core.runtime.Platform;
import org.osgi.framework.BundleContext;
import org.osgi.framework.FrameworkUtil;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.util.Random;
/**
* An obfucscated encapsulation of OAuth parameters for USS. To use this
* class and launch the {@link #main()} method as a a Java application.
* Provide your OAuth Client ID and Secret, and the class will generate encoded
* values. Then decide on a service name, and subclass this class, and configure
* the required values. The encoded values can be (and are) decoded programmatically,
* which is acceptable since USS is not intended for private data.
* <p>
* The values can be overridden at runtime by setting the following properties:
* <ul>
* <li> OAuth Service: <tt><em>serviceName</em>.oauth.service</tt> (the authorization endpoint)
* <li> OAuth Client ID: <tt><em>serviceName</em>.oauth.client.id</tt>
* <li> OAuth Client Secret: <tt><em>serviceName</em>.oauth.client.secret</tt>
* <li> OAuth Callback: <tt><em>serviceName</em>.oauth.expected.callback</tt>
* <li> OAuth Callback: <tt><em>serviceName</em>.oauth.expected.callback</tt>
* </ul>
*
* @author Carsten Reckord
* @author Eike Stepper
* @since 1.0
*/
public abstract class OAuthParameters
{
public static final String PROP_SERVICE = ".oauth.service";
public static final String PROP_EXPECTED_CALLBACK = ".oauth.expected.callback";
public static final String PROP_SCOPES = ".oauth.scopes";
public static final String PROP_CLIENT_ID = ".oauth.client.id";
public static final String PROP_CLIENT_SECRET = ".oauth.client.secret";
public static final String PROP_CLIENT_KEY = ".oauth.client.key";
public static final String DEFAULT_SERVICE = "https://accounts.eclipse.org/";
public static final String DEFAULT_EXPECTED_CALLBACK = "http://localhost/";
private static final String[] DEFAULT_SCOPES = { "profile", "uss_retrieve", "uss_update" };
private final BundleContext bundleContext;
protected OAuthParameters()
{
bundleContext = Platform.isRunning() ? FrameworkUtil.getBundle(getClass()).getBundleContext() : null;
}
protected abstract String getServiceName();
String getDecryptedClientID()
{
return getDecryptedValue(getServiceName() + PROP_CLIENT_ID, getDefaultClientId());
}
protected abstract String getDefaultClientId();
String getDecryptedClientSecret()
{
return getDecryptedValue(getServiceName() + PROP_CLIENT_SECRET, getDefaultClientSecret());
}
protected abstract String getDefaultClientSecret();
public URI getService()
{
String serviceAddress = getProperty(getServiceName() + PROP_SERVICE, getDefaultService());
return URI.create(serviceAddress);
}
protected String getDefaultService()
{
return DEFAULT_SERVICE;
}
public URI getExpectedCallback()
{
String expectedCallbackAddress = getProperty(getServiceName() + PROP_EXPECTED_CALLBACK, getDefaultExpectedCallback());
return URI.create(expectedCallbackAddress);
}
protected String getDefaultExpectedCallback()
{
return DEFAULT_EXPECTED_CALLBACK;
}
public String[] getScopes()
{
String scopesStr = getProperty(getServiceName() + PROP_SCOPES, null);
return scopesStr != null ? initScopes(scopesStr) : getDefaultScopes();
}
protected String[] getDefaultScopes()
{
return DEFAULT_SCOPES;
}
protected abstract String getDefaultClientKey();
private String getDecryptedValue(String propName, String defaultValue)
{
try
{
String property = getProperty(propName);
if (!isEmpty(property))
{
String key = getProperty(getServiceName() + PROP_CLIENT_KEY);
if (!isEmpty(key))
{
return decrypt(property, key);
}
return property;
}
}
catch (Throwable ex)
{
//$FALL-THROUGH$
}
return decrypt(defaultValue, getDefaultClientKey());
}
private static boolean isEmpty(String s)
{
return s == null || s.length() == 0;
}
protected final String getProperty(String propName)
{
return getProperty(propName, null);
}
protected String getProperty(String propName, String defaultValue)
{
String value = bundleContext == null ? System.getProperty(propName) : bundleContext.getProperty(propName);
if (value == null)
{
value = defaultValue;
}
return value;
}
private static String decrypt(String str, String key)
{
byte[] keyBytes = hexToBytes(key);
byte[] bytes = hexToBytes(str);
byte[] result = new byte[bytes.length - 1];
int j = bytes[result.length] - Byte.MIN_VALUE;
crypt(bytes, result, keyBytes, result.length, j);
try
{
return new String(result, StringUtil.UTF8);
}
catch (UnsupportedEncodingException e)
{
throw new IllegalArgumentException(e);
}
}
private static byte[] hexToBytes(String hexStr)
{
int hexStrLen = hexStr.length();
if ((hexStrLen & 1) == 1)
{
hexStr = '0' + hexStr;
hexStrLen++;
}
byte[] out = new byte[hexStrLen / 2];
// Safe to assume the string is even length
byte b1, b2;
for (int i = 0; i < hexStrLen; i += 2)
{
b1 = (byte)Character.digit(hexStr.charAt(i), 16);
b2 = (byte)Character.digit(hexStr.charAt(i + 1), 16);
if (b1 < 0 || b2 < 0)
{
throw new NumberFormatException(hexStr);
}
out[i / 2] = (byte)(b1 << 4 | b2 & 0xff);
}
return out;
}
private static String bytesToHex(byte[] bs)
{
StringBuilder builder = new StringBuilder(bs.length * 2);
for (byte element : bs)
{
builder.append(Character.forDigit(element >>> 4 & 0xf, 16));
builder.append(Character.forDigit(element & 0xf, 16));
}
return builder.toString();
}
private static byte[] encrypt(String str, byte[] key, Random random) throws UnsupportedEncodingException
{
byte[] bytes = str.getBytes("UTF-8");
byte[] result = new byte[bytes.length + 1];
int j = random.nextInt(key.length);
result[bytes.length] = (byte)(j + Byte.MIN_VALUE);
crypt(bytes, result, key, bytes.length, j);
return result;
}
private static void crypt(byte[] bytes, byte[] result, byte[] key, int length, int j)
{
for (int i = 0; i < length; i++)
{
result[i] = (byte)(bytes[i] ^ key[j++ % key.length]);
}
}
private static String[] initScopes(String property)
{
try
{
if (!isEmpty(property))
{
return property.split(","); //$NON-NLS-1$
}
}
catch (Throwable ex)
{
//$FALL-THROUGH$
}
return DEFAULT_SCOPES;
}
/**
* Run to create encrypted client parameters for your client
*/
public static void main(String[] args) throws Exception
{
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
System.out.print("Client ID: ");
String clientID = reader.readLine();
System.out.print("Client Secret: ");
String clientSecret = reader.readLine();
int keyLength = Math.max(clientID.length(), clientSecret.length());
byte[] key = new byte[keyLength];
Random random = new Random(System.currentTimeMillis());
random.nextBytes(key);
clientID = bytesToHex(encrypt(clientID, key, random));
clientSecret = bytesToHex(encrypt(clientSecret, key, random));
String clientKey = bytesToHex(key);
System.out.println();
System.out.println("private static final String CLIENT_ID = \"" + clientID + "\";");
System.out.println("private static final String CLIENT_SECRET = \"" + clientSecret + "\";");
System.out.println("private static final String CLIENT_KEY = \"" + clientKey + "\";");
System.out.println();
}
}