Good morning.
I'm trying to implement OAuth1 authentification in OCaml to get secure LTI request with Moodle.
I have some trouble when I want to rebuild the OAuth1 signature : it doesn't match with the signature Moodle sent me in the POST's payload.
I made manually the build of OAuth's signature following the twitter documentation and the OAuth core specification.
Here is my function :
let signature_oauth liste_args http_method basic_uri consumer_key secret =
let couple_encode = (* 1 : encode keys/values *)
List.map (
fun (k,v) -> (Netencoding.Url.encode ~plus:false k, Netencoding.Url.encode ~plus:false v))
## convert_to_gapi liste_args
in
let couple_trie = (* 2 : sort by key [and value] *)
List.sort
(fun (k1, v1) (k2,v2) ->
let res = compare k1 k2 in
if res = 0 then compare v1 v2 else res) couple_encode
in
let liste_concat = (* 3 : key=value&key2=value2 *)
String.concat "&"
## List.map
(fun (k,v) -> k ^ "=" ^ v) couple_trie
in
let signature_base_string = (* 4 :add HTTP_method and uri *)
sprintf "%s&%s&%s" (String.uppercase_ascii http_method) (Netencoding.Url.encode ~plus:false basic_uri) (Netencoding.Url.encode ~plus:false liste_concat)
in
let signing_key = (Netencoding.Url.encode ~plus:false consumer_key) ^ "&" ^ (Netencoding.Url.encode ~plus:false secret) in (* 5 : Créer la signing_key *)
let encodage = Netencoding.Base64.encode
## Cstruct.to_string
## Nocrypto.Hash.SHA1.hmac (Cstruct.of_string signing_key) (Cstruct.of_string signature_base_string)
in
encodage
My function call : signature_oauth liste_args "post" "localhost:8000/launch" !oauth_consumer_key my_secret
The liste_args argument is the the payload splitted by "&" (Ex : [oauth_version=1.0;oauth_nonce=5aa374e2728914002261bbb7b4bd8e3e];...).
The convert_to_gapi function convert this liste_args in liste of couple (Ex : [(oauth_version,1.0);(oauth_nonce,5aa374e2728914002261bbb7b4bd8e3e);...]. It also remove the oauth_signature.
The payload is application/x-www-form-urlencoded encoded.
Here is an example of a payload I can receive :
oauth_version=1.0&oauth_nonce=5aa374e2728914002261bbb7b4bd8e3e&oauth_timestamp=1588583843&oauth_consumer_key=rfrezFZErzfzHJmpmkBFT&user_id=2&lis_person_sourcedid=&roles=Instructor%2Curn%3Alti%3Asysrole%3Aims%2Flis%2FAdministrator%2Curn%3Alti%3Ainstrole%3Aims%2Flis%2FAdministrator&context_id=2&context_label=CT&context_title=Cours+test&resource_link_title=Test&resource_link_description=&resource_link_id=2&context_type=CourseSection&lis_course_section_sourcedid=1&lis_result_sourcedid=%7B%22data%22%3A%7B%22instanceid%22%3A%222%22%2C%22userid%22%3A%222%22%2C%22typeid%22%3A%221%22%2C%22launchid%22%3A2096392689%7D%2C%22hash%22%3A%225dbc6bff5ec32c9eee91ee3dcb476b3a553ae5337f8a21b3876b52e4e6cf862c%22%7D&lis_outcome_service_url=http%3A%2F%2Flocalhost%2Fmod%2Flti%2Fservice.php&lis_person_name_given=Admin&lis_person_name_family=User&lis_person_name_full=Admin+User&ext_user_username=user&lis_person_contact_email_primary=user%40example.com&launch_presentation_locale=en&ext_lms=moodle-2&tool_consumer_info_product_family_code=moodle&tool_consumer_info_version=2019111802&oauth_callback=about%3Ablank<i_version=LTI-1p0<i_message_type=basic-lti-launch-request&tool_consumer_instance_guid=localhost&tool_consumer_instance_name=%22New+Site%22&tool_consumer_instance_description=%22New+Site%22&launch_presentation_document_target=iframe&launch_presentation_return_url=http%3A%2F%2Flocalhost%2Fmod%2Flti%2Freturn.php%3Fcourse%3D2%26launch_container%3D3%26instanceid%3D2%26sesskey%3DPPPY9j3N8D&oauth_signature_method=HMAC-SHA1&oauth_signature=fuB7Qm0MXLrHPrvniQsBNcvj7BA%3D
I tried to make the OAuth1 signature with a librairie but I got the same signature as my function.
I guess I am missing something in the creation of signature.
I'm building the signature with the same parameters as Moodle (sent in the payload), using the same secret key and the same algorithm.
In the OAuth1's documentation I read they didn't use oauth_callback in the payload but Moodle does.
Should I remove it ?
They talk of an oauth_token too. But Moodle didn't give me one and I didn't have a clue of how getting it. It is essential to build the signature ?
Thanks in advance for your help.
I found the answer.
It had 2 errors :
First one was the signing_key : I only need to make (Netencoding.Url.encode ~plus:false secret) ^ "&"
The other was from Moodle : when you configure an "Automatic tool, based on tool URL" and then switch to a Course Tool, Moodle doesn't care about the Course Tool's configuration. You need first to delete all the fields of the Automatic tool and then switch to your Course Tool.
What helped me to understand:
http://lti.tools/saltire/
https://sandbox.moodledemo.net/
http://lti.tools/oauth/
Related
I am trying to use google_speech1 for Rust, but the documentation provides incomplete examples, which makes it very hard for me, being both new at Rust and at using Google Speech Api, to figure out how to do send a speech to text request.
More specifically, I would like to be able to send a local audio file, indicate the source language and retrieve the transcription.
Here is the closest I could find in the official documentation(https://docs.rs/google-speech1/1.0.8+20181005/google_speech1/struct.SpeechRecognizeCall.html):
use speech1::RecognizeRequest;
// As the method needs a request, you would usually fill it with the desired information
// into the respective structure. Some of the parts shown here might not be applicable !
// Values shown here are possibly random and not representative !
let mut req = RecognizeRequest::default();
// You can configure optional parameters by calling the respective setters at will, and
// execute the final call using `doit()`.
// Values shown here are possibly random and not representative !
let result = hub.speech().recognize(req)
.doit();
UPDATE
Taking a step back, even simple examples provided on the website don't seem to run properly. Here is some sample very basic code:
pub mod speech_api_demo {
extern crate google_speech1 as speech1;
extern crate hyper;
extern crate hyper_rustls;
extern crate yup_oauth2 as oauth2;
use oauth2::{ApplicationSecret, Authenticator, DefaultAuthenticatorDelegate, MemoryStorage};
use speech1::Speech;
use speech1::{Error, Result};
use std::fs::File;
use std::io::Read;
#[derive(Deserialize, Serialize, Default)]
pub struct ConsoleApplicationSecret {
pub web: Option<ApplicationSecret>,
pub installed: Option<ApplicationSecret>,
}
pub fn speech_sample_demo() {
/*
Custom code to generate application secret
*/
let mut file =
File::open("C:\\Users\\YOURNAME\\.google-service-cli\\speech1-secret.json").unwrap();
let mut data = String::new();
file.read_to_string(&mut data).unwrap();
use serde_json as json;
let my_console_secret = json::from_str::<ConsoleApplicationSecret>(&data);
assert!(my_console_secret.is_ok());
let unwrappedConsoleSecret = my_console_secret.unwrap();
assert!(unwrappedConsoleSecret.installed.is_some() && unwrappedConsoleSecret.web.is_none());
let secret: ApplicationSecret = unwrappedConsoleSecret.installed.unwrap();
/*
Custom code to generate application secret - END
*/
// Instantiate the authenticator. It will choose a suitable authentication flow for you,
// unless you replace `None` with the desired Flow.
// Provide your own `AuthenticatorDelegate` to adjust the way it operates and get feedback about
// what's going on. You probably want to bring in your own `TokenStorage` to persist tokens and
// retrieve them from storage.
let auth = Authenticator::new(
&secret,
DefaultAuthenticatorDelegate,
hyper::Client::with_connector(hyper::net::HttpsConnector::new(
hyper_rustls::TlsClient::new(),
)),
<MemoryStorage as Default>::default(),
None,
);
let mut hub = Speech::new(
hyper::Client::with_connector(hyper::net::HttpsConnector::new(
hyper_rustls::TlsClient::new(),
)),
auth,
);
let result = hub.operations().get("name").doit();
match result {
Err(e) => match e {
// The Error enum provides details about what exactly happened.
// You can also just use its `Debug`, `Display` or `Error` traits
Error::HttpError(_)
| Error::MissingAPIKey
| Error::MissingToken(_)
| Error::Cancelled
| Error::UploadSizeLimitExceeded(_, _)
| Error::Failure(_)
| Error::BadRequest(_)
| Error::FieldClash(_)
| Error::JsonDecodeError(_, _) => (println!("{}", e)),
},
Ok(res) => println!("Success: {:?}", res),
}
}
}
Running this code (calling speech_sample_demo) gives the following error:
Token retrieval failed with error: Invalid Scope: 'no description
provided'
I also tried some very ugly code to force the scope into the request, but it did not make any difference. I am having a hard time understanding what this error means. Am I missing something in my request or is it something else getting in the way at the other end? Or maybe that api code library is just broken?
Please also note that client id and client secret provided by default don't work anymore, when I was using those it would say that account is deleted.
I then set up an OAuth 2.0 client and generated the json file which I copied over to default location and then started getting the error above. Maybe it is just me not setting Google Api account properly, but in any case would be great if someone else could try it out to see if I am the only one having those issues.
Once I get over running such a simple request, I have some more code ready to be tested that sends over an audio file, but for now it fails very early on in the process.
The error you get originates from here and means that the OAuth scope you used when generating your credentials file doesn't allow you to access the Google speech API. So the problem is not in your Rust code, but instead in the script you used to generate your OAuth access tokens.
Basically, this means that when you generated your OAuth json file, you requested access to the Google API in a general way, but you didn't say which specific APIs you meant to use. According to this document, you need to request access to the https://www.googleapis.com/auth/cloud-platform scope.
You are missing the flow param to Authenticator. This is how you get the access token. You create an Enum using FlowType.
example:
use oauth2::{ApplicationSecret, Authenticator, DefaultAuthenticatorDelegate, MemoryStorage,FlowType};
let Flo = FlowType::InstalledInteractive;
let auth = Authenticator::new(
&secret,
DefaultAuthenticatorDelegate,
hyper::Client::with_connector(hyper::net::HttpsConnector::new(
hyper_rustls::TlsClient::new(),
)),
<MemoryStorage as Default>::default(),
None,)
See here: https://docs.rs/yup-oauth2/1.0.3/yup_oauth2/enum.FlowType.html
Not exactly easy to figure out.
I made this work via service accounts by doing this
let https = hyper_rustls::HttpsConnectorBuilder::new()
.with_native_roots()
.https_only()
.enable_http1()
.build();
let service_account_key: oauth2::ServiceAccountKey = oauth2::read_service_account_key(
&"PATH_TO_SERVICE_ACCOUNT.json".to_string(),
)
.await
.unwrap();
let auth = oauth2::ServiceAccountAuthenticator::builder(service_account_key)
.build()
.await
.unwrap();
let hub = Speech::new(hyper::Client::builder().build(https), auth);
For months I've been using a url like this, from perl:
http://finance.yahoo.com/d/quotes.csv?s=$s&f=ynl1 #returns yield, name, price;
Today, 11/1/17, it suddenly returns a 999 error.
Is this a glitch, or has Yahoo terminated the service?
I get the error even if I enter the URL directly into a browser as, eg:
http://finance.yahoo.com/d/quotes.csv?s=INTC&f=ynl1
so it doesn't seem to be a 'crumb' problem.
Note: This is NOT a question which has been answered in the past!
It was working yesterday.That it happened on the first of the month is suspicious.
As noted in the other answers and elsewhere (e.g. https://stackoverflow.com/questions/47076404/currency-helper-of-yahoo-sorry-unable-to-process-request-at-this-time-erro/47096766#47096766), Yahoo has indeed ceased operation of the Yahoo Finance API. However, as a workaround, you can access a trove of financial information, in JSON format, for a given ticker symbol, by doing a HTTPS GET request to: https://finance.yahoo.com/quote/SYMBOL (e.g. https://finance.yahoo.com/quote/MSFT). If you do a GET request to the above URL, you'll see that the financial data is contained within the response in JSON format. The following python3 script shows how you can parse individual values that you may be interested in:
import requests
import json
symbol = 'MSFT'
url ='https://finance.yahoo.com/quote/' + symbol
resp = requests.get(url)
# parse the section from the html document containing the raw json data that we need
# you can write jsonstr to a file, then open the file in a web browser to browse the structure of the json data
r = str(resp.content, 'utf-8')
i1 = 0
i1 = r.find('root.App.main', i1)
i1 = r.find('{', i1)
i2 = r.find("\n", i1)
i2 = r.rfind(';', i1, i2)
jsonstr = r[i1:i2]
# load the raw json data into a python data object
data = json.loads(jsonstr)
# pull the values that we are interested in
name = data['context']['dispatcher']['stores']['QuoteSummaryStore']['price']['shortName']
price = data['context']['dispatcher']['stores']['QuoteSummaryStore']['price']['regularMarketPrice']['raw']
change = data['context']['dispatcher']['stores']['QuoteSummaryStore']['price']['regularMarketChange']['raw']
shares_outstanding = data['context']['dispatcher']['stores']['QuoteSummaryStore']['defaultKeyStatistics']['sharesOutstanding']['raw']
market_cap = data['context']['dispatcher']['stores']['QuoteSummaryStore']['summaryDetail']['marketCap']['raw']
trailing_pe = data['context']['dispatcher']['stores']['QuoteSummaryStore']['summaryDetail']['trailingPE']['raw']
earnings_per_share = data['context']['dispatcher']['stores']['QuoteSummaryStore']['defaultKeyStatistics']['trailingEps']['raw']
forward_annual_dividend_rate = data['context']['dispatcher']['stores']['QuoteSummaryStore']['summaryDetail']['dividendRate']['raw']
forward_annual_dividend_yield = data['context']['dispatcher']['stores']['QuoteSummaryStore']['summaryDetail']['dividendYield']['raw']
# print the values
print('Symbol:', symbol)
print('Name:', name)
print('Price:', price)
print('Change:', change)
print('Shares Outstanding:', shares_outstanding)
print('Market Cap:', market_cap)
print('Trailing PE:', trailing_pe)
print('Earnings Per Share:', earnings_per_share)
print('Forward Annual Dividend Rate:', forward_annual_dividend_rate)
print('Forward_annual_dividend_yield:', forward_annual_dividend_yield)
Yahoo confirmed that they terminated the service:
It has come to our attention that this service is being used in violation of the Yahoo Terms of Service. As such, the service is being discontinued. For all future markets and equities data research, please refer to finance.yahoo.com .
There is still a way to get this data by querying some APIs used by the finance.yahoo.com page. Not sure if Yahoo will be supporting it long term as the previous API was (hopefully they will).
I adapted the method used by https://github.com/pstadler/ticker.sh into the following python hack that takes a list of symbols from the command line and outputs some of the variables as a csv:
#!/usr/bin/env python
import sys
import time
import requests
if len(sys.argv) < 2:
print("missing parameters: <symbol> ...")
exit()
apiEndpoint = "https://query1.finance.yahoo.com/v7/finance/quote"
fields = [
'symbol',
'regularMarketVolume',
'regularMarketPrice',
'regularMarketDayHigh',
'regularMarketDayLow',
'regularMarketTime',
'regularMarketChangePercent']
fields = ','.join(fields)
symbols = sys.argv[1:]
symbols = ','.join(symbols)
payload = {
'lang': 'en-US',
'region': 'US',
'corsDomain': 'finance.yahoo.com',
'fields': fields,
'symbols': symbols}
r = requests.get(apiEndpoint, params=payload)
for i in r.json()['quoteResponse']['result']:
if 'regularMarketPrice' in i:
a = []
a.append(i['symbol'])
a.append(i['regularMarketPrice'])
a.append(time.strftime(
'%Y-%m-%d %H:%M:%S', time.localtime(i['regularMarketTime'])))
a.append(i['regularMarketChangePercent'])
a.append(i['regularMarketVolume'])
a.append("{0:.2f} - {1:.2f}".format(
i['regularMarketDayLow'], i['regularMarketDayHigh']))
print(",".join([str(e) for e in a]))
Sample Run:
$ ./getquotePy.py AAPL GOOGL
AAPL,174.5342,2017-11-07 17:21:28,0.1630961,19905458,173.60 - 173.60
GOOGL,1048.6753,2017-11-07 17:21:22,0.5749836,840447,1043.00 - 1043.00
var API = "https://query1.finance.yahoo.com/v7/finance/quote?symbols=AAPL";
$.getJSON(API, function (json) {...});call throws this error: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://www.microplan.at/sar' is therefore not allowed access.
For months I've been using a url like this, from perl:
http://finance.yahoo.com/d/quotes.csv?s=$s&f=ynl1 #returns yield, name, price;
Today, 11/1/17, it suddenly returns a 999 error.
Is this a glitch, or has Yahoo terminated the service?
I get the error even if I enter the URL directly into a browser as, eg:
http://finance.yahoo.com/d/quotes.csv?s=INTC&f=ynl1
so it doesn't seem to be a 'crumb' problem.
Note: This is NOT a question which has been answered in the past!
It was working yesterday.That it happened on the first of the month is suspicious.
As noted in the other answers and elsewhere (e.g. https://stackoverflow.com/questions/47076404/currency-helper-of-yahoo-sorry-unable-to-process-request-at-this-time-erro/47096766#47096766), Yahoo has indeed ceased operation of the Yahoo Finance API. However, as a workaround, you can access a trove of financial information, in JSON format, for a given ticker symbol, by doing a HTTPS GET request to: https://finance.yahoo.com/quote/SYMBOL (e.g. https://finance.yahoo.com/quote/MSFT). If you do a GET request to the above URL, you'll see that the financial data is contained within the response in JSON format. The following python3 script shows how you can parse individual values that you may be interested in:
import requests
import json
symbol = 'MSFT'
url ='https://finance.yahoo.com/quote/' + symbol
resp = requests.get(url)
# parse the section from the html document containing the raw json data that we need
# you can write jsonstr to a file, then open the file in a web browser to browse the structure of the json data
r = str(resp.content, 'utf-8')
i1 = 0
i1 = r.find('root.App.main', i1)
i1 = r.find('{', i1)
i2 = r.find("\n", i1)
i2 = r.rfind(';', i1, i2)
jsonstr = r[i1:i2]
# load the raw json data into a python data object
data = json.loads(jsonstr)
# pull the values that we are interested in
name = data['context']['dispatcher']['stores']['QuoteSummaryStore']['price']['shortName']
price = data['context']['dispatcher']['stores']['QuoteSummaryStore']['price']['regularMarketPrice']['raw']
change = data['context']['dispatcher']['stores']['QuoteSummaryStore']['price']['regularMarketChange']['raw']
shares_outstanding = data['context']['dispatcher']['stores']['QuoteSummaryStore']['defaultKeyStatistics']['sharesOutstanding']['raw']
market_cap = data['context']['dispatcher']['stores']['QuoteSummaryStore']['summaryDetail']['marketCap']['raw']
trailing_pe = data['context']['dispatcher']['stores']['QuoteSummaryStore']['summaryDetail']['trailingPE']['raw']
earnings_per_share = data['context']['dispatcher']['stores']['QuoteSummaryStore']['defaultKeyStatistics']['trailingEps']['raw']
forward_annual_dividend_rate = data['context']['dispatcher']['stores']['QuoteSummaryStore']['summaryDetail']['dividendRate']['raw']
forward_annual_dividend_yield = data['context']['dispatcher']['stores']['QuoteSummaryStore']['summaryDetail']['dividendYield']['raw']
# print the values
print('Symbol:', symbol)
print('Name:', name)
print('Price:', price)
print('Change:', change)
print('Shares Outstanding:', shares_outstanding)
print('Market Cap:', market_cap)
print('Trailing PE:', trailing_pe)
print('Earnings Per Share:', earnings_per_share)
print('Forward Annual Dividend Rate:', forward_annual_dividend_rate)
print('Forward_annual_dividend_yield:', forward_annual_dividend_yield)
Yahoo confirmed that they terminated the service:
It has come to our attention that this service is being used in violation of the Yahoo Terms of Service. As such, the service is being discontinued. For all future markets and equities data research, please refer to finance.yahoo.com .
There is still a way to get this data by querying some APIs used by the finance.yahoo.com page. Not sure if Yahoo will be supporting it long term as the previous API was (hopefully they will).
I adapted the method used by https://github.com/pstadler/ticker.sh into the following python hack that takes a list of symbols from the command line and outputs some of the variables as a csv:
#!/usr/bin/env python
import sys
import time
import requests
if len(sys.argv) < 2:
print("missing parameters: <symbol> ...")
exit()
apiEndpoint = "https://query1.finance.yahoo.com/v7/finance/quote"
fields = [
'symbol',
'regularMarketVolume',
'regularMarketPrice',
'regularMarketDayHigh',
'regularMarketDayLow',
'regularMarketTime',
'regularMarketChangePercent']
fields = ','.join(fields)
symbols = sys.argv[1:]
symbols = ','.join(symbols)
payload = {
'lang': 'en-US',
'region': 'US',
'corsDomain': 'finance.yahoo.com',
'fields': fields,
'symbols': symbols}
r = requests.get(apiEndpoint, params=payload)
for i in r.json()['quoteResponse']['result']:
if 'regularMarketPrice' in i:
a = []
a.append(i['symbol'])
a.append(i['regularMarketPrice'])
a.append(time.strftime(
'%Y-%m-%d %H:%M:%S', time.localtime(i['regularMarketTime'])))
a.append(i['regularMarketChangePercent'])
a.append(i['regularMarketVolume'])
a.append("{0:.2f} - {1:.2f}".format(
i['regularMarketDayLow'], i['regularMarketDayHigh']))
print(",".join([str(e) for e in a]))
Sample Run:
$ ./getquotePy.py AAPL GOOGL
AAPL,174.5342,2017-11-07 17:21:28,0.1630961,19905458,173.60 - 173.60
GOOGL,1048.6753,2017-11-07 17:21:22,0.5749836,840447,1043.00 - 1043.00
var API = "https://query1.finance.yahoo.com/v7/finance/quote?symbols=AAPL";
$.getJSON(API, function (json) {...});call throws this error: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://www.microplan.at/sar' is therefore not allowed access.
I am writing some code to try to get a token to use from Google in OAuth2. This is for a service account, so the instructions are here:
https://developers.google.com/identity/protocols/OAuth2ServiceAccount
I keep getting this error when I post the JWT to Google:
{ "error": "invalid_grant", "error_description": "Invalid JWT Signature." }
Here is the code:
try{
var nowInSeconds : Number = (Date.now() / 1000);
nowInSeconds = Math.round(nowInSeconds);
var fiftyNineMinutesFromNowInSeconds : Number = nowInSeconds + (59 * 60);
var claimSet : Object = {};
claimSet.iss = "{{RemovedForPrivacy}}";
claimSet.scope = "https://www.googleapis.com/auth/plus.business.manage";
claimSet.aud = "https://www.googleapis.com/oauth2/v4/token";
claimSet.iat = nowInSeconds;
claimSet.exp = fiftyNineMinutesFromNowInSeconds;
var header : Object = {};
header.alg = "RS256";
header.typ = "JWT";
/* Stringify These */
var claimSetString = JSON.stringify(claimSet);
var headerString = JSON.stringify(header);
/* Base64 Encode These */
var claimSetBaseSixtyFour = StringUtils.encodeBase64(claimSetString);
var headerBaseSixtyFour = StringUtils.encodeBase64(headerString);
var privateKey = "{{RemovedForPrivacy}}";
/* Create the signature */
var signature : Signature = Signature();
signature = signature.sign(headerBaseSixtyFour + "." + claimSetBaseSixtyFour, privateKey , "SHA256withRSA");
/* Concatenate the whole JWT */
var JWT = headerBaseSixtyFour + "." + claimSetBaseSixtyFour + "." + signature;
/* Set Grant Type */
var grantType = "urn:ietf:params:oauth:grant-type:jwt-bearer"
/* Create and encode the body of the token post request */
var assertions : String = "grant_type=" + dw.crypto.Encoding.toURI(grantType) + "&assertion=" + dw.crypto.Encoding.toURI(JWT);
/* Connect to Google And Ask for Token */
/* TODO Upload Certs? */
var httpClient : HTTPClient = new HTTPClient();
httpClient.setRequestHeader("content-type", "application/x-www-form-urlencoded; charset=utf-8");
httpClient.timeout = 30000;
httpClient.open('POST', "https://www.googleapis.com/oauth2/v4/token");
httpClient.send(assertions);
if (httpClient.statusCode == 200) {
//nothing
} else {
pdict.errorMessage = httpClient.errorText;
}
}
catch(e){
Logger.error("The error with the OAuth Token Generator is --> " + e);
}
Does anyone know why the JWT is failing?
Thanks so much!
Brad
The problem might be related to the fact that your StringUtils.encodeBase64() method is likely to perform a standard base64 encoding.
According to the JWT spec, however, it's not the standard base64 encoding that needs to be used, but the the URL- and filename-safe Base64 encoding, with the = padding characters omitted.
If you don't have a utility method handy for base64URL encoding, you can verify by
replacing all + with -;
replacing all / with _;
removing all =
in your base64-encoded strings.
Also, is your signature also base64-encoded? It needs to be, following the same rules as described above.
I had the same problem before and this is what was wrong:
wrong application name (project ID)
wrong service account ID (email)
The another reason for this error could be "Your service account is not activated", With gsutil installed from the Cloud SDK, you should authenticate with service account credentials.
1- Use an existing service account or create a new one, and download the associated private key.
2- Use gcloud auth activate-service-account to authenticate with the service account:
gcloud auth activate-service-account --key-file [KEY_FILE]
Where [KEY_FILE] is the name of the file that contains your service account credentials.
Link for more detail: Activate service account
This could also happen if a developer mistakenly copies, edits, and uses a service account key file for a purpose other than the one for which the file was originally intended. For example:
Developer A creates SA 1
Developer A uses gcloud iam service-accounts keys create ... to create the secret file for SA 1, encrypts it, and checks it in to source control
Developer B creates SA 2
Developer B (mistakenly) decrypts and copies the secret file from step 2, modifies some of its fields with data from SA 2, then attempts to use it in an application
The resolution in this scenario obviously is for Developer B to get rid of the copied/edited file and create a new secret file with gcloud like Developer A did in step 2.
I had this same error occur when using a service account. I couldn't figure out what was wrong so I came back to it the next day and it worked. So maybe Google Cloud takes some time to propagate every once in a while.
I am making the following request which is returning Json.
let baseUrl = "http://wex-qa.mybluemix.net/resources/question"
let userName = "yourName#aol.com"
let password = "yourCreds"
let authKey = userName + ":" + password
let client = new HttpClient()
client.DefaultRequestHeaders.Authorization <- new AuthenticationHeaderValue("Basic",authKey)
let input = new Dictionary<string,string>()
input.Add("question","what time is it")
let content = new FormUrlEncodedContent(input)
let result = client.PostAsync(baseUrl,content).Result
let resultContent = result.Content.ReadAsStringAsync().Result
I immediately thought of using the Json Type Provider so I made a local file of the response to be the type def. I then went to load the type where I need the credentials and the content. However, I did not see where the .Load() function have an overload to pass in the credentials and the content. Should I continue to make the request via the HttpClient class and use the .Parse() function?
Thanks in advance.
Expanding on the answer in the comments (which is correct).
I had this problem in 2022 with FSharp.Data 4.2.9. There still seems to be no way to put authorisation credentials into the requests for either defining the JsonProvider type, or the runtime collection of data. With a bit of working around, it can be made to work like a type provider, as described in the question and comment.
Get your credentials for web requests from the site. This often involves a single 'key' rather than a username/password pair.
Instantiate the type provider with sample data from the site (extracted with the required credentials).
At runtime, use HttpClient with Authorization, to read JSON from the URL, and then use the Parse method of the type provide to return typed data. This allows you to use intellisense on the returned data
This example is for the MailChimp API, which supports mailing lists and requires a company API Key on web requests.
First, follow instructions on the site to get 'Credentials' for API calls.
Second, use any method to extract sample JSON to a file. I used the PHP samples provided by MailChimp.
Then define the JsonProvider with that data.
type mcListsTypeProvider = JsonProvider< "./lists_sample_data.json">
Here lists is a fundamental component of the API. In this example, I will be working with the "Sales Subscribers" list.
The next function will read from a URL, and return typed Json data, as if directly from the Load function.
The core of this is the jsonProviderParse argument, which is the Parse method from the JsonProvider.
let loadTypedJson url jsonProviderParse =
use httpClient = new HttpClient()
// Set up authorisation
let mailChimpApiKey = "3ef8dXXXXXXXXXXXXXXXXf61e-us18" // Could be a "userName:passowrd" key
httpClient.DefaultRequestHeaders.Authorization <- AuthenticationHeaderValue("Basic",mailChimpApiKey)
// Get content
let result = httpClient.GetAsync(url : string).Result
let content = result.Content.ReadAsStringAsync().Result
// Parse content, with the Parse method from the JsonProvider
jsonProviderParse content
Then use loadTypedJson as you would myTypeProvider.Load("https:..")
// Find the MailChimp list Id for the "Sales Subscribers" list
let listsUrl = "https://us18.api.mailchimp.com/3.0/lists"
let data = loadTypedJson listsUrl mcListsTypeProvider.Parse
let list_id =
data.Lists
|> Seq.find (fun list -> list.Name = "Sale Subscribers")
|> (fun x -> x.Id)
printfn "Sale Subscribers list_id = %s" list_id
Output: list_id = f21XXXXX85
Intellisense working for the JsonProvider