Why does a rule added and immediately removed with declarativeNetRequest.updateDynamicRules not get removed even after the extension is reloaded? - google-chrome-extension

I am trying to write an extension in Manifest Version 3, where I want to modify cookie headers for certain requests. Since the rule will only be applied to specific requests that meets my conditions,
I thought of adding a dynamic rule temporarily for that request, modify the cookie header, and immediately remove it. Here's the function for that rule.
if (condition) {
function makeNewRule(url) {
chrome.declarativeNetRequest.updateDynamicRules(
{
addRules:
[
{
"id": 1000,
"priority": 100,
"action": {
"type": "modifyHeaders",
"requestHeaders": [
{
"header": "cookie",
"operation": "set",
"value": "Modified cookie value 1"
}
]
},
"condition": {
"urlFilter" : url,
"resourceTypes":
["csp_report", "font", "image",
"main_frame", "media", "object",
"other", "ping", "script",
"stylesheet", "sub_frame",
"webbundle", "websocket",
"webtransport"]
}
}
],
removeRuleIds: [1000],
});
}
}
While this works for all requests that meet my condition, and the cookies are being modified observed in the chrome developers tool network window, the rule persists for a later session, even if I reload/update the unpacked extension. If I change the value of the cookie header to ""Modified cookie value 2", the developers tools still shows the previous "Modified cookie value 1". Therefore, I am assuming that the rule that I added is not being removed, and it is persisting across browser sessions. I tried cleaning the cache and reloading the browser. Additionally,
chrome.declarativeNetRequest.getDynamicRules(
e => console.log(e)
);
The snippet above shows the existence of the rule even when removed. How do I remove the rule that I added dynamically within that session?

I update my dynamic rules by removing and then adding them, I'll share my code with you, I hope it helps.
To update rules
function UpdateIntercept(token: string) {
chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: GetInterceptRules(token).map((rule) => rule.id), // remove existing rules
addRules: GetInterceptRules(token)
});
chrome.declarativeNetRequest.getDynamicRules(
e => console.log(e)
);
}
then when I intercept the url
function GetInterceptRules(token: string) {
const allResourceTypes =
Object.values(chrome.declarativeNetRequest.ResourceType);
return [
{
id: 1,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
requestHeaders: [
{
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
header: 'Authorization',
value: 'Bearer ' + token,
},
]
},
condition: {
urlFilter: liveApiUrl,
initiatorDomains: ["mail.google.com"],
resourceTypes: allResourceTypes,
}
},
{
id: 2,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
requestHeaders: [
{
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
header: 'Authorization',
value: 'Bearer ' + token,
},
]
},
condition: {
urlFilter: testApiUrl,
initiatorDomains: ["mail.google.com"],
resourceTypes: allResourceTypes,
}
}
];
}
To intercept the url you want
function interceptURL(requestDetails: chrome.webRequest.WebRequestBodyDetails) {
console.log('intercepted: ' + requestDetails.url);
if (requestDetails.url.includes(liveApiUrl) || requestDetails.url.includes(testApiUrl)) { //maybe the inclue
chrome.runtime.sendMessage({ "message": "refresh_token" }, (token: string) => {
console.log('refreshed token: ' + token)
if (token == undefined) {
chrome.runtime.sendMessage({ "message": "get_token" });
}
});
}
}
I use onBeforeRequest
chrome.webRequest.onBeforeRequest.addListener(
interceptURL,
{ urls: [liveApiUrl, testApiUrl] }
)
In my case, I need to pass a refresh token everytime I intercept that url
still have a couple of things to work on, but hopefully it gives you some direction.

Related

declarativeNetRequest.updateDynamicRules are not updating, how do I properly remove and add rules?

update: I noticed that the token is beinig passed on the first time I run itm, but no new tokens are being appended to my rules.
I have this chrome extension on Gmail where the user logs in and it return an access token, the token is passed to our API through HTTP Request, it works fine the first time around passing the access token, but if I don't refresh Gmail, after 1 hour the access token expires and I get 401 errors on my application. I have a function interceptURL that will match the url and give a refreshed token before the HTTP Request is made (or so I thought).
Maybe after 1h the access token is expired so calling refresh token wont generate a new token?
background script
function interceptURL(requestDetails: chrome.webRequest.WebRequestBodyDetails) {
console.log('intercepted: ' + requestDetails.url);
if (requestDetails.url.includes(liveApiUrl) || requestDetails.url.includes(testApiUrl)) {
chrome.runtime.sendMessage({ "message": "refresh_token" }, (token: string) => {
if (token == undefined) {
chrome.runtime.sendMessage({ "message": "get_token" });
}
});
}
}
chrome.webRequest.onBeforeRequest.addListener(
interceptURL,
{ urls: [liveApiUrl, testApiUrl] }
)
Here are my Rules
function GetInterceptRules(token: string) {
const allResourceTypes =
Object.values(chrome.declarativeNetRequest.ResourceType);
return [
{
id: 1,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
requestHeaders: [
{
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
header: 'Authorization',
value: 'Bearer ' + token,
},
]
},
condition: {
urlFilter: liveApiUrl,
initiatorDomains: ["mail.google.com"],
resourceTypes: allResourceTypes,
}
},
{
id: 2,
priority: 1,
action: {
type: chrome.declarativeNetRequest.RuleActionType.MODIFY_HEADERS,
requestHeaders: [
{
operation: chrome.declarativeNetRequest.HeaderOperation.SET,
header: 'Authorization',
value: 'Bearer ' + token,
},
]
},
condition: {
urlFilter: testApiUrl,
initiatorDomains: ["mail.google.com"],
resourceTypes: allResourceTypes,
}
}
];
My thought was:
1 - I give it a refresh token before every HTTP Request so when I update the dynamic rules, it would pass the new token. (that's what I current have)
2 - I could check when the access token was created and just make sure the code to get token run before the 1 hour ends. (Maybe not the best approach?)
To get the access token
chrome.identity.launchWebAuthFlow(
{
url: azureTokenAuthUrl,
interactive: isInteractive
},
(redirectURL: string) => {
let token: string = '';
if (redirectURL != null) {
let params = new URLSearchParams(redirectURL);
token = params.get("access_token");
}
console.log("acces_token", token);
console.log(redirectURL)
UpdateIntercept(token)
callback(token)
}
Manifest V3
"permissions": [
"webRequest",
"declarativeNetRequest",
"declarativeNetRequestWithHostAccess",
"identity",
"identity.email"
],
"background": {
"service_worker": "/static/js/Background.js"
},
"content_scripts": [
{
"matches": [ "<all_urls>" ],
"css": [ "/css/bootstrap-iso.css" ],
"js": [ "react.production.min.js", "react-bootstrap.min.js", "react-dom.production.min.js" ]
},
{
"matches": [ "*://mail.google.com/*" ],
"css": [ "/css/AuthButton.css" ],
"js": [ "/static/js/AuthButton.js" ]
},
{
"matches": [ "*://mail.google.com/*" ],
"js": [ "/static/js/PushNotification.js" ]
}
],
I've been searching around but can't seem to find a solution for my problem.
I tried using JWT to decode so I know it's expired.
Added the launchAuthFlow with interactivity false to my listener where I check if an email has been opened, if so, trigger launchAuthFlow appending the token to the HTTP Request

Response headers update with declarativeNetRequest update rules not visible in Chrome's DevTools

In my chrome extension V3 I use chrome.declarativeNetRequest.updateSessionRules to modify response headers:
chrome.declarativeNetRequest.updateSessionRules({
addRules: [
{
id: 999,
priority: 1,
condition: {
initiatorDomains: ['localhost'],
resourceTypes: ['main_frame']
},
action: {
type: 'modifyHeaders',
responseHeaders: [
{ header: 'Content-Security-Policy', operation: 'remove' },
{ header: "bar-foo-baz", operation: "set", value: "true" },
],
}
}]
});
The test I do is pretty simple. I violate the Content-Security-Policy rule so I get an error in the console. But when I apply the remove operation all works fine and the error is gone.
But the weird thing is if I look in the DevTools Network tab, I still see the Content-Security-Policy header and I don't see bar-foo-baz.
Does someone know why this is happening?

Microsoft Teams task module with URL not working for external url

trigger a task module that will display a web page. All I was able to get is an empty Task Module with the title, while the specified height and width do not showing URL displayed.
i want to redirect the url from the api.,but api is giving a url but not redirecting
let requestUrl = await getRedirectUrlForSubmitAction(tokenResponse) ===> api call to get the url
const response: MessagingExtensionActionResponse = <MessagingExtensionActionResponse>{
task: {
type: "continue",
value: {
title: "Send recognition",
url: `${request.data.value}`, //url from api call
height: "large"
}
}
};
return Promise.resolve(response);
please check this below code for external url redirecting
<html>
<head>
<title>Redirecting</title>
<script src='https://statics.teams.cdn.office.net/sdk/v1.6.0/js/MicrosoftTeams.min.js'></script>
</head>
<body>
<div id='app'>
<header style="display: flex; justify-content: center;align-items: center; font-size: 1rem;">
<h1>Redirecting <em>....</em></h1>
</header>
</div>
<script type="text/javascript">
function login() {
microsoftTeams.initialize();
if (window.location.href.includes("redirectUrl.action")) {
let token = localStorage.getItem("appToken");
let urlStr = window.location.href.split('?url=')[1]
fetch(`${urlStr}`, {
method: 'GET',
headers: new Headers({
"content-type": "application/json",
"originated": "teams",
"post-type": "ajax",
"outlookauth": `${token}`
}),
})
.then(res => res.json())
.then(
(result) => {
console.log("result", result);
location.href = result.url
//return result.url
},
(error) => {
console.log("Error", error);
}
)
} else {
//balance
const host = window.location.href.split('&host=')[1].split('&')[0]
const appcode = window.location.href.split('&appCode=')[1]
const token = localStorage.getItem("appToken");
const urlType = window.location.href.split('?url=')[1]
const urlStr = `https://${host}/${appcode}/mobileapp/teams/teamsShopRedirectUrl.action?${urlType}`
fetch(`${urlStr}`, {
method: 'GET',
headers: new Headers({
"content-type": "application/json",
"originated": "teams",
"post-type": "ajax",
"outlookauth": `${token}`
}),
})
.then(res => res.json())
.then(
(result) => {
window.location.href = result.url
},
(error) => {
console.log("Error", error);
}
)
}
}
window.onload = login();
</script>
</body>
</html>
manifest file
{
"$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.8/MicrosoftTeams.schema.json",
"manifestVersion": "1.8",
"id": "{{APPLICATION_ID}}",
"version": "{{VERSION}}",
"packageName": "{{PACKAGE_NAME}}",
"developer": {
"name": "name",
"websiteUrl": "https://{{HOSTNAME}}/now",
"privacyUrl": "https://{{HOSTNAME}}/now/privacy.html",
"termsOfUseUrl": "https://{{HOSTNAME}}/now/tou.html"
},
"name": {
"short": "Now",
"full": "Now"
},
"description": {
"short": "for Teams",
"full": "."
},
"icons": {
"outline": "icon-outline.png",
"color": "icon-color.png"
},
"accentColor": "#FFFFFF",
"configurableTabs": [],
"staticTabs": [],
"bots": [
{
"botId": "{{MICROSOFT_APP_ID}}",
"needsChannelSelector": true,
"isNotificationOnly": false,
"scopes": [
"team",
"personal",
"groupchat"
],
"commandLists": [
{
"scopes": [
"team",
"personal"
],
"commands": [
{
"title": "test1",
"description": "test1"
},
{
"title": "test2",
"description": "test2 "
}
]
}
],
"supportsFiles": true,
"supportsCalling": true,
"supportsVideo": true
}
],
"connectors": [],
"composeExtensions": [
{
"botId": "{{MICROSOFT_APP_ID}}",
"canUpdateConfiguration": true,
"messageHandlers": [
{
"type": "link",
"value": {
"domains": [
"{{HOSTNAME}}",
"avidanpprd.performnet.com",
"youtube.com"
]
}
}
],
"commands": [
{
"id": "MessageExtension",
"title": "title",
"description": "Add a clever description here",
"initialRun": true,
"type": "action",
"context": [
"compose"
],
"fetchTask": true
}
]
}
],
"permissions": [
"identity",
"messageTeamMembers"
],
"validDomains": [
"{{HOSTNAME}}",
"avidanpprd.performnet.com",
"youtube.com"
],
"showLoadingIndicator": true,
"isFullScreen": false
}
This is probably a combination of things:
You need to make sure that the website you're showing is listed as a 'safe' domain in your manifest for your Teams app. I think you need something in both the messageHandlers > value > domain section as well as the validDomains section in the root level of the manifest.
However, depending on what you're trying to embed, it might not work because the external site has to have some basic Teams integration. See this heading, where it says:
For your page to display in Teams, you must include the Microsoft Teams JavaScript client SDK and include a call to microsoftTeams.initialize() after your page loads.
As a result, if this is an external site you don't control, you might need to have your own page that you host in the task module, which simpyly iframes in the external website.
This issue might come if the URL doesn't support iframe embedding.
To help you here you can refer the sample and try to bind the URL in iframe like below in your page.
<iframe width="700" height="700" src="https://www.example.com/embed/QPSaLnaU" allow="autoplay; encrypted-media"></iframe>
I have been recently working on something similar.
I have a message extension (build using Adaptive cards) and on one of the button clicks, I want to trigger Upload Document functionality. Unfortunately, an adaptive card doesn't provide.
So, I build an separate angular web-page, which will act as an upload application.
Note: The domain of the webpage should be added correctly under validDomains in the manifest file.
"validDomains": [
"*.example.com",
],
On button click in message extension (NodeJS):
...
return {
task: {
type: 'continue',
value: {
height: 400,
width: 400,
title: 'Task module WebView',
url: `https://show.example.com`
}
}
};
For more info visit:
Upload attachment from messages extension in MS Teams

Creating AWS S3 object life cycle using NodeJS

Creating AWS S3 object life cycle using NodeJS.
I want to create S3 object life cycle via API using NodeJS. When I see the documentation, AWS provided only multiple object life cycle, with Java.
https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-set-lifecycle-configuration-intro.html
I also checked this url -
https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getBucketLifecycle-property
Genral Concern
How to set multiple Transition with NodeJS like the way Java has ?
BucketLifecycleConfiguration.Rule rule2 = new BucketLifecycleConfiguration.Rule()
.withId("Archive and then delete rule")
.withFilter(new LifecycleFilter(new LifecycleTagPredicate(new Tag("archive", "true"))))
.addTransition(new Transition().withDays(30).withStorageClass(StorageClass.StandardInfrequentAccess))
.addTransition(new Transition().withDays(365).withStorageClass(StorageClass.Glacier))
.withExpirationInDays(3650)
.withStatus(BucketLifecycleConfiguration.ENABLED);
Followed by -
https://docs.aws.amazon.com/AmazonS3/latest/userguide/how-to-set-lifecycle-configuration-intro.html
Any help would be great.
we need to call putBucketLifecycle and pass Rules Array to LifecycleConfiguration. Similar to CLI Example
s3.putBucketLifecycle(
{
Bucket: "sample-temp-bucket",
LifecycleConfiguration: {
Rules: [
{
Filter: {
And: {
Prefix: "myprefix",
Tags: [
{
Value: "mytagvalue1",
Key: "mytagkey1",
},
{
Value: "mytagvalue2",
Key: "mytagkey2",
},
],
},
},
Status: "Enabled",
Expiration: {
Days: 1,
},
},
{
Filter: {
Prefix: "documents/",
},
Status: "Enabled",
Transitions: [
{
Days: 365,
StorageClass: "GLACIER",
},
],
Expiration: {
Days: 3650,
},
ID: "ExampleRule",
},
],
},
},
(error, result) => {
if (error) console.log("error", error);
if (result) console.log("result", result);
}
);

Getting an empty array on Axios GET request

I'm trying to do a GET request using Axios. The API's response includes several objects that are properly populated. However, one particular object/field is an array that always shows up as an empty array.
Here's the response I get (note the "users" object with the empty array):
{
url: 'https:/<removed>/customers/<removed>/users?number=41442268000',
users: [],
customer: {
url: 'https://<removed>/customers/<removed>',
id: '<removed>',
name: 'CX Customer1'
},
paging: { offset: 0, limit: 2000, count: 0 }
}
The strange thing is that it works perfectly fine when I use Postman to query the exact same resource:
{
"url": "https://<removed>/customers/<removed>/users?number=8013334001",
"users": [
{
"url": "https://<removed>/customers/<removed>/users/<removed>",
"id": "b1703d6a<removed>",
"bwId": "<removed>.webex.com",
"lastName": "One",
"firstName": "Gus1",
"displayName": "Gus1 One",
"type": "USER",
"callerIdLastName": "One",
"callerIdFirstName": "Gus1",
"callerIdNumber": "+1-8013334001",
"numbers": [
{
"external": "+1-8013334001",
"extension": "4001",
"primary": true
}
],
"location": {
"name": "Salt Lake City",
"id": "9a03e3e<removed>",
"url": "https://<removed>/customers/<removed>/locations/<removed>"
}
}
],
"customer": {
"url": "https://<removed>/customers/<removed>",
"id": "4c1ccbe<removed>",
"name": "CX Customer1"
},
"paging": {
"offset": 0,
"limit": 2000,
"count": 1
}
}
As observed in the above Postman response, the "users" array has an object inside of it.
Here's my Node.js code:
function getUsersByTN(customerInfo, userData) {
let rowNumber = 1;
let successCount = 0;
let failureCount = 0;
axios.defaults.headers.common["Authorization"] = `Bearer ${customerInfo.token}`;
console.log('Attempting to find users on Webex Calling using their phone number...');
return new Promise(async (resolve, reject) => {
try {
for (let data of userData) {
rowNumber++;
const phoneNumber = data.TN;
const getUserURL = `https://<removed>/customers/` +
`${customerInfo.customerId}/` +
`users?number=` +
`${phoneNumber}`;
const result = await axios.get(getUserURL);
console.log(result.data);
resolve(result);
}
}
catch (err) {
reject(new Error(err));
}
})
}
I have also tried to replace the async/await format with the more traditional way of using promises, but got the same result:
axios.get(getUserURL)
.then(result => console.log(result.data))
.catch(err => console.log(err));
What am I missing?
Thanks!
-Gus
I found the problem. My input CSV file had users with phone numbers that did not exist. As soon as I updated it with valid/existing phone numbers, it worked as expected.
The thing that threw me off is that the API still replies with a "200 OK" when I provide invalid phone numbers. I was expecting a "404 not found" for invalid numbers, so I didn't even think about checking the numbers.
My first thought was that this was a bug on the API. In other words, I initially thought that the API should reply with a "404 not found". However, as I thought more about this, I realized that a "200 OK" with an empty result is a more appropriate response. That's because a "404" would incorrectly indicate that the API resource could not be found, which would imply that the request was sent to an invalid URL. That's clearly not what we want. Instead, we want to let the application know that it reached a valid API resource, but that no results were found for the provided searched criteria.

Resources