How can I work around Access-Control-Allow-Origin in extensions - google-chrome-extension

My requests fail because of the same origin policy, but is there anyway I can work around this in extensions using the webRequest api to modify the headers?

Add this to your background.js file:
/**
* Force Access-Control-Allow-Origin
*
* Ideally, we'll want to remove this for production,
* and actually set the header server side instead.
*/
chrome.webRequest.onHeadersReceived.addListener(function onHeadersReceived(resp) {
var len = resp.responseHeaders.length;
while(--len) {
if(resp.responseHeaders[len].name.toLowerCase() === "access-control-allow-origin") {
resp.responseHeaders[len].value = "*";
break;
}
}
if (len === 0) { //if we didn't find it len will be zero
resp.responseHeaders.push({
'name': 'Access-Control-Allow-Origin',
'value': '*'
});
}
return {responseHeaders: resp.responseHeaders};
}, {
urls: ['*://*.YOU-API-DOMAIN.com/*', '*://localhost/*'],
/*TYPES: "main_frame", "sub_frame", "stylesheet", "script",
"image", "object", "xmlhttprequest", "other" */
types: ['xmlhttprequest']
}, ['blocking', 'responseHeaders']);
And also add these to your manifest.json permissions:
"webRequest",
"webRequestBlocking"
Reload extension and you should be good to go!

Related

How to append HTTP headers and append query parameters in one request using declarativeNetRequest rules?

With Manifest V2, it is possible to have access to the sequence of events in webRequest API.
I can easily block requests, modify headers, append additional query parameters and then redirect to constructed URL.
The outcome is that there is one request that is redirected to the newly constructed URL, which has all modifications (including modified headers and new query parameters).
Now, I'm trying to do the same in Manifest V3 using declarativeNetRequest API. I cannot find any official docs on how could I have the same functionality in MV3.
I have defined two dynamic rules:
addParameterToRequest: {
addRules: [
{
id: 1,
priority: 1,
action: {
type: 'redirect',
redirect: {
transform: {
queryTransform: {
addOrReplaceParams: [{key: data.key, value: data.value}],
},
},
},
},
condition: {
urlFilter: data.urlPattern,
resourceTypes: ['main_frame'],
},
},
],
removeRuleIds: [1],
},
addCookie: {
addRules: [
{
id: 3,
priority: 1,
action: {
type: 'modifyHeaders',
responseHeaders: [
{header: 'Cookie', operation: 'append', value: data.value},
],
},
condition: {
urlFilter: data.urlPattern,
resourceTypes: ['main_frame'],
},
},
]
removeRuleIds: [3],
}
In MV3, the outcome is that I have two requests - one which was redirected with an appended query parameter, and the second one with a modified header.
Manifest V2 results:
Original URL = https://localhost/route
Modified URL = redirected https://localhost/route?qparam=value with appended value in Cookie header
Manifest V3 results:
Original URL = https://localhost/route
Modified URL #1 = redirected https://localhost/route?qparam=value
Modified URL #2 = https://localhost/route with appended value in Cookie header
I tried to somehow combine these two rules into one, but with no success. I tried to play with priority in these rules, but whatever I do, the outcome is that I always have two requests instead of one.
How can I achieve the same functionality in MV3?

Chrome Extension Content Policy Directive Error

I published a Chrome extension that lets the user change the background image on a certain website using the image's url which worked fine, however, recently it stopped work with the following error. It was working even after months in the store.
*Refused to load the image 'https://preview.redd.it/5qz0nzspaq481.png?auto=webp&s=767213b884285c0caba56175ac4a231d2764871b' because it violates the following Content Security Policy directive: "img-src 'self' data: *.cloudfront.net *.google-analytics.com .kaptcha.com
background.js
var inputParent = document.getElementsByClassName(
"nav rbx-navbar hidden-xs hidden-sm col-md-5 col-lg-4"
)[0];
// Dropdown button
var dropdownContainer = document.createElement("div");
var dropdownBtn = document.createElement("a");
var dropdownChild = document.createElement("div");
// Dp Container
dropdownContainer.id = "dropdown";
dropdownContainer.className = "cursor-pointer";
// Dp Button
dropdownBtn.id = "dropbtn";
dropdownBtn.className = "font-header-2 nav-menu-title text-header";
dropdownBtn.innerText = "Theme";
// Menu
dropdownChild.id = "myDropdown";
dropdownChild.className = "dropdown-content";
var newDiv = document.createElement("div");
newDiv.className = "font-header-2 nav-menu-title text-header";
newDiv.innerText = "Theme";
// Appends
inputParent.appendChild(dropdownContainer);
inputParent.appendChild(dropdownBtn);
inputParent.appendChild(dropdownChild);
// Input
var input = document.createElement("input");
input.id = "theme-url-input";
input.type = "text";
input.placeholder = "Paste image URL here...";
//input.value = "Image URL...";
// Save
var save = document.createElement("button");
save.id = "save-url";
save.innerText = "Save";
// Update
var update = document.createElement("button");
update.id = "submit-url";
update.innerText = "Update";
dropdownChild.appendChild(input);
dropdownChild.appendChild(save);
dropdownChild.appendChild(update);
document.getElementById("dropbtn").onclick = function () {
document.getElementById("myDropdown").classList.toggle("show");
};
// Close the dropdown menu if the user clicks outside of it
window.onclick = function (e) {
if (
!e.target.matches("#dropbtn") &&
!e.target.matches("#myDropdown") &&
!e.target.matches("#theme-url-input") &&
!e.target.matches("#submit-url") &&
!e.target.matches("#save-url")
) {
var dropdowns = document.getElementsByClassName("dropdown-content");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains("show")) {
openDropdown.classList.remove("show");
}
}
}
};
// Change background and Save
document.getElementById("save-url").onclick = function () {
var value = input.value;
var background = document.getElementsByClassName("container-main")[0];
// Set background URL
chrome.storage.sync.set({ url: value }, function () {
if (value == "") {
background.style.backgroundImage =
"linear-gradient(transparent 40%, #20283c 90%),url(" + url + ")";
}
console.log(url);
});
};
var background = document.getElementsByClassName("container-main")[0];
// Get background URL
chrome.storage.sync.get("url", function (data) {
background.style.backgroundImage =
"linear-gradient(transparent 40%, #20283c 90%),url(" + data.url + ")";
});
document.getElementById("submit-url").onclick = function () {
if (input.value !== "") {
location.reload();
return false;
}
};
Manifest
// Manifest
{
"manifest_version": 3,
"name": "extension name
",
"description": "some desc.",
"version": "1.2.1",
"icons": {"128": "icon_128.png"},
"permissions": [
"storage"
],
"content_security_policy": {
"extension_pages": "img-src 'self' data: *.cloudfront.net *.google-analytics.com .kaptcha.com *.redd.it"
},
"content_scripts": [{
"css": ["style.css","style.scss"],
"js": ["content.js","background.js"],
"matches": ["https://www.website.com/*"]
}],
"action": {
"default_icon": "icon_128.png",
"default_title" : "Extension",
"default_popup" : "popup.html"
}
}
This is because of CSP in your manifest file. Check your manifest.json file and look for the key content_security_policy. If you're on manifest V2, your manifest.json should look like this:
{
"name": "<your_extension_name>",
"description": "<your_extension_description>",
"version": "1.0",
"manifest_version": 2,
...
...
"content_security_policy": "<your CSP policy>" <--- you need to edit this
...
}
While if you're on manifest V3, manifest.json would look like this for you:
{
"name": "<your_extension_name>",
"description": "<your_extension_description>",
"version": "1.0",
"manifest_version": 3,
...
...
"content_security_policy": {
"extension_pages": "<your CSP policy>" <--- you need to edit this
}
...
}
You're trying to load an image which is from this origin: https://preview.redd.it which is a violation of your existing CSP. Edit the line highlighted in above example files to include src from *.redd.it.
So the new policy for img-src should be:
"img-src 'self' data: *.cloudfront.net *.google-analytics.com .kaptcha.com *.redd.it
Edit: Assuming your extension loads only images from different origins. Updated policy that should work:
default-src 'self'; connect-src * data: blob: filesystem:; style-src 'self' data: 'unsafe-inline'; img-src 'self' data: *.cloudfront.net *.google-analytics.com .kaptcha.com *.redd.it; frame-src 'self' data:; font-src 'self' data:; media-src * data: blob: filesystem:;

reason: actual value has 1 more key(s) than expected:

I am running karate tests.
I am posting a request object and i recieve a response object . however in the response object i have one extra field populated .How will i handle the scenario ?
the following is my test
#NFRSubscription.feature
Feature: NFR Subscription API Tests
Background:
* url __arg.test_service.url
* configure ssl = false
* configure httpVersion = 'http2'
Scenario: Create/Patch/Delete NRF subscription by nfInstanceId subscription condition
* def subscriptionId = ''
* def createSubscriptionByNfInstanceIdRequest = read('subscription/subscriptionCreateRequest.json')
* def patchSubscriptionByNfInstanceIdResponse = read('subscription/subscriptionPatchResponse.json')
* def createSubscriptionByNfInstanceIdResponse = createSubscriptionByNfInstanceIdRequest
# Create the NRF subscription by nfInstanceId
Given path '/nnrf-nfm/v1/subscriptions/'
And header Content-Type = 'application/json; charset=utf-8'
And print createSubscriptionByNfInstanceIdRequest
And request createSubscriptionByNfInstanceIdRequest
When method post
Then status 201
And match response == createSubscriptionByNfInstanceIdResponse
And match response.subscriptionId == '#present'
And print response
the content of subscription/subscriptionCreateRequest.json is as follows.
{
"nfStatusNotificationUri": "http://localhost:8080/ip/test",
"reqNfInstanceId": "9c79364e-99e1-42a8-ada1-86c31ad1fa76",
"subscrCond": {
"nfInstanceId": "9c79364e-99e1-42a8-ada1-86c31ad1fa76"
},
"validityTime": "2022-02-18T10:15:15Z",
"reqNotifEvents": [
"NF_REGISTERED"
],
"plmnId": {"mcc": "454", "mnc" : "93" },
"nid": "2465aEB5ff1",
"notifCondition": {
"monitoredAttributes": [
"testattri"
]
},
"reqNfType": "NRF",
"reqNfFqdn": "com.openet.com",
"reqSnssais": [
{ "sst": 10, "sd": "aA82a7" }
],
"reqPlmnList": [
{"mcc": "454", "mnc" : "93" }
],
"reqSnpnList": [
{"mcc": "454", "mnc" : "93" }
],
"servingScope": [
"any string"
],
"nrfSupportedFeatures": "CBAdEd1bef9B118EAd8d5bAfc66B59c1D292fD821d"
}
the content of the file subscription/subscriptionPatchResponse.json is as follows.
{
"nfStatusNotificationUri": "http://localhost:8080/ip/test",
"reqNfInstanceId": "9c79364e-99e1-42a8-ada1-86c31ad1fa76",
"subscrCond": {
"nfInstanceId": "9c79364e-99e1-42a8-ada1-86c31ad1fa76"
},
"validityTime": "2023-03-07T23:20:50Z",
"reqNotifEvents": [
"NF_REGISTERED"
],
"plmnId": {"mcc": "454", "mnc" : "93" },
"nid": "2465aEB5ff1",
"notifCondition": {
"monitoredAttributes": [
"testattri"
]
},
"reqNfType": "NRF",
"reqNfFqdn": "com.openet.com",
"reqSnssais": [
{ "sst": 10, "sd": "aA82a7" }
],
"reqPlmnList": [
{"mcc": "454", "mnc" : "93" }
],
"reqSnpnList": [
{"mcc": "454", "mnc" : "93" }
],
"servingScope": [
"any string"
],
"nrfSupportedFeatures": "CBAdEd1bef9B118EAd8d5bAfc66B59c1D292fD821d"
}
in the response I would expect a new field called subscriptionId .
my test fails at the moment with the following error .
16:00:57 com.intuit.karate.exception.KarateException: NFRSubscription.feature:24 - path: $, actual: {nfStatusNotificationUri=http://localhost:8080/ip/test, reqNfInstanceId=9c79364e-99e1-42a8-ada1-86c31ad1fa76, subscrCond={nfInstanceId=9c79364e-99e1-42a8-ada1-86c31ad1fa76}, subscriptionId=608425, validityTime=2022-02-18T10:15:15Z, reqNotifEvents=["NF_REGISTERED"], plmnId={mcc=454, mnc=93}, nid=2465aEB5ff1, notifCondition={monitoredAttributes=["testattri"]}, reqNfType=NRF, reqNfFqdn=com.openet.com, reqSnssais=[{"sst":10,"sd":"aA82a7"}], reqPlmnList=[{"mcc":"454","mnc":"93"}], reqSnpnList=[{"mcc":"454","mnc":"93"}], servingScope=["any string"], nrfSupportedFeatures=CBAdEd1bef9B118EAd8d5bAfc66B59c1D292fD821d}, expected: {nfStatusNotificationUri=http://localhost:8080/ip/test, reqNfInstanceId=9c79364e-99e1-42a8-ada1-86c31ad1fa76, subscrCond={nfInstanceId=9c79364e-99e1-42a8-ada1-86c31ad1fa76}, validityTime=2022-02-18T10:15:15Z, reqNotifEvents=["NF_REGISTERED"], plmnId={mcc=454, mnc=93}, nid=2465aEB5ff1, notifCondition={monitoredAttributes=["testattri"]}, reqNfType=NRF, reqNfFqdn=com.openet.com, reqSnssais=[{"sst":10,"sd":"aA82a7"}], reqPlmnList=[{"mcc":"454","mnc":"93"}], reqSnpnList=[{"mcc":"454","mnc":"93"}], servingScope=["any string"], nrfSupportedFeatures=CBAdEd1bef9B118EAd8d5bAfc66B59c1D292fD821d}, reason: actual value has 1 more key(s) than expected: {subscriptionId=608425}
any idea how should i formulate createSubscriptionByNfInstanceIdResponse object to handle an extra dynamically generated field . in other words how can i do the following
add response.subscriptionId into createSubscriptionByNfInstanceIdResponse and then compare response and
createSubscriptionByNfInstanceIdResponse object .
And match response == createSubscriptionByNfInstanceIdResponse
I have changed the test to add the subscriptionId field in the response object and then asserted it .
# Create the NRF subscription by nfInstanceId
Given path '/nnrf-nfm/v1/subscriptions/'
And header Content-Type = 'application/json; charset=utf-8'
And print createSubscriptionByNfInstanceIdRequest
And request createSubscriptionByNfInstanceIdRequest
When method post
Then status 201
And match response.subscriptionId == '#present'
And print response
And set createSubscriptionByNfInstanceIdResponse.subscriptionId = response.subscriptionId
And match response == createSubscriptionByNfInstanceIdResponse

how to make gtag to work in chrome extension?

I've added gtag.js to my chrome extension but I don't see anything on the nework, please tell me what I did wrong.
This is my CSP in manifest.json
{
"content_security_policy": "script-src 'self' https://www.googletagmanager.com https://ssl.google-analytics.com https://www.google-analytics.com https://mustsee-earth.firebaseio.com; object-src 'self'"
}
This is my index.html used by my extension (which replaces the user's default tab)
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>mustsee.earth</title>
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-XXXXXXXXX-X"></script>
</head>
Here's how I trigger views and events
gtag('config', GATID, {
page_title: place.name,
page_path: path
})
gtag('event', binding_value.action, {
event_category: binding_value.category,
event_label: binding_value.label,
value: binding_value.value
})
Although I followed every step, here's what I have on the network : nothing.
Here's the dataLayer var which proves my events are added to the queue but not triggered
[
{
"0": "js",
"1": "2018-04-24T21:02:54.881Z"
},
{
"0": "config",
"1": "UA-XXXXXXXXX-X",
"2": {
"checkProtocolTask": null,
"custom_map": {
"dimension5": "under 1.5 or failed"
}
}
},
{
"0": "config",
"1": "UA-XXXXXXXXX-X",
"2": {
"page_title": "Mesquite Flat Sand Dunes",
"page_path": "/mesquite-flat-oleksandr-mokrohuz-small.jpg"
}
},
{
"0": "event",
"1": "click on reload",
"2": {
"event_category": "Image View"
}
}
]
What can the issue be here ?
Adding gtm in the Chrome Extension is a bit tricky job. I had faced the same issues you are facing now. However, this is possible to implement gtm in CE.
Your manifest looks fine. You need to do some configuration changes in https://tagmanager.google.com/
You must add checkProtocolTask : false to each gtm tag in order to track them from Google Chrome Extension.
Add checkProtocolTask : false to Fields to Set
Scroll down to Fields to Set, and add a new field:
Field Name: checkProtocolTask
Value: false
See this SO post for more details.
gtag('config', GATID, {
page_title: place.name,
page_path: path
})
gtag = (function (old_gtag) {
var inited = false;
return function gtag() {
if (!inited && window.ga && window.ga.getAll) {
window.ga.getAll().forEach(function (tracker) {
tracker.set("checkProtocolTask", null);
inited = true;
});
}
if (inited) return old_gtag.apply(this, arguments);
var args = arguments;
setTimeout(function () {
gtag.apply(this, args);
}, 300);
};
})(gtag);
gtag('event', binding_value.action, {
event_category: binding_value.category,
event_label: binding_value.label,
value: binding_value.value
})

Change user-agent headers only in incognito mode with a Chrome extension

I'm trying to have my extension only work while in incognito mode. I can't seem to get it to work by adding "incognito": "split" to my manifest.json, and adding an if statement in the background.js looking for chrome.extension.inIncognitoContext.
Edit:
Okay, so what I'm trying to do is change my user-agent headers while only in incognito. I'm doing this based off this: http://elaineou.com/2016/02/19/how-to-use-chrome-extensions-to-bypass-paywalls/
It works for the said link above, but I can't seem to get it to work for all website while in incognito mode. This also won't be released, and I do have the allowed in incognito button checked.
manifest.json
{
"name": "Incognito Chrome Extension",
"version": "0.1",
"description": "This is an incognito chrome extension.",
"incognito": "split",
"permissions": ["webRequest", "webRequestBlocking",
"http://localhost:3000/",
"http://*/*",
"https://*/*"
],
"background": {
"scripts": ["background.js"]
},
"manifest_version": 2
}
Background.js
var ALLOW_COOKIES = [""];
if(chrome.extension.inIncognitoContext){
function changeRefer(details) {
foundReferer = false;
foundUA = false;
var reqHeaders = details.requestHeaders.filter(function(header) {
// block cookies by default
if (header.name !== "Cookie") {
return header;
}
allowHeader = ALLOW_COOKIES.map(function(url) {
if (details.url.includes(url)) {
return true;
}
return false;
});
if (allowHeader.reduce(function(a, b) { return a || b}, false)) { return header; }
}).map(function(header) {
if (header.name === "Referer") {
header.value = "https://www.google.com/";
foundReferer = true;
}
if (header.name === "User-Agent") {
header.value = "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko";
foundUA = true;
}
return header;
});
// append referer
if (!foundReferer) {
reqHeaders.push({
"name": "Referer",
"value": "https://www.google.com/"
});
}
if (!foundUA) {
reqHeaders.push({
"name": "User-Agent",
"value": "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"
});
}
console.log(reqHeaders);
return {requestHeaders: reqHeaders};
}
function blockCookies(details) {
for (var i = 0; i < details.responseHeaders.length; ++i) {
if (details.responseHeaders[i].name === "Set-Cookie") {
details.responseHeaders.splice(i, 1);
}
}
return {responseHeaders: details.responseHeaders};
}
chrome.webRequest.onBeforeSendHeaders.addListener(changeRefer, {
urls: ["<all_urls>"],
types: ["main_frame"],
}, ["requestHeaders", "blocking"]);
chrome.webRequest.onHeadersReceived.addListener(blockCookies, {
urls: ["<all_urls>"],
types: ["main_frame"],
}, ["responseHeaders", "blocking"]);
}
I guess that your concern is the extra redundant background page process?
Add "incognito": "split" in the manifest file and close the extension's background page in non-incognito mode:
// background page or event page:
if (!chrome.extension.inIncognitoContext) {
window.close();
}
Content scripts will still be run in non-incognito pages. To counter that, just exit your code after checking whether your extension is running in incognito mode (similar to the above check).
Note: If the extension is going to be published and only useful in incognito mode, consider checking whether incognito mode is enabled and offer instructions if it's disabled. See e.g. How can I enable my chrome extension in incognito mode?

Resources