Facebook must have changed something in their Content Security Policy header, because my Firefox Add-on suddenly stopped working.
I need to get a remote XML file, using XMLHttpRequest, and my domain of course isn't included in Facebook CSP (in Firefox console, I see an error with the connect-src policy of the page). It was working like a charm, until today.
It is worth nothing that Firefox Add-ons should not be affected by CSP of the server (this in theory).
So, I need to override Facebook's CSP in order to get my Add-on working again. I found this code, but it's for Chrome Extensions.
chrome.webRequest.onHeadersReceived.addListener(function (details)
{
for (i = 0; i < details.responseHeaders.length; i++) {
if (details.responseHeaders[i].name.toUpperCase() == "X-WEBKIT-CSP") {
details.responseHeaders[i].value = "default-src *;script-src https://*.feedhound.co https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.google-analytics.com *.virtualearth.net *.google.com 127.0.0.1:* *.spotilocal.com:* chrome-extension://lifbcibllhkdhoafpjfnlhfpfgnpldfl 'unsafe-inline' 'unsafe-eval' https://*.akamaihd.net http://*.akamaihd.net;style-src * 'unsafe-inline';connect-src https://*.facebook.com http://*.facebook.com https://*.fbcdn.net http://*.fbcdn.net *.facebook.net *.spotilocal.com:* https://*.akamaihd.net ws://*.facebook.com:* http://*.akamaihd.net https://*.feedhound.co";
}
}
return {
responseHeaders : details.responseHeaders
};
}, {
urls : ["*://*.facebook.com/*"],
types : ["main_frame", "sub_frame", "stylesheet", "script", "image", "object", "xmlhttprequest", "other"]
},
["blocking", "responseHeaders"]
);
Someone can help me implementing something similar for a Firefox Add-on?
I already tried an alternative method, adding my own "content-src" security header with setRequestHeader but without any luck.
Thanks,
Roberto
Thank you, in the meantime I tried a similar solution, but I found that unfortunately it won't be allowed by Mozilla (my fix has been rejected by the reviewer).
The problem is that modifying CSP headers, by setting a new server response with setResponseHeader, may lead to security issues.
I guess that the only acceptable solution would be to run the code inside the chrome, instead of the content, so it wouldn't be affected by server's CSP.
This was my fix:
var MYADDON_CSP_listener = {
observe : function(aSubject, aTopic, aData) {
if (aTopic == "http-on-examine-response") {
let url;
aSubject.QueryInterface(Components.interfaces.nsIHttpChannel);
url = aSubject.URI.spec;
if (/https?:\/\/www.facebook.com\//.test(url)) {
var csp = aSubject.getResponseHeader("content-security-policy");
csp = csp.replace('connect-src', 'connect-src http://*.mywebsite.com https://*.mywebsite.com');
aSubject.setResponseHeader("content-security-policy", csp, false);
}
}
}
};
var MYADDON_observerService = Components.classes["#mozilla.org/observer-service;1"]
.getService(Components.interfaces.nsIObserverService);
MYADDON_observerService.addObserver(MYADDON_CSP_listener, "http-on-examine-response", false);
I have the same issue and was able to resolve it. Look at the solution i have posted here
https://stackoverflow.com/a/19917664/297113
That may help you.
Related
I have integrated Twilio Programmable Voice. Now I am trying to make client to call from browser and also receive calls to browser using Twilio JS Client. When I try to make the call it says.
message: "Capability token is not valid or missing."
I have searched the internet and Twilio documentation as well but nothing is helping me. Here is my code.
View
<button onclick="callCustomer('{{ '+xxxxxxxx' }}')" type="button" class="btn btn-primary btn-lg call-customer-button">
Call customer
</button>
Controller
public function newToken(Request $request) {
// Required for all Twilio access tokens
$twilioAccountSid = 'ACxxxxxxxxx';
$twilioApiKey = 'SKxxxxxxxxxxx';
$twilioApiSecret = 'xxxxxx';
$outgoingApplicationSid = 'APxxxxxxxxxxx';
$identity = "Jhon_Doe";
$token = new AccessToken(
$twilioAccountSid, $twilioApiKey, $twilioApiSecret, 3600, $identity
);
$voiceGrant = new VoiceGrant();
$voiceGrant->setOutgoingApplicationSid($outgoingApplicationSid);
$voiceGrant->setIncomingAllow(true);
$token->addGrant($voiceGrant);
return $token->toJWT();
}
JS
function callCustomer(phoneNumber) {
$.get("/token", {forPage: window.location.pathname}, function (data) {
console.log(data);
const device = new Twilio.Device();
device.setup(data);
var params = {"phoneNumber": phoneNumber};
device.connect(params);
});
}
I have tried to debug my token from jwt.io debug tool and it says:
Invalid Signature.
I am sure that I am missing something but can't figure it out what. Any help or tip will be appreciated.
I have figured it out just posting answer for future reference. The issue was of twilio js version. I had included the older version which was causing the issue. The lates version to this date is Version: 1.13. Capability token is deprecated in this version and uses Access Token now.
The following oAuth2 SSO code in my Office.js addin application works great in IE11, Chrome, Safari and Firefox, but doesn't work in Microsoft Edge. I can see the bearer token is being returned to the pop-up dialog via the url:
https://localhost:3000/login?access_token=ya29.ImG6By-0ZWPQB4MsYxxxxxxxxxxxxxxxxxxxxxxxxxxxxE5XsM9v7SBi-OaUBBQucO05luKVP0pYoSrcYzbaUKAAX&token_type=Bearer
I can also see that the asyncResult.status == succeeded, i.e.
[object Object]: {status: "succeeded", value: Object}
status: "succeeded"
value: Object
addEventHandler: function (){var d=OSF.DDA.SyncMethodCalls[OSF.DDA.SyncMethodNames.AddMessageHandler.id],c=d.verifyAndExtractCall(arguments,a,b),e=c[Microsoft.Office.WebExtension.Parameters.EventType],f=c[Microsoft.Office.WebExtension.Parameters.Handler];return b.addEventHandlerAndFireQueuedEvent(e,f)}
arguments: null
caller: null
length: 0
name: "value"
prototype: Object
proto: function() { [native code] }
close: function (){var c=OSF._OfficeAppFactory.getHostFacade()[OSF.DDA.DispIdHost.Methods.CloseDialog];c(arguments,g,b,a)}
sendMessage: function (){var c=OSF._OfficeAppFactory.getHostFacade()[OSF.DDA.DispIdHost.Methods.SendMessage];return c(arguments,b,a)}
proto: Object
proto: Object
However, the "console.log('hello');" doesn't get called when Microsoft Edge is running the sidebar/add-in.
The pop-up dialog is showing this in the F12 debug console:
HTTP403: FORBIDDEN - The server understood the request, but is refusing to fulfill it.
(XHR)POST - https://browser.pipe.aria.microsoft.com/Collector/3.0/?qsp=true&content-type=application%2Fbond-compact-binary&client-id=NO_AUTH&sdk-version=AWT-Web-JS-1.1.1&x-apikey=a387cfcf60114a43a7699f9fbb49289e-9bceb9fe-1c06-460f-96c5-6a0b247358bc-7238&client-time-epoch-millis=1579626709267&time-delta-to-apply-millis=961
Any ideas?
export function loginUsingOAuth() {
try {
const sealUrl = getFromStorage('seal_url', STORAGE_TYPE.LOCAL_STORAGE);
const redirectUrl = `${window.location.protocol}//${window.location.host}/login`;
let displayInIframe = false;
let promptBeforeOpen = false;
if (typeof sealUrl !== 'undefined' && sealUrl) {
const oAuthUrl = `${sealUrl}/seal-ws/oauth2/login?redirect_uri=${redirectUrl}`;
Office.context.ui.displayDialogAsync(
oAuthUrl,
{
height: 80,
width: 80,
displayInIframe,
promptBeforeOpen
},
asyncResult => {
console.log('asyncResult');
console.log(asyncResult);
addLog(LOG_TYPE.INFO, 'authentication.loginUsingOAuth', asyncResult);
if (asyncResult.status !== 'failed') {
const dialog = asyncResult.value;
dialog.addEventHandler(Office.EventType.DialogMessageReceived, args => {
console.log('hello');
Maybe this is actually a routing issue when executing in Edge? The "/login" callback is routed to the AuthCallback.js component:
const Routes = () => (
<BrowserRouter>
<Switch>
<Route exact path="/login" component={AuthCallback} />
<Route path="/" component={BaseLayout} />
</Switch>
</BrowserRouter>
);
The constructor of the AuthCallback.js component calls messageParent after a short pause:
constructor(props) {
super(props);
const paramsObj = queryString.parse(props.location.search);
const paramsStr = JSON.stringify(paramsObj);
setTimeout(() => {
Office.context.ui.messageParent(paramsStr);
}, 1200);
}
I'm starting to wonder if Edge is messing with the redirect. In the image below you can see that IE and Edge are returning different status codes for the same sign-on operation:
There seems to be two problems with the Edge browser.
The redirect/callback is not calling the components constructor when displayInIframe=false when running on Microsoft Edge. All other browsers work as expected. I've added conditional logic to set displayInIframe=true for the Edge browser use-case
The messageParent method also does not work for the Edge browser when displayInIframe=true. Therefore I've had to extract the auth token in the pop-up dialog callback and stash it away in the local_storage. The parent (the sidebar) is then polling the local_storage to detect that the sign-in has completed. Again, Chrome, Firefox, Safari, IE11 (both Mac and PC) are all fine - its just the Edge browser that is failing.
Whilst this is an ugly solution to the problem it is also imperfect because IF the end-user is not already signed-in to SSO then the Google [Account Selector] dialog is shown, which is a problem when displayInIframe=true as this throws an iframe exception.
I don't see any other option open to us, because the O/S build number and MSWord version dictates which browser is used to render the sidebar. The inability to choose whether IE11 or Edge is used would be bearable if Edge didn't have these functional deficits.
Since today when I try to get the share count the answer is :share field is deprecated for versions v2.9 and higher.
Ex with : https://graph.facebook.com/?id=https://stackoverflow.com&fields=share
Without &fields=share the json content is displayed but without the share value.
I need to get the share count Facebook from an url.
The API has changed indeed.
It should be like this.
https://graph.facebook.com/?id=https://stackoverflow.com&fields=engagement&access_token=user-access-token
You need an access token. If you have a Facebook, go to https://developers.facebook.com/ and make an app.
Graph API Explorer
Then click "Graph API Explorer".
Get Token
and "Get Token" (Get App Token). That's it.
If you use JavaScript for a count, it's will be something like this.
// split('#')[0] : Remove hash params from URL
const url = encodeURIComponent( window.location.href.split('#')[0] );
$.ajax( {
url : '//graph.facebook.com/?id=' + url + '&fields=engagement&access_token=user-access-token',
dataType : 'jsonp',
timeout: 5000,
success : function( obj ) {
let count = 0;
if ( typeof obj.engagement.reaction_count !== 'undefined' ) {
count = obj.engagement.reaction_count;
}
// do something with 'count'
},
error : function() {
// do something
}
} );
There are other count types such as comment_count and share_count.
See https://developers.facebook.com/docs/graph-api/reference/v3.2/url
Is there any way to receive a count without sending an access token?
I wanna know that myself lol
UPDATE:
Thanks to Anton Lukin.
Yeah. I shouldn't show an access token. It must be hidden. I feel very foolish.
So now quick's answer. This really works without the token!
My final (I hope will be final) answer is like this.
// split('#')[0] : Remove hash params from URL
const url = encodeURIComponent( window.location.href.split('#')[0] );
$.ajax( {
url: '//graph.facebook.com/?id=' + url + '&fields=og_object{engagement}',
dataType : 'jsonp',
timeout: 5000,
success : function( obj ) {
let count = 0;
try {
count = obj.og_object.engagement.count
} catch (e) {
console.log(e)
}
// do something with 'count'
},
error : function() {
// do something
}
} );
One point here is that when nobody has ever shared the targeted page, 'og_object.engagement' isn't even defined.
I thought I'd get 0 as a return valule. But that's not the case.
So let's use try-catch.
Now my only concern is API Limits. If your site gets a lot of pageviews, this updated version may not work..
If you do not want use access token or nginx proxy solution, see https://stackoverflow.com/a/45796935/2424880:
You can use the query
https://graph.facebook.com?id=<your-url>&fields=og_object{engagement}
The answer will be
{
"og_object": {
"engagement": {
"count": 197,
"social_sentence": "197 people like this."
},
"id": "895062470590407"
},
"id": "<your-url>"
}
UPDATE 2021: You need access token for this request. You can get temporary access token in Graph API Explorer or generate it with your custom app
Since you can't display your access token on front-end, I suggest you to proxy requests with nginx, hidding your access_token on your server.
You need an access token. Navigate to https://developers.facebook.com/ and make an app.
Go to Graph explorer and copy the token. To obtain permanent token follow this short guide
Add custom rule to your nginx config
http {
...
# Optional: set facebook cache zone
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=facebook:100m inactive=60m;
...
}
server {
server_name example.org;
...
location /facebook {
# Optional: don't log requests
access_log off;
log_not_found off;
# Allow get shares only for single domain (remove condition to allow all domains)
if ( $arg_id ~ "^https://example.org/" ) {
set $args"${args}&access_token=your_access_token_here";
}
# Set dns resolver address (you can change it with any dns server)
resolver 1.1.1.1;
proxy_pass https://graph.facebook.com?$args;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# Optional: add cache for 30 minutes
proxy_ignore_headers Expires;
proxy_ignore_headers Cache-Control;
proxy_cache facebook;
proxy_cache_valid any 30m;
proxy_cache_key $host$uri$is_args$arg_id;
}
...
}
Now you can make response replacing graph.facebook.com with your custom domain.
Before:
https://graph.facebook.com/?fields=engagement&callback=FB.Share&id=https://example.org/&access_token=your_access_token
After:
https://example.org/facebook?fields=engagement&callback=FB.Share&id=https://example.org/
Pay attention to facebook api limits. If you have a large number of requests you can try to use page token. For each engagement user to your page you can make 4800 requests to graph api per day.
If you have app in facebook, its very simple without login, you can get it.
https://graph.facebook.com/?id={URL}&fields=engagement&access_token={your-app_id}|{your-app_secret}
Response will be like :
{
"engagement": {
"reaction_count": 36,
"comment_count": 2,
"share_count": 20,
"comment_plugin_count": 3
},
"id": "https://www.example.com"
}
Ref : https://developers.facebook.com/docs/facebook-login/access-tokens
Can I get a sample code to set basic authorization as header along with other headers ( like x-csrf-token : fetch) in eclipse ?
You can do something like this with jQuery (which is of course included with UI5) for basic authentication:
function ajaxBeforeSend(xhr) {
xhr.setRequestHeader("Authorization", "Basic " + btoa(user + ":" + password));
}
$.ajax({
type: "GET",
url: url,
dataType: "json",
beforeSend: function(xhr) {
ajaxBeforeSend(xhr);
}
}).done(function(data) { /* do something */ }
This is what I've used in some developments and it works well. You can set other headers this way as well.
See http://www.w3schools.com/jsref/met_win_btoa.asp for details on btoa() which base64 encodes the user:pass string.
Your question says: "in eclipse". I don't know what that means as the javascript will work regardless of what editor you use.
Here's the jQuery doco which describes the method used above: http://api.jquery.com/jQuery.ajax/.
(Watch out for CORS issues if service is not on the same host as your app. For CORS I find you also need to add xhr.withCredentials = true; inside the above ajaxBeforeSend() function.)
I am attempting to set up a chrome extension similar to http://code.google.com/chrome/extensions/trunk/samples.html#webrequest except that it would us the onErrorOccurred listener instead to redirect to a specific known page when the get request fails for any reason.
manifest.json:
{
"name": "Custom Error",
"version": "0.1",
"description": "Redirect all navigation errors to specified location/file.",
"permissions": [
"webRequest",
"tabs",
"<all_urls>"
],
"background": {
"page": ["error_listener.html"]
}
}
error_listener.html:
<!doctype html>
<script>
chrome.webRequest.onErrorOccurred.addListener(
function onErrorOccurred(details) {
console.log('onBeforeRequest ', details.url);
return { redirectUrl: 'http://www.google.com' }
},
{urls: ["<all_urls>"]}
//{urls: ["http://*/*", "https://*/*"]}
);
//chrome.tabs.update(details.tabId, {url: "http://www.google.com", ['blocking']});
//alert("what?");
</script>
The extension loads without any errors indicated yet the browser tab is not redirected. I have tried this using both Chrome 16 and Chrome 17; when using Chrome 16, I did change "chrome.webRequest" to "chrome.experimental.webRequest" and added "experimental" to the permissions list.
So far it seems like the problem is that while the extension appears to be loaded when looking at chrome://extensions, the files are not actually loaded--when using Developer Tools, I don't see any reference to error_listener.html.
I have also tried running Chrome 17 with the following flags:
8611 25/01/12-11:22:05> google-chrome --restore-last-session
--debug-on-start --log-level=0 --enable-logging
--enable-extension-activity-logging --enable-extension-alerts
--debug-plugin-loading --debug-print | tee > log1.txt
Obviously, I am just kind of poking around in the dark with that command line. Anyone have any clue as to how to get this working? Thanks in advance for your help!
There is no webRequest.onErrorOccurred event. You may use webNavigation.onErrorOccurred. If you want to catch DNS errors and redirect to another url you may use the code:
<script>
chrome.webNavigation.onErrorOccurred.addListener(function(details)
{
if (details.frameId != 0) //ignore subframes. 0 is main frame
{ return; }
chrome.tabs.update(details.tabId, {url: "https://www.google.com/search?q=" + details.url});
});
</script>
In Chrome 16, you should be using:
"background_page": "background.html"
rather than
"background": {
"page": ["error_listener.html"]
}
This fixes the problem with the background page not loading. It looks like this may have changed in the trunk docs compared to the current docs, and I'm not sure which version of Chrome starts implementing the new manifest.json format.