forked from npshub/mantid
-
Notifications
You must be signed in to change notification settings - Fork 0
/
GitHubApiHelper.cpp
205 lines (173 loc) · 6.88 KB
/
GitHubApiHelper.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
// NScD Oak Ridge National Laboratory, European Spallation Source,
// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
// SPDX - License - Identifier: GPL - 3.0 +
#include "MantidKernel/GitHubApiHelper.h"
#include "MantidJson/Json.h"
#include "MantidKernel/ConfigService.h"
#include "MantidKernel/DateAndTime.h"
#include "MantidKernel/Logger.h"
#include <Poco/Net/HTTPClientSession.h>
#include <Poco/Net/HTTPRequest.h>
#include <Poco/Net/HTTPResponse.h>
#include <Poco/StreamCopier.h>
#include <Poco/URI.h>
#include <boost/lexical_cast.hpp>
#include <json/json.h>
#include <map>
#include <ostream>
#include <string>
namespace Mantid {
using namespace Types::Core;
namespace Kernel {
// Forward declare
class ProxyInfo;
using namespace Poco::Net;
using std::string;
namespace {
// anonymous namespace for some utility functions
/// static Logger object
Logger g_log("GitHubApiHelper");
const std::string RATE_LIMIT_URL("https://api.github.com/rate_limit");
// key to retreive api token from ConfigService
const std::string CONFIG_KEY_GITHUB_TOKEN("network.github.api_token");
std::string formatRateLimit(const int rateLimit, const int remaining, const int expires) {
DateAndTime expiresDateAndTime;
expiresDateAndTime.set_from_time_t(expires);
std::stringstream msg;
msg << "GitHub API limited to " << remaining << " of " << rateLimit << " calls left. Resets at "
<< expiresDateAndTime.toISO8601String() << "Z";
return msg.str();
}
/*
* Small function to encapsulate getting the token from everything else
*/
std::string getApiToken() {
// default token is empty string meaning do unauthenticated calls
std::string token(DEFAULT_GITHUB_TOKEN);
// get the token from configservice if it has been set
if (ConfigService::Instance().hasProperty(CONFIG_KEY_GITHUB_TOKEN)) {
token = ConfigService::Instance().getString(CONFIG_KEY_GITHUB_TOKEN);
}
// unset is the user's way of intentionally turning of authentication
if (token.empty() || token == "unset") {
token = "";
} else {
// error check that token is possibly valid - 40 char
// TODO example: 8ec7afc857540ee60af78cba1cf7779a6ed0b6b9
if (token.size() != 40) {
g_log.notice() << "GitHub API token is not 40 characters (found " << token.size() << ") with token =\"" << token
<< "\" using unauthenticated connection\n";
token = "";
}
}
// log what the token is and create final string to set in header
if (token.empty()) {
// only unauthenticated calls
g_log.information("Making unauthenticated calls to GitHub");
return "";
} else {
g_log.information("Attempting authenticated calls to GitHub");
// create full header using token
std::stringstream token_header;
token_header << "token " << token;
return token_header.str();
}
return token;
}
} // namespace
//----------------------------------------------------------------------------------------------
/** Constructor
*/
GitHubApiHelper::GitHubApiHelper() : InternetHelper() {
// set up the api token so it can be quickly added to the authentication
m_api_token = getApiToken();
addAuthenticationToken();
}
//----------------------------------------------------------------------------------------------
/** Constructor
*/
GitHubApiHelper::GitHubApiHelper(const Kernel::ProxyInfo &proxy) : InternetHelper(proxy) {
// set up the api token so it can be quickly added to the authentication
m_api_token = getApiToken();
addAuthenticationToken();
}
void GitHubApiHelper::reset() {
InternetHelper::reset();
addAuthenticationToken();
}
void GitHubApiHelper::addAuthenticationToken() {
// only add the token if it has been set
if (!m_api_token.empty()) {
addHeader("Authorization", m_api_token);
}
}
bool GitHubApiHelper::isAuthenticated() { return (m_headers.find("Authorization") != m_headers.end()); }
void GitHubApiHelper::processResponseHeaders(const Poco::Net::HTTPResponse &res) {
// get github api rate limit information if available;
int rateLimitRemaining = 0;
int rateLimitLimit;
int rateLimitReset;
try {
rateLimitLimit = boost::lexical_cast<int>(res.get("X-RateLimit-Limit", "-1"));
rateLimitRemaining = boost::lexical_cast<int>(res.get("X-RateLimit-Remaining", "-1"));
rateLimitReset = boost::lexical_cast<int>(res.get("X-RateLimit-Reset", "0"));
} catch (boost::bad_lexical_cast const &) {
rateLimitLimit = -1;
}
if (rateLimitLimit > -1) {
g_log.debug(formatRateLimit(rateLimitLimit, rateLimitRemaining, rateLimitReset));
}
}
std::string GitHubApiHelper::getRateLimitDescription() {
std::stringstream responseStream;
this->sendRequest(RATE_LIMIT_URL, responseStream);
auto responseString = responseStream.str();
Json::Value root;
if (!Mantid::JsonHelpers::parse(responseString, &root, NULL)) {
return "Failed to parse json document from \"" + RATE_LIMIT_URL + "\"";
}
const auto &rateInfo = root.get("rate", "");
if (rateInfo.empty())
return std::string();
const int limit = rateInfo.get("limit", -1).asInt();
const int remaining = rateInfo.get("remaining", -1).asInt();
const int expires = rateInfo.get("reset", 0).asInt();
return formatRateLimit(limit, remaining, expires);
}
int GitHubApiHelper::processAnonymousRequest(Poco::URI &uri, std::ostream &responseStream) {
g_log.debug("Repeating API call anonymously\n");
removeHeader("Authorization");
m_api_token = ""; // all future calls are anonymous
return this->sendRequest(uri.toString(), responseStream);
}
int GitHubApiHelper::sendRequestAndProcess(HTTPClientSession &session, Poco::URI &uri, std::ostream &responseStream) {
// create a request
this->createRequest(uri);
session.sendRequest(*m_request) << m_body;
std::istream &rs = session.receiveResponse(*m_response);
int retStatus = m_response->getStatus();
g_log.debug() << "Answer from web: " << retStatus << " " << m_response->getReason() << "\n";
if (retStatus == HTTP_OK || (retStatus == HTTP_CREATED && m_method == HTTPRequest::HTTP_POST)) {
Poco::StreamCopier::copyStream(rs, responseStream);
if (m_response)
processResponseHeaders(*m_response);
else
g_log.warning("Response is null pointer");
return retStatus;
} else if ((retStatus == HTTP_FORBIDDEN && isAuthenticated()) || (retStatus == HTTP_UNAUTHORIZED) ||
(retStatus == HTTP_NOT_FOUND)) {
// If authentication fails you can get HTTP_UNAUTHORIZED or HTTP_NOT_FOUND
// If the limit runs out you can get HTTP_FORBIDDEN
return this->processAnonymousRequest(uri, responseStream);
} else if (isRelocated(retStatus)) {
return this->processRelocation(*m_response, responseStream);
} else {
Poco::StreamCopier::copyStream(rs, responseStream);
return processErrorStates(*m_response, rs, uri.toString());
}
}
} // namespace Kernel
} // namespace Mantid