Skip to content

Commit cfdb3bf

Browse files
committed
fix: correctly handle iframe allow attribute
1 parent 57deff5 commit cfdb3bf

File tree

7 files changed

+104
-8
lines changed

7 files changed

+104
-8
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"httpbis-digest-headers": "^1.0.0",
3838
"iso8601-duration": "^2.1.2",
3939
"loglevel": "^1.9.2",
40+
"permissions-policy-allows-feature": "^0.0.1",
4041
"react": "^19.0.0",
4142
"react-dom": "^19.0.0",
4243
"safe-buffer": "5.2.1",

pnpm-lock.yaml

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/background/services/background.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,14 @@ export class Background {
314314
);
315315
return;
316316

317+
case 'IS_MONETIZATION_ALLOWED':
318+
return success(
319+
this.monetizationService.isMonetizationAllowed(
320+
message.payload,
321+
sender,
322+
),
323+
);
324+
317325
// endregion
318326

319327
// region App

src/background/services/monetization.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
import type { Runtime, Tabs } from 'webextension-polyfill';
22
import type {
3+
IsMonetizationAllowedPayload,
34
PayWebsitePayload,
45
PayWebsiteResponse,
56
ResumeMonetizationPayload,
67
StartMonetizationPayload,
78
StopMonetizationPayload,
89
} from '@/shared/messages';
910
import { PaymentSession } from './paymentSession';
10-
import { computeRate, getSender, getTabId } from '@/background/utils';
11+
import { computeRate, getSender, getTab, getTabId } from '@/background/utils';
1112
import { isOutOfBalanceError } from './openPayments';
1213
import {
1314
OUTGOING_PAYMENT_POLLING_MAX_ATTEMPTS,
@@ -21,6 +22,7 @@ import {
2122
removeQueryParams,
2223
transformBalance,
2324
} from '@/shared/helpers';
25+
import { PermissionsPolicy } from 'permissions-policy-allows-feature';
2426
import type { AmountValue, PopupStore, Storage } from '@/shared/types';
2527
import type { OutgoingPayment } from '@interledger/open-payments';
2628
import type { Cradle } from '@/background/container';
@@ -550,6 +552,30 @@ export class MonetizationService {
550552
};
551553
}
552554

555+
isMonetizationAllowed(
556+
payload: IsMonetizationAllowedPayload,
557+
sender: Runtime.MessageSender,
558+
): boolean {
559+
const tab = getTab(sender);
560+
561+
// This will be stored in tabState likely
562+
// https://github.com/interledger/web-monetization-extension/issues/959
563+
// We also want to allow/disallow monetization on host-document in future with this.
564+
const permissionsPolicy = new PermissionsPolicy({
565+
headerValue: '', // we'll get this via webRequest in future
566+
origin: tab.url,
567+
defaultAllowlist: { monetization: "'self'" },
568+
});
569+
570+
// get the permissions policy for given iframe
571+
const { allowAttribute: allow, origin } = payload;
572+
const iframePermissionsPolicy = permissionsPolicy.inherit({
573+
allow,
574+
origin,
575+
});
576+
return iframePermissionsPolicy.allowsFeature('monetization', origin);
577+
}
578+
553579
private async adjustSessionsAmount(
554580
sessions: PaymentSession[],
555581
rate: AmountValue,

src/content/__tests__/monetizationLinkManager.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const msg: MessageMocks = {
6161
START_MONETIZATION: jest.fn(),
6262
STOP_MONETIZATION: jest.fn(),
6363
TAB_FOCUSED: jest.fn(),
64+
IS_MONETIZATION_ALLOWED: jest.fn(),
6465
};
6566
const messageMock = jest.spyOn(messageManager, 'send');
6667
// @ts-expect-error let it go

src/content/services/frameManager.ts

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ export class FrameManager {
1818
private frameAllowAttrObserver: MutationObserver;
1919
private frames = new Map<
2020
HTMLIFrameElement,
21-
{ frameId: string | null; requestIds: string[] }
21+
{
22+
frameId: string | null;
23+
requestIds: string[];
24+
allowAttribute: string;
25+
origin: string;
26+
}
2227
>();
2328

2429
constructor({ window, document, logger, message }: Cradle) {
@@ -78,9 +83,21 @@ export class FrameManager {
7883
}
7984
const hasTarget = this.frames.has(target);
8085
const typeSpecified =
81-
target instanceof HTMLIFrameElement && target.allow === 'monetization';
86+
target instanceof HTMLIFrameElement &&
87+
target.allow.includes('monetization');
8288

89+
let allowedByPermissionsPolicy = false;
8390
if (!hasTarget && typeSpecified) {
91+
const res = await this.message.send('IS_MONETIZATION_ALLOWED', {
92+
allowAttribute: target.allow,
93+
origin: new URL(target.src, location.origin).origin,
94+
});
95+
if (res.success && res.payload) {
96+
allowedByPermissionsPolicy = true;
97+
}
98+
}
99+
100+
if (!hasTarget && typeSpecified && allowedByPermissionsPolicy) {
84101
await this.onAddedFrame(target);
85102
handledTags.add(target);
86103
} else if (hasTarget && !typeSpecified) {
@@ -97,6 +114,8 @@ export class FrameManager {
97114
this.frames.set(frame, {
98115
frameId: null,
99116
requestIds: [],
117+
allowAttribute: frame.allow,
118+
origin: new URL(frame.src, location.origin).origin,
100119
});
101120
}
102121

@@ -181,7 +200,7 @@ export class FrameManager {
181200
private bindMessageHandler() {
182201
this.window.addEventListener(
183202
'message',
184-
(event: MessageEvent<ContentToContentMessage>) => {
203+
async (event: MessageEvent<ContentToContentMessage>) => {
185204
const { message, payload, id } = event.data;
186205
if (!HANDLED_MESSAGES.includes(message)) {
187206
return;
@@ -201,15 +220,26 @@ export class FrameManager {
201220
this.frames.set(frame, {
202221
frameId: id,
203222
requestIds: [],
223+
allowAttribute: frame.allow,
224+
origin: new URL(frame.src, location.origin).origin,
204225
});
205226
return;
206227

207-
case 'IS_MONETIZATION_ALLOWED_ON_START':
228+
case 'IS_MONETIZATION_ALLOWED_ON_START': {
208229
event.stopPropagation();
209-
if (frame.allow === 'monetization') {
230+
const permissionsPolicyPayload = {
231+
allowAttribute: frame.allow,
232+
origin: new URL(frame.src, location.origin).origin,
233+
};
234+
const res = await this.message.send(
235+
'IS_MONETIZATION_ALLOWED',
236+
permissionsPolicyPayload,
237+
);
238+
if (res.success && res.payload) {
210239
this.frames.set(frame, {
211240
frameId: id,
212241
requestIds: payload.map((p) => p.requestId),
242+
...permissionsPolicyPayload,
213243
});
214244
eventSource.postMessage(
215245
{ message: 'START_MONETIZATION', id, payload },
@@ -218,20 +248,31 @@ export class FrameManager {
218248
}
219249

220250
return;
251+
}
221252

222-
case 'IS_MONETIZATION_ALLOWED_ON_RESUME':
253+
case 'IS_MONETIZATION_ALLOWED_ON_RESUME': {
223254
event.stopPropagation();
224-
if (frame.allow === 'monetization') {
255+
const permissionsPolicyPayload = {
256+
allowAttribute: frame.allow,
257+
origin: new URL(frame.src, location.origin).origin,
258+
};
259+
const res = await this.message.send(
260+
'IS_MONETIZATION_ALLOWED',
261+
permissionsPolicyPayload,
262+
);
263+
if (res.success && res.payload) {
225264
this.frames.set(frame, {
226265
frameId: id,
227266
requestIds: payload.map((p) => p.requestId),
267+
...permissionsPolicyPayload,
228268
});
229269
eventSource.postMessage(
230270
{ message: 'RESUME_MONETIZATION', id, payload },
231271
'*',
232272
);
233273
}
234274
return;
275+
}
235276

236277
default:
237278
return;

src/shared/messages.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,11 @@ export type StopMonetizationPayload = StopMonetizationPayloadEntry[];
193193

194194
export type ResumeMonetizationPayload = StartMonetizationPayload;
195195

196+
export interface IsMonetizationAllowedPayload {
197+
allowAttribute: string;
198+
origin: string;
199+
}
200+
196201
export interface IsTabMonetizedPayload {
197202
value: boolean;
198203
}
@@ -218,6 +223,10 @@ export type ContentToBackgroundMessage = {
218223
input: ResumeMonetizationPayload;
219224
output: never;
220225
};
226+
IS_MONETIZATION_ALLOWED: {
227+
input: IsMonetizationAllowedPayload;
228+
output: boolean;
229+
};
221230
};
222231
// #endregion
223232

0 commit comments

Comments
 (0)