Fix the CoinbaseClient to use their updated auth.

Closes #43

// FREEBIE
This commit is contained in:
Tina Huang 2015-01-28 17:11:36 -10:00 committed by Moxie Marlinspike
parent b15d227696
commit 20b6aebb29
11 changed files with 84 additions and 42 deletions

View File

@ -18,3 +18,4 @@ github:
coinbase:
apiKey: # Your Coinbase API key.
apiSecret: # Your Coinbase API secret.

View File

@ -21,6 +21,7 @@ import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.whispersystems.bithub.auth.GithubWebhookAuthenticator;
import org.whispersystems.bithub.client.CoinbaseClient;
import org.whispersystems.bithub.client.GithubClient;
import org.whispersystems.bithub.config.CoinbaseConfiguration;
import org.whispersystems.bithub.config.RepositoryConfiguration;
import org.whispersystems.bithub.controllers.DashboardController;
import org.whispersystems.bithub.controllers.GithubController;
@ -64,9 +65,11 @@ public class BithubService extends Application<BithubServerConfiguration> {
BigDecimal payoutRate = config.getBithubConfiguration().getPayoutRate();
String organizationName = config.getOrganizationConfiguration().getName();
String donationUrl = config.getOrganizationConfiguration().getDonationUrl().toExternalForm();
String coinbaseApiKey = config.getCoinbaseConfiguration().getApiKey();
String coinbaseApiSecret = config.getCoinbaseConfiguration().getApiSecret();
GithubClient githubClient = new GithubClient(githubUser, githubToken);
CoinbaseClient coinbaseClient = new CoinbaseClient(config.getCoinbaseConfiguration().getApiKey());
CoinbaseClient coinbaseClient = new CoinbaseClient(coinbaseApiKey, coinbaseApiSecret);
CacheManager cacheManager = new CacheManager(coinbaseClient, githubClient, githubRepositories, payoutRate);
environment.servlets().addFilter("CORS", CrossOriginFilter.class)

View File

@ -24,18 +24,23 @@ import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;
import org.apache.commons.codec.binary.Hex;
import org.codehaus.jackson.map.ObjectMapper;
import org.whispersystems.bithub.entities.Author;
import org.whispersystems.bithub.entities.BalanceResponse;
import org.whispersystems.bithub.entities.BitcoinTransaction;
import org.whispersystems.bithub.entities.BitcoinTransactionResponse;
import org.whispersystems.bithub.entities.ExchangeRate;
import org.whispersystems.bithub.entities.CoinbseRecentTransactionsResponse;
import org.whispersystems.bithub.entities.CoinbaseTransaction;
import org.whispersystems.bithub.entities.CoinbseRecentTransactionsResponse;
import org.whispersystems.bithub.entities.ExchangeRate;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.List;
/**
@ -45,26 +50,30 @@ import java.util.List;
*/
public class CoinbaseClient {
private static final String COINBASE_URL = "https://coinbase.com/";
private static final String COINBASE_URL = "https://coinbase.com";
private static final String BALANCE_PATH = "/api/v1/account/balance";
private static final String PAYMENT_PATH = "/api/v1/transactions/send_money";
private static final String EXCHANGE_PATH = "/api/v1/currencies/exchange_rates";
private static final String RECENT_TRANSACTIONS_PATH = "/api/v1/transactions";
private final String apiKey;
private final String apiSecret;
private final Client client;
public CoinbaseClient(String apiKey) {
this.apiKey = apiKey;
this.client = Client.create(getClientConfig());
private static final ObjectMapper objectMapper = new ObjectMapper();
public CoinbaseClient(String apiKey, String apiSecret) {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.client = Client.create(getClientConfig());
}
public List<CoinbaseTransaction> getRecentTransactions() throws IOException {
public List<CoinbaseTransaction> getRecentTransactions()
throws IOException, TransferFailedException
{
try {
return client.resource(COINBASE_URL)
.path(RECENT_TRANSACTIONS_PATH)
.queryParam("api_key", apiKey)
.get(CoinbseRecentTransactionsResponse.class).getTransactions();
return getAuthenticatedWebResource(RECENT_TRANSACTIONS_PATH, null).get(CoinbseRecentTransactionsResponse.class)
.getTransactions();
} catch (UniformInterfaceException | ClientHandlerException e) {
throw new IOException(e);
}
@ -89,23 +98,19 @@ public class CoinbaseClient {
throws TransferFailedException
{
try {
WebResource resource = client.resource(COINBASE_URL)
.path(PAYMENT_PATH)
.queryParam("api_key", apiKey);
String note = "Commit payment:\n__" + author.getUsername() + "__ " + url;
BitcoinTransaction transaction = new BitcoinTransaction(author.getEmail(),
amount.toPlainString(),
note);
boolean success = resource.type(MediaType.APPLICATION_JSON_TYPE)
.accept(MediaType.APPLICATION_JSON)
.entity(transaction)
.post(BitcoinTransactionResponse.class)
.isSuccess();
WebResource.Builder resource = getAuthenticatedWebResource(PAYMENT_PATH, transaction);
if (!success) {
BitcoinTransactionResponse response = resource.type(MediaType.APPLICATION_JSON_TYPE)
.entity(transaction)
.post(BitcoinTransactionResponse.class);
if (!response.isSuccess()) {
throw new TransferFailedException();
}
@ -114,16 +119,11 @@ public class CoinbaseClient {
}
}
public BigDecimal getAccountBalance() throws IOException {
public BigDecimal getAccountBalance() throws IOException, TransferFailedException {
try {
WebResource resource = client.resource(COINBASE_URL)
.path(BALANCE_PATH)
.queryParam("api_key", apiKey);
String amount = resource.accept(MediaType.APPLICATION_JSON)
.get(BalanceResponse.class)
WebResource.Builder resource = getAuthenticatedWebResource(BALANCE_PATH, null);
String amount = resource.get(BalanceResponse.class)
.getAmount();
if (amount == null) {
throw new IOException("Empty amount in response!");
}
@ -134,6 +134,27 @@ public class CoinbaseClient {
}
}
private WebResource.Builder getAuthenticatedWebResource(String path, Object body) throws TransferFailedException {
try {
String json = body == null ? "" : objectMapper.writeValueAsString(body);
String nonce = String.valueOf(System.currentTimeMillis());
String message = nonce + COINBASE_URL + path + json;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(apiSecret.getBytes(), "HmacSHA256"));
String signature = new String(Hex.encodeHex(mac.doFinal(message.getBytes())));
return client.resource(COINBASE_URL)
.path(path)
.accept(MediaType.APPLICATION_JSON)
.header("ACCESS_SIGNATURE", signature)
.header("ACCESS_NONCE", nonce)
.header("ACCESS_KEY", apiKey);
} catch (NoSuchAlgorithmException | InvalidKeyException | IOException e) {
throw new TransferFailedException();
}
}
private ClientConfig getClientConfig() {
ClientConfig config = new DefaultClientConfig();
config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);

View File

@ -17,13 +17,13 @@
package org.whispersystems.bithub.client;
public class TransferFailedException extends Throwable {
public class TransferFailedException extends Exception {
public TransferFailedException() {
super();
}
public TransferFailedException(RuntimeException e) {
public TransferFailedException(Throwable e) {
super(e);
}
}

View File

@ -27,7 +27,15 @@ public class CoinbaseConfiguration {
@NotEmpty
private String apiKey;
@JsonProperty
@NotEmpty
private String apiSecret;
public String getApiKey() {
return apiKey;
}
public String getApiSecret() {
return apiSecret;
}
}

View File

@ -96,7 +96,7 @@ public class GithubController {
public void handleCommits(@Auth Authentication auth,
@HeaderParam("X-Forwarded-For") String clientIp,
@FormParam("payload") String eventString)
throws IOException, UnauthorizedHookException
throws IOException, UnauthorizedHookException, TransferFailedException
{
authenticate(clientIp);
PushEvent event = getEventFromPayload(eventString);

View File

@ -22,13 +22,13 @@ import com.fasterxml.jackson.annotation.JsonProperty;
public class BitcoinTransaction {
@JsonProperty
private String to;
public String to;
@JsonProperty
private String amount;
public String amount;
@JsonProperty
private String notes;
public String notes;
public BitcoinTransaction(String to, String amount, String notes) {
this.to = to;

View File

@ -19,12 +19,19 @@ package org.whispersystems.bithub.entities;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import java.util.List;
@JsonIgnoreProperties(ignoreUnknown = true)
public class BitcoinTransactionResponse {
private boolean success;
private List<String> errors;
public boolean isSuccess() {
return success;
}
public void setErrors(List<String> errors) {
this.errors = errors;
}
}

View File

@ -4,6 +4,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.whispersystems.bithub.client.CoinbaseClient;
import org.whispersystems.bithub.client.GithubClient;
import org.whispersystems.bithub.client.TransferFailedException;
import org.whispersystems.bithub.config.RepositoryConfiguration;
import org.whispersystems.bithub.entities.CoinbaseTransaction;
import org.whispersystems.bithub.entities.Payment;
@ -94,7 +95,7 @@ public class CacheManager implements Managed {
cachedTransactions.set(transactions);
cachedRepositories.set(repositories);
} catch (IOException e) {
} catch (IOException | TransferFailedException e) {
logger.warn("Failed to update badge", e);
}
}
@ -114,7 +115,7 @@ public class CacheManager implements Managed {
}
private CurrentPayment createCurrentPaymentForBalance(CoinbaseClient coinbaseClient)
throws IOException
throws IOException, TransferFailedException
{
BigDecimal currentBalance = coinbaseClient.getAccountBalance();
BigDecimal paymentBtc = currentBalance.multiply(payoutRate);
@ -128,7 +129,7 @@ public class CacheManager implements Managed {
}
private List<Transaction> createRecentTransactions(CoinbaseClient coinbaseClient)
throws IOException
throws IOException, TransferFailedException
{
List<CoinbaseTransaction> recentTransactions = coinbaseClient.getRecentTransactions();
BigDecimal exchangeRate = coinbaseClient.getExchangeRate();

View File

@ -77,7 +77,7 @@ public class GithubControllerTest {
@Before
public void setup() throws Exception {
public void setup() throws Exception, TransferFailedException {
when(coinbaseClient.getAccountBalance()).thenReturn(BALANCE);
when(coinbaseClient.getExchangeRate()).thenReturn(EXCHANGE_RATE);
}

View File

@ -5,6 +5,7 @@ import org.junit.ClassRule;
import org.junit.Test;
import org.whispersystems.bithub.client.CoinbaseClient;
import org.whispersystems.bithub.client.GithubClient;
import org.whispersystems.bithub.client.TransferFailedException;
import org.whispersystems.bithub.config.RepositoryConfiguration;
import org.whispersystems.bithub.controllers.StatusController;
import org.whispersystems.bithub.entities.CoinbseRecentTransactionsResponse;
@ -49,7 +50,7 @@ public class StatusControllerTest {
resources = ResourceTestRule.builder()
.addResource(new StatusController(coinbaseManager, null))
.build();
} catch (Exception e) {
} catch (Exception | TransferFailedException e) {
throw new AssertionError(e);
}
}