Prevent authentication service capability downgrades
Co-Authored-By: GPT-5.6-Sol <noreply@openai.com>
This commit is contained in:
1 parent
203e1639c3
commit
f2b4731bb3
5 files changed
+40
-7
No files matched your search
@@ -4,6 +4,7 @@
|
|||||||
// a separate empty store rather than a shared path to credential-bearing QML.
|
// a separate empty store rather than a shared path to credential-bearing QML.
|
||||||
|
|
||||||
var services = ({})
|
var services = ({})
|
||||||
|
var trustedIds = ({})
|
||||||
|
|
||||||
function has(id) {
|
function has(id) {
|
||||||
return services[String(id || "")] !== undefined
|
return services[String(id || "")] !== undefined
|
||||||
@@ -12,11 +13,16 @@ function has(id) {
|
|||||||
function put(id, service) {
|
function put(id, service) {
|
||||||
var key = String(id || "")
|
var key = String(id || "")
|
||||||
if (!key || !service) return
|
if (!key || !service) return
|
||||||
|
trustedIds[key] = true
|
||||||
if (services[key] && services[key] !== service && typeof services[key].destroy === "function")
|
if (services[key] && services[key] !== service && typeof services[key].destroy === "function")
|
||||||
services[key].destroy()
|
services[key].destroy()
|
||||||
services[key] = service
|
services[key] = service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTrusted(id) {
|
||||||
|
return trustedIds[String(id || "")] === true
|
||||||
|
}
|
||||||
|
|
||||||
function ids() {
|
function ids() {
|
||||||
return Object.keys(services)
|
return Object.keys(services)
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-7
@@ -375,7 +375,7 @@ ShellRoot {
|
|||||||
if (!shell.pluginHasBarCapabilities(manifest)) return false
|
if (!shell.pluginHasBarCapabilities(manifest)) return false
|
||||||
var id = shell.pluginRegistry.resolveEnabledId(String(requestedId || ""))
|
var id = shell.pluginRegistry.resolveEnabledId(String(requestedId || ""))
|
||||||
var target = shell.pluginRegistry.installedPlugins[id]
|
var target = shell.pluginRegistry.installedPlugins[id]
|
||||||
if (!target || shell.isAuthenticationService(target)) return false
|
if (!target || shell.isAuthenticationService(target, id)) return false
|
||||||
if (shell.barEntryConfigured(id)) return true
|
if (shell.barEntryConfigured(id)) return true
|
||||||
var uiKinds = ["bar-widget", "panel", "overlay", "menu"]
|
var uiKinds = ["bar-widget", "panel", "overlay", "menu"]
|
||||||
for (var i = 0; i < uiKinds.length; i++)
|
for (var i = 0; i < uiKinds.length; i++)
|
||||||
@@ -842,9 +842,11 @@ ShellRoot {
|
|||||||
return serviceFor(shell.pluginRegistry.resolveEnabledId(pluginId))
|
return serviceFor(shell.pluginRegistry.resolveEnabledId(pluginId))
|
||||||
}
|
}
|
||||||
|
|
||||||
function isAuthenticationService(manifest) {
|
function isAuthenticationService(manifest, pluginId) {
|
||||||
return !!manifest && Array.isArray(manifest.__hostCapabilities)
|
var key = String(pluginId || (manifest && manifest.id) || "")
|
||||||
&& manifest.__hostCapabilities.indexOf("authentication") !== -1
|
return AuthServiceStore.isTrusted(key)
|
||||||
|
|| (!!manifest && Array.isArray(manifest.__hostCapabilities)
|
||||||
|
&& manifest.__hostCapabilities.indexOf("authentication") !== -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
function ensureService(pluginId) {
|
function ensureService(pluginId) {
|
||||||
@@ -857,7 +859,7 @@ ShellRoot {
|
|||||||
if (!manifest.entryPoints || !manifest.entryPoints.service) return null
|
if (!manifest.entryPoints || !manifest.entryPoints.service) return null
|
||||||
var url = pluginRegistry.entryPointUrl(manifest, "service")
|
var url = pluginRegistry.entryPointUrl(manifest, "service")
|
||||||
if (!url) return null
|
if (!url) return null
|
||||||
var authenticationService = shell.isAuthenticationService(manifest)
|
var authenticationService = shell.isAuthenticationService(manifest, key)
|
||||||
if (authenticationService && AuthServiceStore.has(key)) return null
|
if (authenticationService && AuthServiceStore.has(key)) return null
|
||||||
|
|
||||||
var comp = Qt.createComponent(url, Component.PreferSynchronous)
|
var comp = Qt.createComponent(url, Component.PreferSynchronous)
|
||||||
@@ -908,7 +910,7 @@ ShellRoot {
|
|||||||
if (!Array.isArray(m.kinds) || m.kinds.indexOf("service") === -1) continue
|
if (!Array.isArray(m.kinds) || m.kinds.indexOf("service") === -1) continue
|
||||||
if (!m.entryPoints || !m.entryPoints.service) continue
|
if (!m.entryPoints || !m.entryPoints.service) continue
|
||||||
if (!pluginRegistry.isEnabled(id)) continue
|
if (!pluginRegistry.isEnabled(id)) continue
|
||||||
var authenticationService = shell.isAuthenticationService(m)
|
var authenticationService = shell.isAuthenticationService(m, id)
|
||||||
if (_services[id]) {
|
if (_services[id]) {
|
||||||
if (authenticationService) {
|
if (authenticationService) {
|
||||||
// A service that gains a trusted authentication capability must move
|
// A service that gains a trusted authentication capability must move
|
||||||
@@ -965,7 +967,7 @@ ShellRoot {
|
|||||||
&& authenticationManifest.entryPoints
|
&& authenticationManifest.entryPoints
|
||||||
&& authenticationManifest.entryPoints.service
|
&& authenticationManifest.entryPoints.service
|
||||||
if (stillAuthenticationService && pluginRegistry.isEnabled(authenticationId)
|
if (stillAuthenticationService && pluginRegistry.isEnabled(authenticationId)
|
||||||
&& shell.isAuthenticationService(authenticationManifest)) continue
|
&& shell.isAuthenticationService(authenticationManifest, authenticationId)) continue
|
||||||
AuthServiceStore.destroy(authenticationId)
|
AuthServiceStore.destroy(authenticationId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ QtObject {
|
|||||||
return AuthServiceStore.has(id)
|
return AuthServiceStore.has(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isTrusted(id) {
|
||||||
|
return AuthServiceStore.isTrusted(id)
|
||||||
|
}
|
||||||
|
|
||||||
function updateManifest(id, manifest) {
|
function updateManifest(id, manifest) {
|
||||||
AuthServiceStore.updateManifest(id, manifest)
|
AuthServiceStore.updateManifest(id, manifest)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ ShellRoot {
|
|||||||
ownSettings: api.updateEntryInline(caller, {}) === true,
|
ownSettings: api.updateEntryInline(caller, {}) === true,
|
||||||
foreignSettings: api.updateEntryInline("omarchy.lock", {}) === false,
|
foreignSettings: api.updateEntryInline("omarchy.lock", {}) === false,
|
||||||
authStoreOwnerRetains: authStoreOwner.has("omarchy.lock") === true,
|
authStoreOwnerRetains: authStoreOwner.has("omarchy.lock") === true,
|
||||||
|
authStoreOwnerRemembersTrust: authStoreOwner.isTrusted("omarchy.lock") === true,
|
||||||
authStoreOwnerUpdatesManifest: root.ownService.manifest
|
authStoreOwnerUpdatesManifest: root.ownService.manifest
|
||||||
&& root.ownService.manifest.version === "kept",
|
&& root.ownService.manifest.version === "kept",
|
||||||
authStoreImportIsolated: authStoreReader.has("omarchy.lock") === false,
|
authStoreImportIsolated: authStoreReader.has("omarchy.lock") === false,
|
||||||
|
|||||||
@@ -37,12 +37,32 @@ qml_matches "$shell_qml" 'comp\.createObject\( *manifest\.__isFirstParty *&& *!a
|
|||||||
fail "third-party and authentication services are detached from the host object tree"
|
fail "third-party and authentication services are detached from the host object tree"
|
||||||
qml_matches "$shell_qml" 'AuthServiceStore\.put\( *key, *inst *\)' ||
|
qml_matches "$shell_qml" 'AuthServiceStore\.put\( *key, *inst *\)' ||
|
||||||
fail "authentication services are retained outside the host service map"
|
fail "authentication services are retained outside the host service map"
|
||||||
|
qml_matches "$shell_qml" 'AuthServiceStore\.isTrusted\( *key *\)' ||
|
||||||
|
fail "live authentication classification survives public manifest mutation"
|
||||||
qml_matches "$shell_qml" 'AuthServiceStore\.updateManifest\( *id, *shell\.publicPluginManifest\( *m *\) *\)' ||
|
qml_matches "$shell_qml" 'AuthServiceStore\.updateManifest\( *id, *shell\.publicPluginManifest\( *m *\) *\)' ||
|
||||||
fail "kept authentication services receive only a public manifest snapshot"
|
fail "kept authentication services receive only a public manifest snapshot"
|
||||||
qml_matches "$shell_qml" 'if *\( *!serviceKeepLoaded\( *authenticationId *\) *\) *AuthServiceStore\.destroy\( *authenticationId *\)' ||
|
qml_matches "$shell_qml" 'if *\( *!serviceKeepLoaded\( *authenticationId *\) *\) *AuthServiceStore\.destroy\( *authenticationId *\)' ||
|
||||||
fail "keepLoaded authentication services survive plugin rescans"
|
fail "keepLoaded authentication services survive plugin rescans"
|
||||||
pass "third-party and authentication services are detached from the host object tree"
|
pass "third-party and authentication services are detached from the host object tree"
|
||||||
|
|
||||||
|
run_node_test <<'JS'
|
||||||
|
const fs = require('fs')
|
||||||
|
const vm = require('vm')
|
||||||
|
const store = {}
|
||||||
|
vm.createContext(store)
|
||||||
|
vm.runInContext(
|
||||||
|
fs.readFileSync(path.join(root, 'shell/services/AuthServiceStore.js'), 'utf8'),
|
||||||
|
store
|
||||||
|
)
|
||||||
|
const service = { destroy() {} }
|
||||||
|
store.put('omarchy.lock', service)
|
||||||
|
store.destroy('omarchy.lock')
|
||||||
|
assert(
|
||||||
|
!store.has('omarchy.lock') && store.isTrusted('omarchy.lock'),
|
||||||
|
'authentication classification survives service teardown'
|
||||||
|
)
|
||||||
|
JS
|
||||||
|
|
||||||
qml_matches "$shell_qml" 'inst\.shell *= *shell\.pluginShellFor\( *manifest *\)' ||
|
qml_matches "$shell_qml" 'inst\.shell *= *shell\.pluginShellFor\( *manifest *\)' ||
|
||||||
fail "service plugins receive a scoped shell facade"
|
fail "service plugins receive a scoped shell facade"
|
||||||
qml_matches "$shell_qml" 'item\.shell *= *shell\.pluginShellFor\( *panelEntry\.manifest *\)' ||
|
qml_matches "$shell_qml" 'item\.shell *= *shell\.pluginShellFor\( *panelEntry\.manifest *\)' ||
|
||||||
|
|||||||
Reference in new issue
Block a user