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 }
70
71 // log what the token is and create final string to set in header
72 if (token.empty()) {
73 // only unauthenticated calls
74 g_log.information("Making unauthenticated calls to GitHub");
75 return "";
76 } else {
77 g_log.information("Attempting authenticated calls to GitHub");
78
79 // create full header using token
80 std::stringstream token_header;
81 token_header << "token " << token;
82 return token_header.str();
83 }
84
85 return token;
86}
87} // namespace
88
89//----------------------------------------------------------------------------------------------
93
94//----------------------------------------------------------------------------------------------
97GitHubApiHelper::GitHubApiHelper(const Kernel::ProxyInfo &proxy) : InternetHelper(proxy), m_api_token(getApiToken()) {
99}
100
105
107 // only add the token if it has been set
108 if (!m_api_token.empty()) {
109 addHeader("Authorization", m_api_token);
110 }
111}
112
113bool GitHubApiHelper::isAuthenticated() { return (m_headers.find("Authorization") != m_headers.end()); }
114
115void GitHubApiHelper::processResponseHeaders(const HTTPResponse &res) {
116 // get github api rate limit information if available;
117 int rateLimitRemaining = 0;
118 int rateLimitLimit;
119 int rateLimitReset;
120 try {
121 rateLimitLimit = boost::lexical_cast<int>(res.get("X-RateLimit-Limit", "-1"));
122 rateLimitRemaining = boost::lexical_cast<int>(res.get("X-RateLimit-Remaining", "-1"));
123 rateLimitReset = boost::lexical_cast<int>(res.get("X-RateLimit-Reset", "0"));
124 } catch (boost::bad_lexical_cast const &) {
125 rateLimitLimit = -1;
126 }
127 if (rateLimitLimit > -1) {
128 g_log.debug(formatRateLimit(rateLimitLimit, rateLimitRemaining, rateLimitReset));
129 }
130}
131
133 std::stringstream responseStream;
134 this->sendRequest(RATE_LIMIT_URL, responseStream);
135 auto responseString = responseStream.str();
136
137 Json::Value root;
138 if (!Mantid::JsonHelpers::parse(responseString, &root, NULL)) {
139 return "Failed to parse json document from \"" + RATE_LIMIT_URL + "\"";
140 }
141
142 const auto &rateInfo = root.get("rate", "");
143 if (rateInfo.empty())
144 return std::string();
145
146 const int limit = rateInfo.get("limit", -1).asInt();
147 const int remaining = rateInfo.get("remaining", -1).asInt();
148 const int expires = rateInfo.get("reset", 0).asInt();
149
150 return formatRateLimit(limit, remaining, expires);
151}
152
154 std::ostream &responseStream) {
155 g_log.debug("Repeating API call anonymously\n");
156 removeHeader("Authorization");
157 m_api_token = ""; // all future calls are anonymous
158 return this->sendRequest(uri.toString(), responseStream);
159}
160
162 std::ostream &responseStream) {
163 // create a request
164 this->createRequest(uri);
165 session.sendRequest(*m_request) << m_body;
166
167 std::istream &rs = session.receiveResponse(*m_response);
168 const auto retStatus = static_cast<HTTPStatus>(m_response->getStatus());
169 g_log.debug() << "Answer from web: " << static_cast<int>(retStatus) << " " << m_response->getReason() << "\n";
170
171 if (retStatus == HTTPStatus::OK || (retStatus == HTTPStatus::CREATED && m_method == HTTPRequest::HTTP_POST)) {
172 Poco::StreamCopier::copyStream(rs, responseStream);
173 if (m_response)
175 else
176 g_log.warning("Response is null pointer");
177 return retStatus;
178 } else if ((retStatus == HTTPStatus::FORBIDDEN && isAuthenticated()) || (retStatus == HTTPStatus::UNAUTHORIZED) ||
179 (retStatus == HTTPStatus::NOT_FOUND)) {
180 // If authentication fails you can get HTTP_UNAUTHORIZED or HTTP_NOT_FOUND
181 // If the limit runs out you can get HTTP_FORBIDDEN
182 return this->processAnonymousRequest(uri, responseStream);
183 } else if (isRelocated(retStatus)) {
184 return static_cast<InternetHelper::HTTPStatus>(this->processRelocation(*m_response, responseStream));
185 } else {
186 Poco::StreamCopier::copyStream(rs, responseStream);
187 return processErrorStates(*m_response, rs, uri.toString());
188 }
189}
190
191} // namespace Kernel
192} // 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:145
void warning(const std::string &msg)
Logs at warning level.
Definition Logger.cpp:117
void information(const std::string &msg)
Logs at information level.
Definition Logger.cpp:136
ProxyInfo : Container for carrying around network proxy information.
Definition ProxyInfo.h:17
Helper class which provides the Collimation Length for SANS instruments.