Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: disable integrations per website #830

Merged
merged 3 commits into from
Dec 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions add-on/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@
"message": "Active Tab",
"description": "A menu item in Browser Action pop-up (panel_activeTabSiteRedirectEnable)"
},
"panel_activeTabSiteRedirectToggle": {
"message": "Redirect on $1",
"description": "A menu item in Browser Action pop-up (panel_activeTabSiteRedirectToggle)"
"panel_activeTabSiteIntegrationsToggle": {
"message": "Enable on $1",
Copy link
Member Author

@lidel lidel Dec 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if Enable on <hostname> is the best label for this menu item, but it is short and simple.
Let me know if there is a better one.

"description": "A menu item in Browser Action pop-up (panel_activeTabSiteIntegrationsToggle)"
},
"panel_activeTabSiteRedirectToggleTooltip": {
"message": "Click to toggle gateway redirects on $1",
"description": "A menu item tooltip in Browser Action pop-up (panel_activeTabSiteRedirectToggleTooltip)"
"panel_activeTabSiteIntegrationsToggleTooltip": {
"message": "Click to toggle all IPFS integrations on $1",
"description": "A menu item tooltip in Browser Action pop-up (panel_activeTabSiteIntegrationsToggleTooltip)"
},
"panel_pinCurrentIpfsAddress": {
"message": "Pin IPFS Resource",
Expand Down Expand Up @@ -280,7 +280,7 @@
"description": "An option description on the Preferences screen (option_useCustomGateway_description)"
},
"option_dnslinkRedirect_title": {
"message": "Force page load from custom gateway",
"message": "Load websites from Custom Gateway",
Copy link
Member Author

@lidel lidel Dec 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Softening language because we restored the old behavior in #831
(and matching capitalization from other places)

"description": "An option title on the Preferences screen (option_dnslinkRedirect_title)"
},
"option_dnslinkRedirect_description": {
Expand All @@ -296,15 +296,15 @@
"description": "An option description on the Preferences screen (option_dnslinkDataPreload_description)"
},
"option_dnslinkRedirect_warning": {
"message": "Redirecting to a path-based gateway breaks Origin-based security isolation of DNSLink website! Please leave this disabled unless you are aware of (and ok with) related risks.",
"message": "Redirecting to a path-based gateway breaks Origin-based security isolation of DNSLink websites. Make sure you understand related risks.",
"description": "A warning on the Preferences screen, displayed when URL does not belong to Secure Context (option_customGatewayUrl_warning)"
},
"option_noRedirectHostnames_title": {
"message": "Redirect Opt-Outs",
"description": "An option title on the Preferences screen (option_noRedirectHostnames_title)"
"option_noIntegrationsHostnames_title": {
"message": "IPFS Integrations Opt-Outs",
"description": "An option title on the Preferences screen (option_noIntegrationsHostnames_title)"
},
"option_noRedirectHostnames_description": {
"message": "List of websites that should not be redirected to the Custom Gateway (includes subresources from other domains). One hostname per line.",
"option_noIntegrationsHostnames_description": {
"message": "List of websites that should not have any IPFS integrations enabled. One hostname per line.",
"description": "An option description on the Preferences screen (option_noRedirectHostnames_description)"
},
"option_publicGatewayUrl_title": {
Expand Down
9 changes: 5 additions & 4 deletions add-on/src/lib/ipfs-companion.js
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ module.exports = async function init () {
openViaWebUI: state.openViaWebUI,
apiURLString: dropSlash(state.apiURLString),
redirect: state.redirect,
noRedirectHostnames: state.noRedirectHostnames,
noIntegrationsHostnames: state.noIntegrationsHostnames,
currentTab: await browser.tabs.query({ active: true, currentWindow: true }).then(tabs => tabs[0])
}
try {
Expand All @@ -257,7 +257,7 @@ module.exports = async function init () {
info.isIpfsContext = ipfsPathValidator.isIpfsPageActionsContext(url)
info.currentDnslinkFqdn = dnslinkResolver.findDNSLinkHostname(url)
info.currentFqdn = info.currentDnslinkFqdn || new URL(url).hostname
info.currentTabRedirectOptOut = info.noRedirectHostnames && info.noRedirectHostnames.includes(info.currentFqdn)
info.currentTabIntegrationsOptOut = info.noIntegrationsHostnames && info.noIntegrationsHostnames.includes(info.currentFqdn)
info.isRedirectContext = info.currentFqdn && ipfsPathValidator.isRedirectPageActionsContext(url)
}
// Still here?
Expand Down Expand Up @@ -365,7 +365,8 @@ module.exports = async function init () {

async function onDOMContentLoaded (details) {
if (!state.active) return // skip content script injection when off
if (!details.url.startsWith('http')) return // skip special pages
if (!details.url || !details.url.startsWith('http')) return // skip empty and special pages
if (!state.activeIntegrations(details.url)) return // skip if opt-out exists
// console.info(`[ipfs-companion] onDOMContentLoaded`, details)
if (state.linkify) {
console.info(`[ipfs-companion] Running linkfy experiment for ${details.url}`)
Expand Down Expand Up @@ -679,7 +680,7 @@ module.exports = async function init () {
case 'detectIpfsPathHeader':
case 'preloadAtPublicGateway':
case 'openViaWebUI':
case 'noRedirectHostnames':
case 'noIntegrationsHostnames':
case 'dnslinkRedirect':
state[key] = change.newValue
break
Expand Down
5 changes: 3 additions & 2 deletions add-on/src/lib/ipfs-proxy/enable-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ const { createProxyAclError } = require('./pre-acl')
function createEnableCommand (getIpfs, getState, getScope, accessControl, requestAccess) {
return async (opts) => {
const scope = await getScope()
const state = getState()
log(`received window.ipfs.enable request from ${scope}`, opts)

// Check if all access to the IPFS node is disabled
if (!getState().ipfsProxy) throw new Error('User disabled access to API proxy in IPFS Companion')
// Check if access to the IPFS node is disabled
if (!state.ipfsProxy || !state.activeIntegrations(scope)) throw new Error('User disabled access to API proxy in IPFS Companion')

// NOOP if .enable() was called without any arguments
if (!opts) return
Expand Down
7 changes: 4 additions & 3 deletions add-on/src/lib/ipfs-proxy/pre-acl.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
// no access decision has been made yet.
function createPreAcl (permission, getState, getScope, accessControl, requestAccess) {
return async (...args) => {
// Check if all access to the IPFS node is disabled
if (!getState().ipfsProxy) {
const scope = await getScope()
const state = getState()
// Check if access to the IPFS node is disabled
if (!state.ipfsProxy || !state.activeIntegrations(scope)) {
throw createProxyAclError(undefined, undefined, 'User disabled access to API proxy in IPFS Companion')
}

const scope = await getScope()
const access = await getAccessWithPrompt(accessControl, requestAccess, scope, permission)

if (!access.allow) {
Expand Down
4 changes: 2 additions & 2 deletions add-on/src/lib/ipfs-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ function createRequestModifier (getState, dnslinkResolver, ipfsPathValidator, ru
if (request.url.startsWith('http://127.0.0.1') || request.url.startsWith('http://localhost') || request.url.startsWith('http://[::1]')) {
ignore(request.requestId)
}
// skip if a per-site redirect opt-out exists
// skip if a per-site opt-out exists
const parentUrl = request.originUrl || request.initiator // FF: originUrl (Referer-like Origin URL), Chrome: initiator (just Origin)
const fqdn = new URL(request.url).hostname
const parentFqdn = parentUrl && parentUrl !== 'null' && request.url !== parentUrl ? new URL(parentUrl).hostname : null
if (state.noRedirectHostnames.some(optout =>
if (state.noIntegrationsHostnames.some(optout =>
fqdn !== 'gateway.ipfs.io' && (fqdn.endsWith(optout) || (parentFqdn && parentFqdn.endsWith(optout))
))) {
ignore(request.requestId)
Expand Down
9 changes: 8 additions & 1 deletion add-on/src/lib/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ exports.optionDefaults = Object.freeze({
publicGatewayUrl: 'https://ipfs.io',
publicSubdomainGatewayUrl: 'https://dweb.link',
useCustomGateway: true,
noRedirectHostnames: [],
noIntegrationsHostnames: [],
automaticMode: true,
linkify: false,
dnslinkPolicy: 'best-effort',
Expand Down Expand Up @@ -132,4 +132,11 @@ exports.migrateOptions = async (storage) => {
ipfsNodeConfig: buildDefaultIpfsNodeConfig()
})
}
// ~ v2.9.x: migrating noRedirectHostnames → noIntegrationsHostnames
// https://github.com/ipfs-shipyard/ipfs-companion/pull/830
const { noRedirectHostnames } = await storage.get('noRedirectHostnames')
if (noRedirectHostnames) {
await storage.set({ noIntegrationsHostnames: noRedirectHostnames })
await storage.remove('noRedirectHostnames')
}
}
14 changes: 13 additions & 1 deletion add-on/src/lib/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const offlinePeerCount = -1
// which should work without setting CORS headers
const webuiCid = 'Qmexhq2sBHnXQbvyP2GfUdbnY7HCagH2Mw5vUNSBn2nxip' // v2.7.2

function initState (options) {
function initState (options, overrides) {
// we store options and some pregenerated values to avoid async storage
// reads and minimize performance impact on overall browsing experience
const state = Object.assign({}, options)
Expand All @@ -31,6 +31,18 @@ function initState (options) {
state.dnslinkPolicy = String(options.dnslinkPolicy) === 'false' ? false : options.dnslinkPolicy
state.webuiCid = webuiCid
state.webuiRootUrl = `${state.gwURLString}ipfs/${state.webuiCid}/`
// attach helper functions
state.activeIntegrations = (url) => {
if (!state.active) return false
try {
const fqdn = new URL(url).hostname
return !(state.noIntegrationsHostnames.find(host => fqdn.endsWith(host)))
} catch (_) {
return false
}
}
// apply optional overrides
if (overrides) Object.assign(state, overrides)
return state
}

Expand Down
16 changes: 8 additions & 8 deletions add-on/src/options/forms/gateways-form.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ function gatewaysForm ({
ipfsNodeType,
customGatewayUrl,
useCustomGateway,
noRedirectHostnames,
noIntegrationsHostnames,
publicGatewayUrl,
publicSubdomainGatewayUrl,
onOptionChange
Expand All @@ -23,7 +23,7 @@ function gatewaysForm ({
const onUseCustomGatewayChange = onOptionChange('useCustomGateway')
const onPublicGatewayUrlChange = onOptionChange('publicGatewayUrl', normalizeGatewayURL)
const onPublicSubdomainGatewayUrlChange = onOptionChange('publicSubdomainGatewayUrl', normalizeGatewayURL)
const onNoRedirectHostnamesChange = onOptionChange('noRedirectHostnames', hostTextToArray)
const onNoIntegrationsHostnamesChange = onOptionChange('noIntegrationsHostnames', hostTextToArray)
const mixedContentWarning = !secureContextUrl.test(customGatewayUrl)
const supportRedirectToCustomGateway = ipfsNodeType !== 'embedded'
const allowChangeOfCustomGateway = ipfsNodeType !== 'embedded:chromesockets'
Expand Down Expand Up @@ -110,18 +110,18 @@ function gatewaysForm ({
` : null}
${supportRedirectToCustomGateway ? html`
<div>
<label for="noRedirectHostnames">
<label for="noIntegrationsHostnames">
<dl>
<dt>${browser.i18n.getMessage('option_noRedirectHostnames_title')}</dt>
<dd>${browser.i18n.getMessage('option_noRedirectHostnames_description')}</dd>
<dt>${browser.i18n.getMessage('option_noIntegrationsHostnames_title')}</dt>
<dd>${browser.i18n.getMessage('option_noIntegrationsHostnames_description')}</dd>
</dl>
</label>
<textarea
id="noRedirectHostnames"
id="noIntegrationsHostnames"
spellcheck="false"
onchange=${onNoRedirectHostnamesChange}
onchange=${onNoIntegrationsHostnamesChange}
rows="4"
>${hostArrayToText(noRedirectHostnames)}</textarea>
>${hostArrayToText(noIntegrationsHostnames)}</textarea>
</div>
` : null}
</fieldset>
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/options/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ module.exports = function optionsPage (state, emit) {
useCustomGateway: state.options.useCustomGateway,
publicGatewayUrl: state.options.publicGatewayUrl,
publicSubdomainGatewayUrl: state.options.publicSubdomainGatewayUrl,
noRedirectHostnames: state.options.noRedirectHostnames,
noIntegrationsHostnames: state.options.noIntegrationsHostnames,
onOptionChange
})}
${fileImportForm({
Expand Down
22 changes: 10 additions & 12 deletions add-on/src/popup/browser-action/context-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ function contextActions ({
currentTab,
currentFqdn,
currentDnslinkFqdn,
currentTabRedirectOptOut,
currentTabIntegrationsOptOut,
ipfsNodeType,
isIpfsContext,
isPinning,
isUnPinning,
isPinned,
isIpfsOnline,
isApiAvailable,
onToggleSiteRedirect,
onToggleSiteIntegrations,
onViewOnGateway,
onCopy,
onPin,
Expand Down Expand Up @@ -69,24 +69,22 @@ function contextActions ({
</div>
`
}
/* TODO: change "redirect on {fqdn}" to "disable on {fqdn}" and disable all integrations
// removed per site toggle for now: ${renderSiteRedirectToggle()}
const renderSiteRedirectToggle = () => {
const renderSiteIntegrationsToggle = () => {
if (!isRedirectContext) return
return html`
${navItem({
text: browser.i18n.getMessage('panel_activeTabSiteRedirectToggle', currentFqdn),
title: browser.i18n.getMessage('panel_activeTabSiteRedirectToggleTooltip', currentFqdn),
text: browser.i18n.getMessage('panel_activeTabSiteIntegrationsToggle', currentFqdn),
title: browser.i18n.getMessage('panel_activeTabSiteIntegrationsToggleTooltip', currentFqdn),
style: 'truncate',
disabled: !(active && redirect),
switchValue: active && redirect && !currentTabRedirectOptOut,
onClick: onToggleSiteRedirect
disabled: !(active),
switchValue: active && !currentTabIntegrationsOptOut,
onClick: onToggleSiteIntegrations
})}
`
}
*/
return html`
<div class='fade-in pv1'>
${renderSiteIntegrationsToggle()}
${renderIpfsContextItems()}
</div>
`
Expand All @@ -101,7 +99,7 @@ function activeTabActions (state) {
return html`
<div>
${navHeader('panel_activeTabSectionHeader')}
<div class="fade-in pv1 bb b--black-10">
<div class="fade-in pv0 bb b--black-10">
${contextActions(state)}
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/browser-action/operations.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module.exports = function operations ({
}) {
const activeRedirectSwitch = active && ipfsNodeType !== 'embedded'
return html`
<div class="fade-in pv1 bb b--black-10">
<div class="fade-in pb1">
${navItem({
text: browser.i18n.getMessage('panel_redirectToggle'),
title: browser.i18n.getMessage('panel_redirectToggleTooltip'),
Expand Down
6 changes: 3 additions & 3 deletions add-on/src/popup/browser-action/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,19 @@ module.exports = function browserActionPage (state, emit) {
const onOpenWebUi = () => emit('openWebUi')
const onOpenPrefs = () => emit('openPrefs')
const onToggleGlobalRedirect = () => emit('toggleGlobalRedirect')
const onToggleSiteRedirect = () => emit('toggleSiteRedirect')
const onToggleSiteIntegrations = () => emit('toggleSiteIntegrations')
const onToggleActive = () => emit('toggleActive')

const headerProps = Object.assign({ onToggleActive, onOpenPrefs }, state)
const activeTabActionsProps = Object.assign({ onViewOnGateway, onToggleSiteRedirect, onCopy, onPin, onUnPin }, state)
const activeTabActionsProps = Object.assign({ onViewOnGateway, onToggleSiteIntegrations, onCopy, onPin, onUnPin }, state)
const opsProps = Object.assign({ onQuickImport, onOpenWebUi, onToggleGlobalRedirect }, state)

return html`
<div class="sans-serif" style="text-rendering: optimizeLegibility;">
${header(headerProps)}
${operations(opsProps)}
${activeTabActions(activeTabActionsProps)}
${tools(opsProps)}
${operations(opsProps)}
</div>
`
}
21 changes: 11 additions & 10 deletions add-on/src/popup/browser-action/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ module.exports = (state, emitter) => {
currentTab: null,
currentFqdn: null,
currentDnslinkFqdn: null,
noRedirectHostnames: []
noIntegrationsHostnames: []
})

let port
Expand Down Expand Up @@ -166,22 +166,23 @@ module.exports = (state, emitter) => {
}
})

emitter.on('toggleSiteRedirect', async () => {
state.currentTabRedirectOptOut = !state.currentTabRedirectOptOut
emitter.on('toggleSiteIntegrations', async () => {
state.currentTabIntegrationsOptOut = !state.currentTabIntegrationsOptOut
emitter.emit('render')

try {
let noRedirectHostnames = state.noRedirectHostnames
let noIntegrationsHostnames = state.noIntegrationsHostnames
// if we are on /ipns/fqdn.tld/ then use hostname from DNSLink
const fqdn = state.currentDnslinkFqdn || state.currentFqdn
if (noRedirectHostnames.includes(fqdn)) {
noRedirectHostnames = noRedirectHostnames.filter(host => !host.endsWith(fqdn))
if (noIntegrationsHostnames.includes(fqdn)) {
noIntegrationsHostnames = noIntegrationsHostnames.filter(host => !host.endsWith(fqdn))
} else {
noRedirectHostnames.push(fqdn)
noIntegrationsHostnames.push(fqdn)
}
// console.dir('toggleSiteRedirect', state)
await browser.storage.local.set({ noRedirectHostnames })
// console.dir('toggleSiteIntegrations', state)
await browser.storage.local.set({ noIntegrationsHostnames })

// TODO: remove below? does it still make sense in "integrations toggle" context?
// Reload the current tab to apply updated redirect preference
if (!state.currentDnslinkFqdn || !IsIpfs.ipnsUrl(state.currentTab.url)) {
// No DNSLink, reload URL as-is
Expand All @@ -198,7 +199,7 @@ module.exports = (state, emitter) => {
})
}
} catch (error) {
console.error(`Unable to update redirect state due to ${error}`)
console.error(`Unable to update integrations state due to ${error}`)
emitter.emit('render')
}
})
Expand Down
2 changes: 1 addition & 1 deletion add-on/src/popup/browser-action/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = function tools ({
return html`
<div>
${navHeader('panel_toolsSectionHeader')}
<div class="fade-in pv1 bb b--black-10">
<div class="fade-in pt1">
${navItem({
text: browser.i18n.getMessage('panel_quickImport'),
style: 'b',
Expand Down
4 changes: 2 additions & 2 deletions add-on/src/popup/page-action/page.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ module.exports = function pageActionPage (state, emit) {
const onCopy = (copyAction) => emit('copy', copyAction)
const onPin = () => emit('pin')
const onUnPin = () => emit('unPin')
const onToggleSiteRedirect = () => emit('toggleSiteRedirect')
const onToggleSiteIntegrations = () => emit('toggleSiteIntegrations')

const contextActionsProps = Object.assign({ onViewOnGateway, onCopy, onPin, onUnPin, onToggleSiteRedirect }, state)
const contextActionsProps = Object.assign({ onViewOnGateway, onCopy, onPin, onUnPin, onToggleSiteIntegrations }, state)

// Instant init: page-action is shown only in ipfsContext
contextActionsProps.isIpfsContext = true
Expand Down
Loading