1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package com.meltmedia.cadmium.core.github;
17
18 import java.io.File;
19 import java.util.ArrayList;
20 import java.util.List;
21 import java.util.Map;
22
23 import org.apache.http.HttpMessage;
24 import org.apache.http.HttpResponse;
25 import org.apache.http.HttpStatus;
26 import org.apache.http.client.HttpClient;
27 import org.apache.http.client.methods.HttpDelete;
28 import org.apache.http.client.methods.HttpGet;
29 import org.apache.http.client.methods.HttpPost;
30 import org.apache.http.entity.StringEntity;
31 import org.apache.http.impl.client.DefaultHttpClient;
32 import org.apache.http.util.EntityUtils;
33 import org.eclipse.jgit.util.Base64;
34 import org.slf4j.Logger;
35 import org.slf4j.LoggerFactory;
36
37 import com.google.gson.Gson;
38 import com.google.gson.reflect.TypeToken;
39 import com.meltmedia.cadmium.core.FileSystemManager;
40
41 public class ApiClient {
42 private static final Logger log = LoggerFactory.getLogger(ApiClient.class);
43 private String token;
44
45 public ApiClient(String token) throws Exception {
46 this.token = token;
47
48 String username = getUserName();
49 if(username == null) {
50 throw new Exception("Token has been revoked.");
51 }
52 }
53
54 public ApiClient() throws Exception {
55 String cadmiumToken = getToken();
56
57 if(cadmiumToken != null) {
58 this.token = cadmiumToken;
59
60 String username = getUserName();
61 if(username == null) {
62 throw new Exception("Token has been revoked.");
63 }
64 } else {
65 throw new Exception("No token for user has been found.");
66 }
67 }
68
69 public static String getToken() throws Exception {
70 String cadmiumToken = System.getProperty("user.home");
71 if(cadmiumToken != null) {
72 cadmiumToken = FileSystemManager.getChildDirectoryIfExists(cadmiumToken, ".cadmium");
73 if(cadmiumToken != null) {
74 cadmiumToken = FileSystemManager.getFileIfCanRead(cadmiumToken, "github.token");
75 if(cadmiumToken != null) {
76 cadmiumToken = FileSystemManager.getFileContents(cadmiumToken);
77 }
78 }
79 }
80 return cadmiumToken;
81 }
82
83 public static Authorization authorize(String username, String password, List<String> scopes) throws Exception {
84 int limitRemain = getRateLimitRemain(null);
85 if(limitRemain > 0) {
86 DefaultHttpClient client = new DefaultHttpClient();
87
88 HttpPost post = new HttpPost("https://api.github.com/authorizations");
89 setupBasicAuth(username, password, post);
90 AuthBody body = new AuthBody();
91 if(scopes != null) {
92 body.scopes = scopes.toArray(new String[] {});
93 }
94 String bodySt = new Gson().toJson(body, AuthBody.class);
95 log.debug("Loggin in with post body [{}]", bodySt);
96 StringEntity postEntity = new StringEntity(bodySt);
97 post.setEntity(postEntity);
98
99 HttpResponse response = client.execute(post);
100 if(response.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED) {
101 Authorization auth = new Gson().fromJson(EntityUtils.toString(response.getEntity()), Authorization.class);
102 return auth;
103 } else {
104 String errResponse = EntityUtils.toString(response.getEntity());
105 log.warn("Github auth failed: {}", errResponse);
106 throw new Exception(errResponse);
107 }
108 } else {
109 throw new Exception("Request is rate limited.");
110 }
111 }
112
113 public static void authorizeAndCreateTokenFile(String username, String password, List<String> scopes) throws Exception {
114 Authorization auth = authorize(username, password, scopes);
115 if(auth != null && auth.getToken() != null) {
116 String cadmiumToken = System.getProperty("user.home");
117 if(cadmiumToken != null) {
118 File cadmiumDir = new File(cadmiumToken, ".cadmium").getAbsoluteFile();
119 if(cadmiumDir.exists() || cadmiumDir.mkdirs()) {
120 FileSystemManager.writeStringToFile(cadmiumDir.getAbsolutePath(), "github.token", auth.getToken());
121 }
122 }
123 }
124 }
125
126 public static List<Long> getAuthorizationIds(String username, String password) throws Exception {
127 int limitRemain = getRateLimitRemain(null);
128 List<Long> authIds = new ArrayList<Long>();
129 if(limitRemain > 0) {
130 DefaultHttpClient client = new DefaultHttpClient();
131
132 HttpGet get = new HttpGet("https://api.github.com/authorizations");
133 setupBasicAuth(username, password, get);
134
135 HttpResponse response = client.execute(get);
136 if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
137 List<Map<String, Object>> auths = new Gson().fromJson(EntityUtils.toString(response.getEntity()), new TypeToken<List<Map<String, Object>>>() {}.getType());
138 if(auths != null && auths.size() > 0) {
139 for(Map<String, Object> auth : auths) {
140 if(auth != null && auth.containsKey("id")) {
141 Double id = (Double)auth.get("id");
142 authIds.add(id.longValue());
143 }
144 }
145 }
146 }
147 } else {
148 throw new Exception("Request is rate limited.");
149 }
150 return authIds;
151 }
152
153 private static void setupBasicAuth(String username, String password, HttpMessage message) {
154 message.addHeader("Authorization", "Basic "+Base64.encodeBytes(new String(username+":"+password).getBytes()));
155 }
156
157 public void deauthorizeToken(String username, String password, long authId) throws Exception {
158 int limitRemain = getRateLimitRemain();
159 if(limitRemain > 0) {
160 DefaultHttpClient client = new DefaultHttpClient();
161
162 HttpDelete delete = new HttpDelete("https://api.github.com/authorizations/"+authId);
163 setupBasicAuth(username, password, delete);
164
165 HttpResponse response = client.execute(delete);
166 if(response.getStatusLine().getStatusCode() != HttpStatus.SC_NO_CONTENT) {
167 throw new Exception("Failed to deauthorize token "+token);
168 }
169 } else {
170 throw new Exception("Request is rate limited.");
171 }
172 }
173
174 public boolean isTeamMember(String teamId) throws Exception {
175 int limitRemain = getRateLimitRemain();
176 if(limitRemain > 0) {
177 HttpClient client = new DefaultHttpClient();
178
179 HttpGet get = new HttpGet("https://api.github.com/teams/"+teamId);
180 addAuthHeader(get);
181
182 HttpResponse response = client.execute(get);
183
184 if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
185 return true;
186 }
187 } else {
188 throw new Exception("Request is rate limited.");
189 }
190 return false;
191 }
192
193 public String getUserName() throws Exception {
194 int limitRemain = getRateLimitRemain();
195
196 if(limitRemain > 0) {
197 HttpClient client = new DefaultHttpClient();
198
199 HttpGet get = new HttpGet("https://api.github.com/user");
200 addAuthHeader(get);
201
202 HttpResponse response = client.execute(get);
203 if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
204 String responseString = EntityUtils.toString(response.getEntity());
205
206 Map<String, Object> responseObj = new Gson().fromJson(responseString, new TypeToken<Map<String, Object>>() {}.getType());
207
208 if(responseObj.containsKey("login")) {
209 return (String) responseObj.get("login");
210 } else if(responseObj.containsKey("message")) {
211 throw new Exception((String) responseObj.get("message"));
212 }
213 } else if(response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
214 throw new Exception("Unauthorized");
215 }
216 } else {
217 throw new Exception("Request is rate limited.");
218 }
219 return null;
220 }
221
222 public int getRateLimitRemain() throws Exception {
223 return getRateLimitRemain(token);
224 }
225
226 public static int getRateLimitRemain(String token) throws Exception {
227 HttpClient client = new DefaultHttpClient();
228
229 HttpGet get = new HttpGet("https://api.github.com/rate_limit");
230 addAuthHeader(get, token);
231
232 HttpResponse response = client.execute(get);
233 if(response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
234 String responseString = EntityUtils.toString(response.getEntity());
235
236 RateLimitResponse responseObj = new Gson().fromJson(responseString, new TypeToken<RateLimitResponse>() {}.getType());
237 if(responseObj.rate != null) {
238 if(responseObj.rate.remaining != null) {
239 log.info("The remaining rate limit is {}", responseObj.rate.remaining);
240 return responseObj.rate.remaining;
241 }
242 }
243 } else if(response.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED){
244 throw new Exception("Unauthorized!");
245 }
246 return -1;
247 }
248
249 public long commentOnCommit(String repoUri, String sha, String comment) throws Exception {
250 String orgRepo = getOrgRepo(repoUri);
251
252 HttpClient client = new DefaultHttpClient();
253
254 HttpPost post = new HttpPost("https://api.github.com/repos/" + orgRepo + "/commits/" + sha + "/comments");
255 addAuthHeader(post);
256
257 Comment commentBody = new Comment();
258 commentBody.body = comment;
259
260 String commentJsonString = new Gson().toJson(commentBody, Comment.class);
261 log.info("Setting a new comment [{}]", commentJsonString);
262 post.setEntity(new StringEntity(commentJsonString));
263
264 HttpResponse response = client.execute(post);
265
266 if(response.getStatusLine().getStatusCode() == HttpStatus.SC_CREATED) {
267 String resp = EntityUtils.toString(response.getEntity());
268 IdExtractor id = new Gson().fromJson(resp, IdExtractor.class);
269 return id.id;
270 } else {
271 throw new Exception("Failed to create new comment of commit ["+orgRepo+":"+sha+"]: "+response.getStatusLine().toString());
272 }
273 }
274
275 public static String getOrgRepo(String repoUri) throws Exception {
276 repoUri = repoUri.replace(".git", "");
277 repoUri = repoUri.split(":")[1];
278 String orgName = null;
279 String repoName = null;
280 String splitRepo[] = repoUri.split("/");
281 for(int i=0; i<splitRepo.length; i++) {
282 if(i > 0) {
283 if(splitRepo[i-1].trim().length() > 0 && splitRepo[i].trim().length() > 0) {
284 orgName = splitRepo[i-1];
285 repoName = splitRepo[i];
286 }
287 }
288 }
289 if(orgName == null || repoName == null) {
290 throw new Exception("Invalid repo uri");
291 }
292 return orgName + "/" + repoName;
293 }
294
295 private void addAuthHeader(HttpMessage message) {
296 addAuthHeader(message, token);
297 }
298
299 private static void addAuthHeader(HttpMessage message, String token) {
300 if(token != null) {
301 message.addHeader("Authorization", "token "+token);
302 }
303 }
304
305 private static class IdExtractor {
306 Long id;
307 }
308
309 private static class Comment {
310 @SuppressWarnings("unused")
311 String body;
312 }
313
314 private static class Rate {
315 Integer remaining;
316 }
317
318 private static class RateLimitResponse {
319 Rate rate;
320 }
321
322 private static class AuthBody {
323 @SuppressWarnings("unused")
324 String scopes[];
325 }
326
327 public static class Authorization {
328 protected Long id;
329 protected String token;
330 protected String scopes[];
331 public Long getId() {
332 return id;
333 }
334 public void setId(Long id) {
335 this.id = id;
336 }
337 public String getToken() {
338 return token;
339 }
340 public void setToken(String token) {
341 this.token = token;
342 }
343 public String[] getScopes() {
344 return scopes;
345 }
346 public void setScopes(String[] scopes) {
347 this.scopes = scopes;
348 }
349 }
350
351 }