Skip to content
This repository has been archived by the owner on Feb 3, 2022. It is now read-only.

Commit

Permalink
Fix the CoinbaseClient to use their updated auth.
Browse files Browse the repository at this point in the history
Closes #43

// FREEBIE
  • Loading branch information
Tina Huang authored and moxie0 committed Apr 6, 2015
1 parent b15d227 commit 20b6aeb
Show file tree
Hide file tree
Showing 11 changed files with 84 additions and 42 deletions.
1 change: 1 addition & 0 deletions config/sample.yml
Expand Up @@ -18,3 +18,4 @@ github:

coinbase:
apiKey: # Your Coinbase API key.
apiSecret: # Your Coinbase API secret.
5 changes: 4 additions & 1 deletion src/main/java/org/whispersystems/bithub/BithubService.java
Expand Up @@ -21,6 +21,7 @@
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;
Expand Down Expand Up @@ -64,9 +65,11 @@ public void run(BithubServerConfiguration config, Environment environment)
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)
Expand Down
81 changes: 51 additions & 30 deletions src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java
Expand Up @@ -24,18 +24,23 @@
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;

/**
Expand All @@ -45,26 +50,30 @@
*/
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);
}
Expand All @@ -89,23 +98,19 @@ public void sendPayment(Author author, BigDecimal amount, String url)
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);

BitcoinTransactionResponse response = resource.type(MediaType.APPLICATION_JSON_TYPE)
.entity(transaction)
.post(BitcoinTransactionResponse.class);

if (!success) {
if (!response.isSuccess()) {
throw new TransferFailedException();
}

Expand All @@ -114,16 +119,11 @@ public void sendPayment(Author author, BigDecimal amount, String url)
}
}

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!");
}
Expand All @@ -134,6 +134,27 @@ public BigDecimal getAccountBalance() throws IOException {
}
}

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);
Expand Down
Expand Up @@ -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);
}
}
Expand Up @@ -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;
}
}
Expand Up @@ -96,7 +96,7 @@ public GithubController(List<RepositoryConfiguration> repositories,
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);
Expand Down
Expand Up @@ -22,13 +22,13 @@
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;
Expand Down
Expand Up @@ -19,12 +19,19 @@

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;
}
}
Expand Up @@ -4,6 +4,7 @@
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;
Expand Down Expand Up @@ -94,7 +95,7 @@ public void run() {
cachedTransactions.set(transactions);
cachedRepositories.set(repositories);

} catch (IOException e) {
} catch (IOException | TransferFailedException e) {
logger.warn("Failed to update badge", e);
}
}
Expand All @@ -114,7 +115,7 @@ private List<Repository> createRepositories(GithubClient githubClient,
}

private CurrentPayment createCurrentPaymentForBalance(CoinbaseClient coinbaseClient)
throws IOException
throws IOException, TransferFailedException
{
BigDecimal currentBalance = coinbaseClient.getAccountBalance();
BigDecimal paymentBtc = currentBalance.multiply(payoutRate);
Expand All @@ -128,7 +129,7 @@ private CurrentPayment createCurrentPaymentForBalance(CoinbaseClient coinbaseCli
}

private List<Transaction> createRecentTransactions(CoinbaseClient coinbaseClient)
throws IOException
throws IOException, TransferFailedException
{
List<CoinbaseTransaction> recentTransactions = coinbaseClient.getRecentTransactions();
BigDecimal exchangeRate = coinbaseClient.getExchangeRate();
Expand Down
Expand Up @@ -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);
}
Expand Down
Expand Up @@ -5,6 +5,7 @@
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;
Expand Down Expand Up @@ -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);
}
}
Expand Down

0 comments on commit 20b6aeb

Please sign in to comment.