While integrating Chromecast into iOS app, I have faced the problem accessing media content that requires authentication. In this particular case, authentication token must be added to the request in the http header, not as token in the url. There does not seem to be a way to do this with Cast SDK directly. So I have played with custom CAF receiver app, hoping that I can pass this data through customdata to receiver app and receiver app then would form the request with proper http header using playerManager.setMessageInterceptor. But again, how to add custom http header to the final request in the CAF receiver app?
This is how I did it:
const context = cast.framework.CastReceiverContext.getInstance();
const playerManager = context.getPlayerManager();
const playbackConfig = new cast.framework.PlaybackConfig();
playbackConfig.manifestRequestHandler = requestInfo => {
requestInfo.headers = {SomeHeader: "SomeValue", Hello: "World"};
};
playerManager.setMessageInterceptor(cast.framework.messages.MessageType.LOAD, requestData => {
console.log("loaded " + requestData.media.contentId);
playerManager.setPlaybackConfig(playbackConfig);
return requestData;
}
);
Related
I am trying to call a 3rd party service that uses Oauth2 Password Credentials to get an authentication token. Ballerina is returning the following messages.
2020-04-23 15:07:35,414 ERROR [ballerina/oauth2] - Received an invalid response with status-code: 406; and payload: {"fault":{"faultstring":"Raising fault. Fault name : RF.Raise-406-Exception","detail":{"errorcode":"steps.raisefault.RaiseFault"}}}
2020-04-23 15:07:35,418 ERROR [ballerina/oauth2] - Failed to generate OAuth2 token. : error {ballerina/oauth2}Error message=Received an invalid response with status-code: 406; and payload: {"fault":{"faultstring":"Raising fault. Fault name : RF.Raise-406-Exception","detail":{"errorcode":"steps.raisefault.RaiseFault"}}}
error {ballerina/http}AuthenticationFailed message=Failed to prepare request at bearer auth handler. cause=error {ballerina/auth}Error message=Failed to generate OAuth2 token. cause=error {ballerina/oauth2}Error message=Received an invalid response with status-code: 406; and payload: {"fault":{"faultstring":"Raising fault. Fault name : RF.Raise-406-Exception","detail":{"errorcode":"steps.raisefault.RaiseFault"}}}
It's the 406 code that is confusing me as I have set both the content type & accept headers to "application/json" which is what the service requires.
However, the second message says "Failed to generate OAuth2 token" so could it be the call to get the oauth token that is returning the 406? If so how do I set the accept header on the token service call?
Using Ballerina I have called the token endpoint and successfully got a token but if I try to call a service using a PasswordGrantConfig those are the errors I get. I've tried everything I can think of and have successfully got other services using ClientCredentialsGrantConfig to work.
Any help gratefully received.
The relevant code is below. The three sections below are parts of the code in 3 different .bal files.
// configure the Oauth2 Config
import ballerina/config;
import ballerina/http;
import ballerina/oauth2;
public function getOauth2Handler() returns http:BearerAuthHandler {
oauth2:PasswordGrantConfig passwordGrantConfig = {
tokenUrl: config:getAsString("experian.authentication.tokenUrl"),
username: config:getAsString("experian.authentication.username"),
password: config:getAsString("experian.authentication.password"),
clientId: config:getAsString("experian.authentication.clientId"),
clientSecret: config:getAsString("experian.authentication.clientSecret"),
credentialBearer: http:AUTH_HEADER_BEARER
};
oauth2:OutboundOAuth2Provider oauth2Provider = new (passwordGrantConfig);
return new (oauth2Provider);
}
// Configure the API Client
http:ClientConfiguration delphiSelectClientConfig = {
auth: {
authHandler: experian:getOauth2Handler()
}
};
experian:DelphiSelectClientConfig delphiSelectConfig = {
serviceUrl: config:getAsString("experian.services.delphi-select.serviceUrl"),
clientConfig: delphiSelectClientConfig
};
experian:DelphiSelectClient delphiSelectClient = new (delphiSelectConfig);
// Call the endpoint using the Oath2 configuration
import ballerina/http;
import ballerina/io;
public type DelphiSelectClientConfig record {
string serviceUrl;
http:ClientConfiguration clientConfig;
};
//==============================
//============Client============
//==============================
public type DelphiSelectClient client object {
public http:Client clientEp;
public http:ClientConfiguration config;
public function __init(DelphiSelectClientConfig config) {
http:Client httpEp = new (config.serviceUrl, {auth: config.clientConfig.auth});
self.clientEp = httpEp;
self.config = config.clientConfig;
}
public remote function newApplication() returns #untainted json|error {
io:println("In newApplication function");
http:Request request = new;
json requestBody = newApplicationBody; // get test data from json in another file
request.setJsonPayload(requestBody);
var response = check self.clientEp->post("/application", request);
var payload = check response.getJsonPayload();
return payload;
}
};
I have also modified my test code to call the token EP and deliberately set accept to an unacceptable value, for example, "text/csv". In this case I get the same error response. However setting accept to "*/*" does work. Final test; accept of "" (empty) also fails so I suspect that the BearerAuthHandler is not setting any value for accept.
So can I force the BearerAuthHandler to set an accept of "application/json"?
Thanks.
See picture below.
Also, the example in the Oath2 spec you referenced shows a content-type value being set. Even a value of “*/*” would work but I suspect Ballerina leaves it blank.
I have raised the GitHub issue Need to be able to set http header values for OutboundOAuth2Provider
The main objective of http:OutboundAuthHandler objects are to prepare the http:Request with authentication information that needs to be authenticated with external endpoint you are calling to.
The http:BearerAuthHandler is responsible for adding Authorization header with the value of Bearer <token>. "token" is prepared with the provided information. So, there is no option to force http:BearerAuthHandler to set any header for the request.
But in this case, if the API successfully respond if there is Accept header with the value of application/json, you can simply add that header to the http:Request before calling the POST request as follow:
request.addHeader("Accept", "application/json");
My company uses Twilio Flex as our phone system and I was recently tasked with setting up a feature that will let us edit a TwiML voice message that plays before our normal voice message. This TwiML message will be changed through a Twilio bot that I've published in our Microsoft Teams.
The reason for this is so that our support desk can add a short message in the lines of "We're currently experiencing issues with X" before our normal "Welcome to [Company] support" message.
If TwiML's can be edited using HTTP POST/PUT or Twilio's API this should be a trivial matter, but so far I've not been able to figure out how.
I couldn't find any reference to this in the API doc, so I decided that HTTP POST would be the way to go. Using this as a start off point, I'm able to retrieve my TwiML using HTTP GET:
https://support.twilio.com/hc/en-us/articles/223132187--Not-Authorized-error-when-trying-to-view-TwiML-Bin-URL
const axios = require('axios');
const crypto = require('crypto');
const accountSidFlex = process.env.accountSidFlex;
const authTokenFlex = process.env.authTokenFlex;
var URL = 'https://handler.twilio.com/twiml/EHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + '?AccountSid=' + accountSidFlex
var twilioSig = crypto.createHmac('sha1', authTokenFlex).update(new Buffer(URL, 'utf-8')).digest('Base64')
var config = {
headers:{
'X-TWILIO-SIGNATURE': twilioSig
}
}
axios.get(
URL,config
).catch(error => console.log(error))
.then(response => {
console.log(response.data)
})
response.data shows the TwiML's current XML content.
My attempts at a POST only gives the same output as the GET, while PUT gives 405 Method Not Allowed.
var URL = 'https://handler.twilio.com/twiml/EHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' + '?AccountSid=' + accountSidFlex
var twilioSig = crypto.createHmac('sha1', authTokenFlex).update(new Buffer(URL, 'utf-8')).digest('Base64')
var config = {
headers:{
'X-TWILIO-SIGNATURE': twilioSig,
'Content-Type': 'text/xml'
}
}
var xml =
'<?xml version="1.0" encoding="UTF-8"?>\
<Response><Play digits="www"/>\
<Say voice="alice">"We are currently experiencing X related issues". </Say>\
</Response>';
axios.post(
URL,xml,config
)
.catch(error => console.log(error))
.then(response => {
console.log(response.data)
})
Ideally I'd like to be able to change a specific TwiML using either HTTP methods or the Twilio-API, so that we can use it in out Studio Flow. We'd just keep it silent until we need to add something to it and revert back to silent once the issues have passed.
Any help would be appreciated!
You cannot currently change the contents of TwiML Bins, Studio Flows, or Twilio Functions programatically. I believe the key functionality you are looking for is a way to dynamically update the messaging (Say/Play Widget) in a Studio flow based on some condition.
One way is to use a Function Widget to retrieve a Twilio Sync document for the message, returning the message as JSON and have the Say/Play widget play that message. You can find the Twilio Sync REST API examples for Add, Modify, and Retrieve in the associated document.
You can retrieve the parsed response using variable syntax detailed here, https://www.twilio.com/docs/studio/widget-library#run-function.
I am working on ionic app which will be used on android and iOS platforms. App uses one endpoint to post user data to the backend. Data posted on a backend is as follows:
{
"name": "Citizen Foo",
"emailAddress": "citizen.foo#gmail.com",
"role": "citizen"
}
For security purpose every request which is being sent is validated. In order to do this client sends authorization header with every request. Backend creates one for every request and matches it with one sent by client and then only responds otherwise throw an exception. For creating the authorization header, data sent over post request is also a part of the logic. I have simplified this logic becuase the actual problem is different but this part is important to understand the problem.
Following is the sample code on client side:
var sRequestBody = JSON.stringify(data);
var requestBodyBytes = this.getByteArray(unescape(encodeURIComponent(sRequestBody)));
var authHeader = md5.base64(requestBodyBytes);
var headers = {};
headers['Authorization'] = authHeader;
this.nativeHttp.setDataSerializer('json');
this.nativeHttp.clearCookies();
this.nativeHttp.setSSLCertMode('nocheck');
return Observable.fromPromise(this.nativeHttp.post(url, data, headers));
Then, backend which is asp.net web api, calculates authorization in the same way and matches it with one send by client and gives the response if it matches.
var rawContent = await content.ReadAsByteArrayAsync();
var stringContent = content.ReadAsStringAsync().Result;
var hash = md5.ComputeHash(rawContent);
var authorization = Convert.ToBase64String(hash);
if (authHeader != authorization)
throw;
When the above call is made from android,
value of dataString on client is
"{"name":"Citizen Foo","emailAddress":"citizen.foo#gmail.com","role":"citizen"}"
value of stringContent on server is
"{""name"":""Citizen Foo"",""emailAddress"":""citizen.foo#gmail.com"",""role"":""citizen""}"
and it allows request coming from android app.
When the same is run on iOS and the post user call is made,
Value of dataString on client is
"{"name":"Citizen Foo","emailAddress":"citizen.foo#gmail.com","role":"citizen"}"
Value of stringContent on server is
"{""name"":""Citizen Foo"",""role"":""citizen"",""emailAddress"":""citizen.foo#gmail.com""}"
and it does not allow requst coming from iOS app.
And the only reason it is happening is because of the way user object is serialized/received at the backend when request is made from iOS. Order of properties while sending is name,emailAddress,role. While it is received with order name,role,emailAddress. Thus, the authorization value calculated on server side is different than authHeader coming from client and the call is terminated.
stringContent was added on server side just to debug and understand what is being received at server. Order of the object properties on client is different than object properties received on server side. Is there a way to maintain the order of object properties when the request is made from iOS platform? Direction in any way to solve this problem is appreciated.
One solution that worked for us is this:
this.httpClient.setDataSerializer('utf8');
response = await this.httpClient.post(url, JSON.stringify(postBody), {'Content-Type': 'application/json'});
So key points here are:
utf8 as data serializer
body has to be stringified
header 'Content-Type': 'application/json' has to be set
I am working on a mobile app using xamarin forms, the application needs to get some data from an api so i use http client to implement that.
so, the http requests work fine on both Android and Windows Phone, but when i try to debug the request on IOS i get an error which of course doesn't show an exception details.. but while debugging i found that the error happens while encoding the contents of the request, so it returns NULL in the IOS case, while it returns the encoded content in both android and windows phone case.
I used different ways to send the resquest and to encode the request, but got the same result.
Here are my trials:
the http request code1:
var values = new Dictionary<string, string>
{
{ "ExpiryDate", dateAndTime.ToString() },
{ "IsHijri", "false" },
{ "CR_No", CetNum.Text }
};
var content = new FormUrlEncodedContent(values);
var response = await client2.PostAsync("https://XXXXXXX.com/api/XXXXXX", content);
and this is the request code trial 2 :
var myContent = JsonConvert.SerializeObject(cert);
var buffer = System.Text.Encoding.UTF8.GetBytes(myContent);
var byteContent = new ByteArrayContent(buffer);
byteContent.Headers.ContentType = new MediaTypeHeaderValue("application/json");
var Response = await client.PostAsync("api/PlaPla/PlaPla",
byteContent).ConfigureAwait(false);
string res = await Response.Content.ReadAsStringAsync();
Those trials work fine and get the content encoded with other platforms, but not IOS.
the error message i get is System.NullReferenceException: Object
reference not set to an instance of an objec
So, is there a different way i should be using to send an http request in the ios scenario ? any help ? Thanks.
To use Events API for Slack App development, there is a setting for "Events API Request URLs" as described in doc:
In the Events API, your Events API Request URL is the target location
where all the events your application is subscribed to will be
delivered, regardless of the team or event type.
There is a UI for changing the URL "manually" at api.slack.com under
"Event Subscriptions" section in settings. There is also url_verification event after changing the Request URL described here.
My question - Is there an API call (method) so I can update the endpoint (Request URL) from my server code?
For example, in Facebook API there is a call named subscriptions where I can change webhook URL after initial setup - link
Making a POST request with the callback_url, verify_token, and object
fields will reactivate the subscription.
PS. To give a background, this is needed for development using outbound tunnel with dynamic endpoint URL, e.g. ngrok free subscription. By the way, ngrok is referenced in sample "onboarding" app by slack here
Update. I checked Microsoft Bot Framework, and they seems to use RTM (Real Time Messaging) for slack which doesn't require Request URL setup, and not Events API. Same time, e.g. for Facebook they (MS Bot) instruct me to manually put their generated URL to webhook settings of a FB app, so there is no automation on that.
Since this question was originally asked, Slack has introduced app manifests, which enable API calls to change app configurations. This can be used to update URLs and other parameters, or create/delete apps.
At the time of writing, the manifest / manifest API is in beta:
Beta API — this API is in beta, and is subject to change without the usual notice period for changes.
so the this answer might not exactly fit the latest syntax as they make changes.
A programatic workflow might look as follows:
Pull a 'template' manifest from an existing version of the application, with most of the settings as intended (scopes, name, etc.)
Change parts of the manifest to meet the needs of development
Verify the manifest
Update a slack app or create a new one for testing
API List
Basic API list
Export a manifest as JSON: apps.manifest.export
Validate a manifest JSON: apps.manifest.validate
Update an existing app: apps.manifest.update
Create a new app from manifest: apps.manifest.create
Delete an app: apps.manifest.delete
Most of these API requests are Tier 1 requests, so only on the order of 1+ per minute.
API Access
You'll need to create and maintain "App Configuration Tokens". They're created in the "Your Apps" dashboard. More info about them here.
Example NodeJS Code
const axios = require('axios');
// Change these values:
const TEMPLATE_APP_ID = 'ABC1234XYZ';
const PUBLIC_URL = 'https://www.example.com/my/endpoint';
let access = {
slackConfigToken: "xoxe.xoxp-1-MYTOKEN",
slackConfigRefreshToken: "xoxe-1-MYREFRESHTOKEN",
slackConfigTokenExp: 1648550283
};
// Helpers ------------------------------------------------------------------------------------------------------
// Get a new access token with the refresh token
async function refreshTokens() {
let response = await axios.get(`https://slack.com/api/tooling.tokens.rotate?refresh_token=${access.slackConfigRefreshToken}`);
if (response.data.ok === true) {
access.slackConfigToken = response.data.token;
access.slackConfigRefreshToken = response.data.refresh_token;
access.slackConfigTokenExp = response.data.exp;
console.log(access);
} else {
console.error('> [error] The token could not be refreshed. Visit https://api.slack.com/apps and generate tokens.');
process.exit(1);
}
}
// Get an app manifest from an existing slack app
async function getManifest(applicationID) {
const config = {headers: { Authorization: `Bearer ${access.slackConfigToken}` }};
let response = await axios.get(`https://slack.com/api/apps.manifest.export?app_id=${applicationID}`, config);
if (response.data.ok === true) return response.data.manifest;
else {
console.error('> [error] Invalid could not get manifest:', response.data.error);
process.exit(1);
}
}
// Create a slack application with the given manifest
async function createDevApp(manifest) {
const config = {headers: { Authorization: `Bearer ${access.slackConfigToken}` }};
let response = await axios.get(`https://slack.com/api/apps.manifest.create?manifest=${encodeURIComponent(JSON.stringify(manifest))}`, config);
if (response.data.ok === true) return response.data;
else {
console.error('> [error] Invalid could not create app:', response.data.error);
process.exit(1);
}
}
// Verify that a manifest is valid
async function verifyManifest(manifest) {
const config = {headers: { Authorization: `Bearer ${access.slackConfigToken}` }};
let response = await axios.get(`https://slack.com/api/apps.manifest.validate?manifest=${encodeURIComponent(JSON.stringify(manifest))}`, config);
if (response.data.ok !== true) {
console.error('> [error] Manifest did not verify:', response.data.error);
process.exit(1);
}
}
// Main ---------------------------------------------------------------------------------------------------------
async function main() {
// [1] Check token expiration time ------------
if (access.slackConfigTokenExp < Math.floor(new Date().getTime() / 1000))
// Token has expired. Refresh it.
await refreshTokens();
// [2] Load a manifest from an existing slack app to use as a template ------------
const templateManifest = await getManifest(TEMPLATE_APP_ID);
// [3] Update URLS and data in the template ------------
let devApp = { name: 'Review App', slashCommand: '/myslashcommand' };
templateManifest.settings.interactivity.request_url = `${PUBLIC_URL}/slack/events`;
templateManifest.settings.interactivity.message_menu_options_url = `${PUBLIC_URL}/slack/events`;
templateManifest.features.slash_commands[0].url = `${PUBLIC_URL}/slack/events`;
templateManifest.oauth_config.redirect_urls[0] = `${PUBLIC_URL}/slack/oauth_redirect`;
templateManifest.settings.event_subscriptions.request_url = `${PUBLIC_URL}/slack/events`;
templateManifest.display_information.name = devApp.name;
templateManifest.features.bot_user.display_name = devApp.name;
templateManifest.features.slash_commands[0].command = devApp.slashCommand;
// [5] Verify that the manifest is still valid ------------
await verifyManifest(templateManifest);
// [6] Create our new slack dev application ------------
devApp.data = await createDevApp(templateManifest);
console.log(devApp);
}
main();
Hope this helps anyone else looking to update Slack applications programatically.
No, such a method does not exist in the official documentation. There might be an unofficial method - there are quite a few of them actually - but personally I doubt it.
But you don't need this feature for developing Slack apps. Just simulate the POST calls from Slack on your local dev machine with a script and then do a final test together with Slack on your webserver on the Internet.