Skip to content

Commit 20b6aeb

Browse files
Tina Huangmoxie0
authored andcommitted
Fix the CoinbaseClient to use their updated auth.
Closes signalapp#43 // FREEBIE
1 parent b15d227 commit 20b6aeb

File tree

11 files changed

+84
-42
lines changed

11 files changed

+84
-42
lines changed

config/sample.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ github:
1818

1919
coinbase:
2020
apiKey: # Your Coinbase API key.
21+
apiSecret: # Your Coinbase API secret.

src/main/java/org/whispersystems/bithub/BithubService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.whispersystems.bithub.auth.GithubWebhookAuthenticator;
2222
import org.whispersystems.bithub.client.CoinbaseClient;
2323
import org.whispersystems.bithub.client.GithubClient;
24+
import org.whispersystems.bithub.config.CoinbaseConfiguration;
2425
import org.whispersystems.bithub.config.RepositoryConfiguration;
2526
import org.whispersystems.bithub.controllers.DashboardController;
2627
import org.whispersystems.bithub.controllers.GithubController;
@@ -64,9 +65,11 @@ public void run(BithubServerConfiguration config, Environment environment)
6465
BigDecimal payoutRate = config.getBithubConfiguration().getPayoutRate();
6566
String organizationName = config.getOrganizationConfiguration().getName();
6667
String donationUrl = config.getOrganizationConfiguration().getDonationUrl().toExternalForm();
68+
String coinbaseApiKey = config.getCoinbaseConfiguration().getApiKey();
69+
String coinbaseApiSecret = config.getCoinbaseConfiguration().getApiSecret();
6770

6871
GithubClient githubClient = new GithubClient(githubUser, githubToken);
69-
CoinbaseClient coinbaseClient = new CoinbaseClient(config.getCoinbaseConfiguration().getApiKey());
72+
CoinbaseClient coinbaseClient = new CoinbaseClient(coinbaseApiKey, coinbaseApiSecret);
7073
CacheManager cacheManager = new CacheManager(coinbaseClient, githubClient, githubRepositories, payoutRate);
7174

7275
environment.servlets().addFilter("CORS", CrossOriginFilter.class)

src/main/java/org/whispersystems/bithub/client/CoinbaseClient.java

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,23 @@
2424
import com.sun.jersey.api.client.config.ClientConfig;
2525
import com.sun.jersey.api.client.config.DefaultClientConfig;
2626
import com.sun.jersey.api.json.JSONConfiguration;
27-
27+
import org.apache.commons.codec.binary.Hex;
28+
import org.codehaus.jackson.map.ObjectMapper;
2829
import org.whispersystems.bithub.entities.Author;
2930
import org.whispersystems.bithub.entities.BalanceResponse;
3031
import org.whispersystems.bithub.entities.BitcoinTransaction;
3132
import org.whispersystems.bithub.entities.BitcoinTransactionResponse;
32-
import org.whispersystems.bithub.entities.ExchangeRate;
33-
import org.whispersystems.bithub.entities.CoinbseRecentTransactionsResponse;
3433
import org.whispersystems.bithub.entities.CoinbaseTransaction;
34+
import org.whispersystems.bithub.entities.CoinbseRecentTransactionsResponse;
35+
import org.whispersystems.bithub.entities.ExchangeRate;
3536

37+
import javax.crypto.Mac;
38+
import javax.crypto.spec.SecretKeySpec;
3639
import javax.ws.rs.core.MediaType;
3740
import java.io.IOException;
3841
import java.math.BigDecimal;
42+
import java.security.InvalidKeyException;
43+
import java.security.NoSuchAlgorithmException;
3944
import java.util.List;
4045

4146
/**
@@ -45,26 +50,30 @@
4550
*/
4651
public class CoinbaseClient {
4752

48-
private static final String COINBASE_URL = "https://coinbase.com/";
53+
private static final String COINBASE_URL = "https://coinbase.com";
4954
private static final String BALANCE_PATH = "/api/v1/account/balance";
5055
private static final String PAYMENT_PATH = "/api/v1/transactions/send_money";
5156
private static final String EXCHANGE_PATH = "/api/v1/currencies/exchange_rates";
5257
private static final String RECENT_TRANSACTIONS_PATH = "/api/v1/transactions";
5358

5459
private final String apiKey;
60+
private final String apiSecret;
5561
private final Client client;
5662

57-
public CoinbaseClient(String apiKey) {
58-
this.apiKey = apiKey;
59-
this.client = Client.create(getClientConfig());
63+
private static final ObjectMapper objectMapper = new ObjectMapper();
64+
65+
public CoinbaseClient(String apiKey, String apiSecret) {
66+
this.apiKey = apiKey;
67+
this.apiSecret = apiSecret;
68+
this.client = Client.create(getClientConfig());
6069
}
6170

62-
public List<CoinbaseTransaction> getRecentTransactions() throws IOException {
71+
public List<CoinbaseTransaction> getRecentTransactions()
72+
throws IOException, TransferFailedException
73+
{
6374
try {
64-
return client.resource(COINBASE_URL)
65-
.path(RECENT_TRANSACTIONS_PATH)
66-
.queryParam("api_key", apiKey)
67-
.get(CoinbseRecentTransactionsResponse.class).getTransactions();
75+
return getAuthenticatedWebResource(RECENT_TRANSACTIONS_PATH, null).get(CoinbseRecentTransactionsResponse.class)
76+
.getTransactions();
6877
} catch (UniformInterfaceException | ClientHandlerException e) {
6978
throw new IOException(e);
7079
}
@@ -89,23 +98,19 @@ public void sendPayment(Author author, BigDecimal amount, String url)
8998
throws TransferFailedException
9099
{
91100
try {
92-
WebResource resource = client.resource(COINBASE_URL)
93-
.path(PAYMENT_PATH)
94-
.queryParam("api_key", apiKey);
95-
96101
String note = "Commit payment:\n__" + author.getUsername() + "__ " + url;
97102

98103
BitcoinTransaction transaction = new BitcoinTransaction(author.getEmail(),
99104
amount.toPlainString(),
100105
note);
101106

102-
boolean success = resource.type(MediaType.APPLICATION_JSON_TYPE)
103-
.accept(MediaType.APPLICATION_JSON)
104-
.entity(transaction)
105-
.post(BitcoinTransactionResponse.class)
106-
.isSuccess();
107+
WebResource.Builder resource = getAuthenticatedWebResource(PAYMENT_PATH, transaction);
108+
109+
BitcoinTransactionResponse response = resource.type(MediaType.APPLICATION_JSON_TYPE)
110+
.entity(transaction)
111+
.post(BitcoinTransactionResponse.class);
107112

108-
if (!success) {
113+
if (!response.isSuccess()) {
109114
throw new TransferFailedException();
110115
}
111116

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

117-
public BigDecimal getAccountBalance() throws IOException {
122+
public BigDecimal getAccountBalance() throws IOException, TransferFailedException {
118123
try {
119-
WebResource resource = client.resource(COINBASE_URL)
120-
.path(BALANCE_PATH)
121-
.queryParam("api_key", apiKey);
122-
123-
String amount = resource.accept(MediaType.APPLICATION_JSON)
124-
.get(BalanceResponse.class)
124+
WebResource.Builder resource = getAuthenticatedWebResource(BALANCE_PATH, null);
125+
String amount = resource.get(BalanceResponse.class)
125126
.getAmount();
126-
127127
if (amount == null) {
128128
throw new IOException("Empty amount in response!");
129129
}
@@ -134,6 +134,27 @@ public BigDecimal getAccountBalance() throws IOException {
134134
}
135135
}
136136

137+
private WebResource.Builder getAuthenticatedWebResource(String path, Object body) throws TransferFailedException {
138+
try {
139+
String json = body == null ? "" : objectMapper.writeValueAsString(body);
140+
String nonce = String.valueOf(System.currentTimeMillis());
141+
String message = nonce + COINBASE_URL + path + json;
142+
Mac mac = Mac.getInstance("HmacSHA256");
143+
mac.init(new SecretKeySpec(apiSecret.getBytes(), "HmacSHA256"));
144+
145+
String signature = new String(Hex.encodeHex(mac.doFinal(message.getBytes())));
146+
147+
return client.resource(COINBASE_URL)
148+
.path(path)
149+
.accept(MediaType.APPLICATION_JSON)
150+
.header("ACCESS_SIGNATURE", signature)
151+
.header("ACCESS_NONCE", nonce)
152+
.header("ACCESS_KEY", apiKey);
153+
} catch (NoSuchAlgorithmException | InvalidKeyException | IOException e) {
154+
throw new TransferFailedException();
155+
}
156+
}
157+
137158
private ClientConfig getClientConfig() {
138159
ClientConfig config = new DefaultClientConfig();
139160
config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);

src/main/java/org/whispersystems/bithub/client/TransferFailedException.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@
1717

1818
package org.whispersystems.bithub.client;
1919

20-
public class TransferFailedException extends Throwable {
20+
public class TransferFailedException extends Exception {
2121

2222
public TransferFailedException() {
2323
super();
2424
}
2525

26-
public TransferFailedException(RuntimeException e) {
26+
public TransferFailedException(Throwable e) {
2727
super(e);
2828
}
2929
}

src/main/java/org/whispersystems/bithub/config/CoinbaseConfiguration.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,15 @@ public class CoinbaseConfiguration {
2727
@NotEmpty
2828
private String apiKey;
2929

30+
@JsonProperty
31+
@NotEmpty
32+
private String apiSecret;
33+
3034
public String getApiKey() {
3135
return apiKey;
3236
}
37+
38+
public String getApiSecret() {
39+
return apiSecret;
40+
}
3341
}

src/main/java/org/whispersystems/bithub/controllers/GithubController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public GithubController(List<RepositoryConfiguration> repositories,
9696
public void handleCommits(@Auth Authentication auth,
9797
@HeaderParam("X-Forwarded-For") String clientIp,
9898
@FormParam("payload") String eventString)
99-
throws IOException, UnauthorizedHookException
99+
throws IOException, UnauthorizedHookException, TransferFailedException
100100
{
101101
authenticate(clientIp);
102102
PushEvent event = getEventFromPayload(eventString);

src/main/java/org/whispersystems/bithub/entities/BitcoinTransaction.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@
2222
public class BitcoinTransaction {
2323

2424
@JsonProperty
25-
private String to;
25+
public String to;
2626

2727
@JsonProperty
28-
private String amount;
28+
public String amount;
2929

3030
@JsonProperty
31-
private String notes;
31+
public String notes;
3232

3333
public BitcoinTransaction(String to, String amount, String notes) {
3434
this.to = to;

src/main/java/org/whispersystems/bithub/entities/BitcoinTransactionResponse.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,19 @@
1919

2020
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
2121

22+
import java.util.List;
23+
2224
@JsonIgnoreProperties(ignoreUnknown = true)
2325
public class BitcoinTransactionResponse {
2426

2527
private boolean success;
28+
private List<String> errors;
2629

2730
public boolean isSuccess() {
2831
return success;
2932
}
33+
34+
public void setErrors(List<String> errors) {
35+
this.errors = errors;
36+
}
3037
}

src/main/java/org/whispersystems/bithub/storage/CacheManager.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.slf4j.LoggerFactory;
55
import org.whispersystems.bithub.client.CoinbaseClient;
66
import org.whispersystems.bithub.client.GithubClient;
7+
import org.whispersystems.bithub.client.TransferFailedException;
78
import org.whispersystems.bithub.config.RepositoryConfiguration;
89
import org.whispersystems.bithub.entities.CoinbaseTransaction;
910
import org.whispersystems.bithub.entities.Payment;
@@ -94,7 +95,7 @@ public void run() {
9495
cachedTransactions.set(transactions);
9596
cachedRepositories.set(repositories);
9697

97-
} catch (IOException e) {
98+
} catch (IOException | TransferFailedException e) {
9899
logger.warn("Failed to update badge", e);
99100
}
100101
}
@@ -114,7 +115,7 @@ private List<Repository> createRepositories(GithubClient githubClient,
114115
}
115116

116117
private CurrentPayment createCurrentPaymentForBalance(CoinbaseClient coinbaseClient)
117-
throws IOException
118+
throws IOException, TransferFailedException
118119
{
119120
BigDecimal currentBalance = coinbaseClient.getAccountBalance();
120121
BigDecimal paymentBtc = currentBalance.multiply(payoutRate);
@@ -128,7 +129,7 @@ private CurrentPayment createCurrentPaymentForBalance(CoinbaseClient coinbaseCli
128129
}
129130

130131
private List<Transaction> createRecentTransactions(CoinbaseClient coinbaseClient)
131-
throws IOException
132+
throws IOException, TransferFailedException
132133
{
133134
List<CoinbaseTransaction> recentTransactions = coinbaseClient.getRecentTransactions();
134135
BigDecimal exchangeRate = coinbaseClient.getExchangeRate();

src/test/java/org/whispersystems/bithub/tests/controllers/GithubControllerTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ public class GithubControllerTest {
7777

7878

7979
@Before
80-
public void setup() throws Exception {
80+
public void setup() throws Exception, TransferFailedException {
8181
when(coinbaseClient.getAccountBalance()).thenReturn(BALANCE);
8282
when(coinbaseClient.getExchangeRate()).thenReturn(EXCHANGE_RATE);
8383
}

0 commit comments

Comments
 (0)