Mantid
Loading...
Searching...
No Matches
GitHubApiHelper.cpp
Go to the documentation of this file.
1// Mantid Repository : https://github.com/mantidproject/mantid
2//
3// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4// NScD Oak Ridge National Laboratory, European Spallation Source,
5// Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6// SPDX - License - Identifier: GPL - 3.0 +
8#include "MantidJson/Json.h"
11#include "MantidKernel/Logger.h"
12#include <Poco/Net/HTTPClientSession.h>
13#include <Poco/Net/HTTPRequest.h>
14#include <Poco/Net/HTTPResponse.h>
15#include <Poco/StreamCopier.h>
16#include <Poco/URI.h>
17
18#include <boost/algorithm/string/predicate.hpp>
19#include <boost/lexical_cast.hpp>
20#include <json/json.h>
21#include <map>
22#include <ostream>
23#include <string>
24
25namespace Mantid {
26using namespace Types::Core;
27namespace Kernel {
28
29// Forward declare
30class ProxyInfo;
31
32using namespace Poco::Net;
33using std::string;
34
35namespace {
36// anonymous namespace for some utility functions
38Logger g_log("GitHubApiHelper");
39
40const std::string RATE_LIMIT_URL("https://api.github.com/rate_limit");
41
42// key to retreive api token from ConfigService
43const std::string CONFIG_KEY_GITHUB_TOKEN("network.github.api_token");
44
45std::string formatRateLimit(const int rateLimit, const int remaining, const int expires) {
46 DateAndTime expiresDateAndTime;
47 expiresDateAndTime.set_from_time_t(expires);
48
49 std::stringstream msg;
50 msg << "GitHub API limited to " << remaining << " of " << rateLimit << " calls left. Resets at "
51 << expiresDateAndTime.toISO8601String() << "Z";
52 return msg.str();
53}
54
55/*
56 * Small function to encapsulate getting the token from everything else
57 */
58std::string getApiToken() {
59 // default token is empty string meaning do unauthenticated calls
60 std::string token(DEFAULT_GITHUB_TOKEN);
61 // get the token from configservice if it has been set
62 if (ConfigService::Instance().hasProperty(CONFIG_KEY_GITHUB_TOKEN)) {
63 token = ConfigService::Instance().getString(CONFIG_KEY_GITHUB_TOKEN);
64 }
65
66 // unset is the user's way of intentionally turning of authentication
67 if (token.empty() || boost::istarts_with(token, "unset")) {
68 token = "";
69 } else {
70 // error check that token is possibly valid - 40 char
71 // TODO example: 8ec7afc857540ee60af78cba1cf7779a6ed0b6b9
72 if (token.size() != 40) {
73 g_log.notice() << "GitHub API token is not 40 characters (found " << token.size() << ") with token =\"" << token
74 << "\" using unauthenticated connection\n";
75 token = "";
76 }
77 }
78
79 // log what the token is and create final string to set in header
80 if (token.empty()) {
81 // only unauthenticated calls
82 g_log.information("Making unauthenticated calls to GitHub");
83 return "";
84 } else {
85 g_log.information("Attempting authenticated calls to GitHub");
86
87 // create full header using token
88 std::stringstream token_header;
89 token_header << "token " << token;
90 return token_header.str();
91 }
92
93 return token;
94}
95} // namespace
96
97//----------------------------------------------------------------------------------------------
101
102//----------------------------------------------------------------------------------------------
105GitHubApiHelper::GitHubApiHelper(const Kernel::ProxyInfo &proxy) : InternetHelper(proxy), m_api_token(getApiToken()) {
107}
108
112}
113
115 // only add the token if it has been set
116 if (!m_api_token.empty()) {
117 addHeader("Authorization", m_api_token);
118 }
119}
120
121bool GitHubApiHelper::isAuthenticated() { return (m_headers.find("Authorization") != m_headers.end()); }
122
123void GitHubApiHelper::processResponseHeaders(const HTTPResponse &res) {
124 // get github api rate limit information if available;
125 int rateLimitRemaining = 0;
126 int rateLimitLimit;
127 int rateLimitReset;
128 try {
129 rateLimitLimit = boost::lexical_cast<int>(res.get("X-RateLimit-Limit", "-1"));
130 rateLimitRemaining = boost::lexical_cast<int>(res.get("X-RateLimit-Remaining", "-1"));
131 rateLimitReset = boost::lexical_cast<int>(res.get("X-RateLimit-Reset", "0"));
132 } catch (boost::bad_lexical_cast const &) {
133 rateLimitLimit = -1;
134 }
135 if (rateLimitLimit > -1) {
136 g_log.debug(formatRateLimit(rateLimitLimit, rateLimitRemaining, rateLimitReset));
137 }
138}
139
141 std::stringstream responseStream;
142 this->sendRequest(RATE_LIMIT_URL, responseStream);
143 auto responseString = responseStream.str();
144
145 Json::Value root;
146 if (!Mantid::JsonHelpers::parse(responseString, &root, NULL)) {
147 return "Failed to parse json document from \"" + RATE_LIMIT_URL + "\"";
148 }
149
150 const auto &rateInfo = root.get("rate", "");
151 if (rateInfo.empty())
152 return std::string();
153
154 const int limit = rateInfo.get("limit", -1).asInt();
155 const int remaining = rateInfo.get("remaining", -1).asInt();
156 const int expires = rateInfo.get("reset", 0).asInt();
157
158 return formatRateLimit(limit, remaining, expires);
159}
160
162 std::ostream &responseStream) {
163 g_log.debug("Repeating API call anonymously\n");
164 removeHeader("Authorization");
165 m_api_token = ""; // all future calls are anonymous
166 return this->sendRequest(uri.toString(), responseStream);
167}
168
170 std::ostream &responseStream) {
171 // create a request
172 this->createRequest(uri);
173 session.sendRequest(*m_request) << m_body;
174
175 std::istream &rs = session.receiveResponse(*m_response);
176 const auto retStatus = static_cast<HTTPStatus>(m_response->getStatus());
177 g_log.debug() << "Answer from web: " << static_cast<int>(retStatus) << " " << m_response->getReason() << "\n";
178
179 if (retStatus == HTTPStatus::OK || (retStatus == HTTPStatus::CREATED && m_method == HTTPRequest::HTTP_POST)) {
180 Poco::StreamCopier::copyStream(rs, responseStream);
181 if (m_response)
183 else
184 g_log.warning("Response is null pointer");
185 return retStatus;
186 } else if ((retStatus == HTTPStatus::FORBIDDEN && isAuthenticated()) || (retStatus == HTTPStatus::UNAUTHORIZED) ||
187 (retStatus == HTTPStatus::NOT_FOUND)) {
188 // If authentication fails you can get HTTP_UNAUTHORIZED or HTTP_NOT_FOUND
189 // If the limit runs out you can get HTTP_FORBIDDEN
190 return this->processAnonymousRequest(uri, responseStream);
191 } else if (isRelocated(retStatus)) {
192 return static_cast<InternetHelper::HTTPStatus>(this->processRelocation(*m_response, responseStream));
193 } else {
194 Poco::StreamCopier::copyStream(rs, responseStream);
195 return processErrorStates(*m_response, rs, uri.toString());
196 }
197}
198
199} // namespace Kernel
200} // namespace Mantid
virtual InternetHelper::HTTPStatus sendRequestAndProcess(Poco::Net::HTTPClientSession &session, Poco::URI &uri, std::ostream &responseStream) override
std::string getRateLimitDescription()
String describing the rate limit status.
void reset() override
Resets properties to defaults (except the proxy)
InternetHelper::HTTPStatus processAnonymousRequest(Poco::URI &uri, std::ostream &responseStream)
virtual void processResponseHeaders(const Poco::Net::HTTPResponse &res) override
Process any headers from the response stream Basic implementation does nothing.
std::string m_api_token
API token for github access.
InternetHelper : A helper class for supporting access to resources through HTTP and HTTPS.
void createRequest(Poco::URI &uri)
InternetHelper::HTTPStatus processRelocation(const Poco::Net::HTTPResponse &response, std::ostream &responseStream)
bool isRelocated(const HTTPStatus &response)
Checks the HTTP status to decide if this is a relocation.
std::unique_ptr< Poco::Net::HTTPRequest > m_request
void addHeader(const std::string &key, const std::string &value)
Adds a header.
virtual HTTPStatus processErrorStates(const Poco::Net::HTTPResponse &res, std::istream &rs, const std::string &url)
Process any HTTP errors states.
virtual HTTPStatus sendRequest(const std::string &url, std::ostream &responseStream)
Performs a request using http or https depending on the url.
void removeHeader(const std::string &key)
Removes a header.
virtual void reset()
Resets properties to defaults (except the proxy)
std::unique_ptr< Poco::Net::HTTPResponse > m_response
void debug(const std::string &msg)
Logs at debug level.
Definition: Logger.cpp:114
void notice(const std::string &msg)
Logs at notice level.
Definition: Logger.cpp:95
void warning(const std::string &msg)
Logs at warning level.
Definition: Logger.cpp:86
void information(const std::string &msg)
Logs at information level.
Definition: Logger.cpp:105
ProxyInfo : Container for carrying around network proxy information.
Definition: ProxyInfo.h:17
static T & Instance()
Return a reference to the Singleton instance, creating it if it does not already exist Creation is do...
Helper class which provides the Collimation Length for SANS instruments.