26#include <wx/datetime.h>
32wxString joinUrl(
const wxString& aBaseUrl,
const wxString& aPath )
34 if( aPath.StartsWith( wxS(
"https://" ) ) || aPath.StartsWith( wxS(
"http://" ) ) )
37 wxString base = aBaseUrl;
39 while( base.EndsWith( wxS(
"/" ) ) )
42 if( aPath.StartsWith( wxS(
"/" ) ) )
45 return base + wxS(
"/" ) + aPath;
52 endpoint.Replace( wxS(
"{part_id}" ),
UrlEncode( aPartId ) );
65 if( !curl.
SetURL( aRequest.
url.ToStdString() ) )
67 aError = wxString::Format( wxS(
"Unable to set URL '%s'." ), aRequest.
url );
86 aResponse.
body = wxString::FromUTF8( curl.
GetBuffer().c_str() );
106 static const wxString suffix = wxS(
"/.well-known/kicad-remote-provider" );
108 if( aProviderUrl.EndsWith( suffix ) )
111 wxString base = aProviderUrl;
113 while( base.EndsWith( wxS(
"/" ) ) )
116 return base + suffix;
128 request.
headers.push_back( { wxS(
"Accept" ), wxS(
"application/json" ) } );
130 nlohmann::json responseJson;
132 if( !
FetchJson( request, responseJson, aError ) )
145 aMetadata = *metadata;
165 request.
headers.push_back( { wxS(
"Accept" ), wxS(
"application/json" ) } );
167 nlohmann::json responseJson;
169 if( !
FetchJson( request, responseJson, aError ) )
173 std::optional<REMOTE_PROVIDER_OAUTH_SERVER_METADATA> metadata =
183 aMetadata = *metadata;
197 request.
body = wxString( wxS(
"grant_type=authorization_code" ) )
202 request.
headers.push_back( { wxS(
"Accept" ), wxS(
"application/json" ) } );
204 { wxS(
"Content-Type" ), wxS(
"application/x-www-form-urlencoded" ) } );
205 request.
headers.push_back( { wxS(
"Cache-Control" ), wxS(
"no-store" ) } );
225 request.
body = wxString( wxS(
"grant_type=refresh_token" ) )
226 + wxS(
"&refresh_token=" ) +
UrlEncode( aRefreshToken )
227 + wxS(
"&client_id=" ) +
UrlEncode( aClientId );
228 request.
headers.push_back( { wxS(
"Accept" ), wxS(
"application/json" ) } );
230 { wxS(
"Content-Type" ), wxS(
"application/x-www-form-urlencoded" ) } );
231 request.
headers.push_back( { wxS(
"Cache-Control" ), wxS(
"no-store" ) } );
243 const wxString& aClientId,
const wxString& aToken,
254 request.
body = wxString( wxS(
"token=" ) ) +
UrlEncode( aToken )
255 + wxS(
"&client_id=" ) +
UrlEncode( aClientId );
256 request.
headers.push_back( { wxS(
"Accept" ), wxS(
"application/json" ) } );
258 { wxS(
"Content-Type" ), wxS(
"application/x-www-form-urlencoded" ) } );
266 const wxString& aAccessToken )
const
271 if( aAccessToken.IsEmpty() )
287 aError.
message =
_(
"Sign in required for this provider." );
293 request.
url = buildPartsUrl( aProvider, aPartId );
294 request.
headers.push_back( { wxS(
"Accept" ), wxS(
"application/json" ) } );
296 if( !aAccessToken.IsEmpty() )
297 request.
headers.push_back( { wxS(
"Authorization" ), wxS(
"Bearer " ) + aAccessToken } );
299 nlohmann::json responseJson;
301 if( !
FetchJson( request, responseJson, aError ) )
305 std::optional<REMOTE_PROVIDER_PART_MANIFEST> manifest =
315 aManifest = *manifest;
321 const wxString& aAccessToken, wxString& aNonceUrl,
329 aError.
message =
_(
"Provider metadata does not include a session bootstrap URL." );
334 body[
"access_token"] = aAccessToken.ToStdString();
335 body[
"next_url"] = aMetadata.
panel_url.ToStdString();
340 request.
body = wxString::FromUTF8( body.dump().c_str() );
341 request.
headers.push_back( { wxS(
"Accept" ), wxS(
"application/json" ) } );
342 request.
headers.push_back( { wxS(
"Content-Type" ), wxS(
"application/json" ) } );
344 nlohmann::json responseJson;
346 if( !
FetchJson( request, responseJson, aError ) )
349 if( !responseJson.contains(
"nonce_url" ) || !responseJson[
"nonce_url"].is_string() )
352 aError.
message =
_(
"Bootstrap response did not include a nonce URL." );
356 aNonceUrl = wxString::FromUTF8( responseJson[
"nonce_url"].get_ref<const std::string&>().c_str() );
358 wxString securityError;
361 wxS(
"Bootstrap nonce URL" ) ) )
364 aError.
message = securityError;
371 aError.
message =
_(
"Bootstrap nonce URL origin does not match the bootstrap endpoint." );
383 wxString transportError;
385 if( !
m_handler( aRequest, aResponse, transportError ) )
388 aError.
message = transportError;
407 aError.
message = wxString::Format(
_(
"Remote provider request failed with HTTP %d." ),
426 aJson = nlohmann::json::parse( response.
body.ToStdString() );
428 catch(
const std::exception& e )
432 wxString::Format(
_(
"Remote provider returned invalid JSON: %s" ), wxString::FromUTF8( e.what() ) );
448 json = nlohmann::json::parse( aResponse.
body.ToStdString() );
450 catch(
const std::exception& e )
453 aError.
message = wxString::Format(
_(
"Remote provider returned invalid token JSON: %s" ),
454 wxString::FromUTF8( e.what() ) );
462 wxString::FromUTF8(
json.at(
"access_token" ).get_ref<
const std::string&>().c_str() );
464 ? wxString::FromUTF8(
465 json.at(
"refresh_token" ).get_ref<
const std::string&>().c_str() )
468 ? wxString::FromUTF8(
469 json.at(
"id_token" ).get_ref<
const std::string&>().c_str() )
472 ? wxString::FromUTF8(
473 json.at(
"token_type" ).get_ref<
const std::string&>().c_str() )
474 : wxString(
"Bearer" );
476 ? wxString::FromUTF8(
json.at(
"scope" ).get_ref<
const std::string&>().c_str() )
479 const long long expiresIn =
json.contains(
"expires_in" ) ?
json.at(
"expires_in" ).get<
long long>() : 0;
480 aTokens.
expires_at =
static_cast<long long>( wxDateTime::Now().GetTicks() ) + expiresIn;
482 catch(
const std::exception& e )
485 aError.
message = wxString::Format(
_(
"Remote provider token response was incomplete: %s" ),
486 wxString::FromUTF8( e.what() ) );
int Perform()
Equivalent to curl_easy_perform.
bool SetPostFields(const std::vector< std::pair< std::string, std::string > > &aFields)
Set fields for application/x-www-form-urlencoded POST request.
bool SetUserAgent(const std::string &aAgent)
Set the request user agent.
int GetResponseStatusCode()
void SetHeader(const std::string &aName, const std::string &aValue)
Set an arbitrary header for the HTTP(s) request.
const std::string & GetBuffer()
Return a reference to the received data buffer.
bool SetURL(const std::string &aURL)
Set the request URL.
bool SetFollowRedirects(bool aFollow)
Enable the following of HTTP(s) and other redirects, by default curl does not follow redirects.
bool SetConnectTimeout(long aTimeoutSecs)
Set the connection timeout in seconds.
const std::string GetErrorText(int aCode)
Fetch CURL's "friendly" error string for a given error code.
REMOTE_PROVIDER_HTTP_HANDLER m_handler
bool RevokeToken(const REMOTE_PROVIDER_OAUTH_SERVER_METADATA &aMetadata, const wxString &aClientId, const wxString &aToken, REMOTE_PROVIDER_ERROR &aError) const
static wxString MetadataDiscoveryUrl(const wxString &aProviderUrl)
REMOTE_PROVIDER_SIGNIN_STATE GetSignInState(const REMOTE_PROVIDER_METADATA &aProvider, const wxString &aAccessToken) const
bool ExchangeBootstrapNonce(const REMOTE_PROVIDER_METADATA &aMetadata, const wxString &aAccessToken, wxString &aNonceUrl, REMOTE_PROVIDER_ERROR &aError) const
bool SendRequest(const REMOTE_PROVIDER_HTTP_REQUEST &aRequest, REMOTE_PROVIDER_HTTP_RESPONSE &aResponse, REMOTE_PROVIDER_ERROR &aError) const
bool FetchOAuthServerMetadata(const REMOTE_PROVIDER_METADATA &aProvider, REMOTE_PROVIDER_OAUTH_SERVER_METADATA &aMetadata, REMOTE_PROVIDER_ERROR &aError) const
bool FetchManifest(const REMOTE_PROVIDER_METADATA &aProvider, const wxString &aPartId, const wxString &aAccessToken, REMOTE_PROVIDER_PART_MANIFEST &aManifest, REMOTE_PROVIDER_ERROR &aError) const
bool ExchangeAuthorizationCode(const REMOTE_PROVIDER_OAUTH_SERVER_METADATA &aMetadata, const OAUTH_SESSION &aSession, const wxString &aCode, OAUTH_TOKEN_SET &aTokens, REMOTE_PROVIDER_ERROR &aError) const
bool RefreshAccessToken(const REMOTE_PROVIDER_OAUTH_SERVER_METADATA &aMetadata, const wxString &aClientId, const wxString &aRefreshToken, OAUTH_TOKEN_SET &aTokens, REMOTE_PROVIDER_ERROR &aError) const
bool FetchJson(const REMOTE_PROVIDER_HTTP_REQUEST &aRequest, nlohmann::json &aJson, REMOTE_PROVIDER_ERROR &aError) const
bool ParseTokenResponse(const REMOTE_PROVIDER_HTTP_RESPONSE &aResponse, OAUTH_TOKEN_SET &aTokens, REMOTE_PROVIDER_ERROR &aError) const
bool DiscoverProvider(const wxString &aProviderUrl, REMOTE_PROVIDER_METADATA &aMetadata, REMOTE_PROVIDER_ERROR &aError) const
std::function< bool(const REMOTE_PROVIDER_HTTP_REQUEST &, REMOTE_PROVIDER_HTTP_RESPONSE &, wxString &)> REMOTE_PROVIDER_HTTP_HANDLER
REMOTE_PROVIDER_SIGNIN_STATE
wxString NormalizedUrlOrigin(const wxString &aUrl)
Return a normalized scheme://host:port origin string for aUrl.
wxString UrlEncode(const wxString &aValue)
Percent-encode a string for use in URL query parameters (RFC 3986 unreserved characters are passed th...
bool ValidateRemoteUrlSecurity(const wxString &aUrl, bool aAllowInsecureLocalhost, wxString &aError, const wxString &aLabel)
Validate that aUrl uses HTTPS, or HTTP on a loopback address when aAllowInsecureLocalhost is true.
REMOTE_PROVIDER_ERROR_TYPE type
std::vector< REMOTE_PROVIDER_HTTP_HEADER > headers
REMOTE_PROVIDER_HTTP_METHOD method
static std::optional< REMOTE_PROVIDER_PART_MANIFEST > FromJson(const nlohmann::json &aJson, bool aAllowInsecureLocalhost, wxString &aError)
wxString result
Test unit parsing edge cases and error handling.