View Javadoc

1   /**
2    *    Copyright 2012 meltmedia
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *        http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
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 }