Repository navigation
Add conditional permission support #886
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
base: master
Are you sure you want to change the base?
Changes from 2 commits
84181ad
6abc833
405cba6
7ad09fe
e11e249
5c5b9a7
e996745
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,6 +52,9 @@ const MODELS = { | |
| unsupported: new FlatpakUnsupportedModel(), | ||
| }; | ||
|
|
||
| /* Models that support conditional permissions */ | ||
| const CONDITIONAL_MODELS = [MODELS.shared, MODELS.sockets, MODELS.devices, MODELS.features]; | ||
|
|
||
| function generate_index() { | ||
| const index = {}; | ||
|
|
||
|
|
@@ -102,6 +105,11 @@ function generate() { | |
| const statusProperty = `${property}-status`; | ||
| properties[statusProperty] = GObject.ParamSpec.string( | ||
| statusProperty, statusProperty, statusProperty, FLAGS, FlatsealOverrideStatus.ORIGINAL); | ||
|
|
||
| /* conditional requests */ | ||
| const conditionalProperty = `${property}-conditional`; | ||
| properties[conditionalProperty] = GObject.ParamSpec.string( | ||
| conditionalProperty, conditionalProperty, conditionalProperty, FLAGS, ''); | ||
|
Malika7188 marked this conversation as resolved.
Outdated
|
||
| }); | ||
| }); | ||
|
|
||
|
|
@@ -192,21 +200,34 @@ var FlatpakPermissionsModel = GObject.registerClass({ | |
| .split(';'); | ||
|
|
||
| values.forEach(option => { | ||
| /* Flatseal does not support conditionals, but skips them | ||
| * to avoid corrupting the overrides file. */ | ||
| if (option.startsWith(CONDITIONAL_PREFIX)) | ||
| return; | ||
| let isConditional = false; | ||
| let bareOption = option; | ||
|
|
||
| if (option.startsWith(CONDITIONAL_PREFIX)) { | ||
|
Malika7188 marked this conversation as resolved.
|
||
| isConditional = true; | ||
| [bareOption] = option.slice(CONDITIONAL_PREFIX.length).split(':'); | ||
| } | ||
|
Malika7188 marked this conversation as resolved.
|
||
|
|
||
| model = this.constructor._find(`${group}_${key}_${option.replace('!', '')}`); | ||
| model = this.constructor._find(`${group}_${key}_${bareOption.replace('!', '')}`); | ||
|
Malika7188 marked this conversation as resolved.
|
||
|
|
||
| if (model === null) | ||
| model = this.constructor._find(`${group}_${key}`); | ||
|
|
||
| if (model === null && overrides && !global) | ||
| model = MODELS.unsupported; | ||
|
|
||
| if (model !== null) | ||
| if (model === null) | ||
|
Malika7188 marked this conversation as resolved.
Outdated
|
||
| return; | ||
|
|
||
| /* Preserves the original conditional string for models | ||
| * that don't support conditionals, so the condition | ||
| * is not lost.*/ | ||
| if (isConditional && model !== MODELS.unsupported) { | ||
| model.loadFromKeyFile(group, key, bareOption, overrides, global); | ||
| model.markConditional(bareOption, option); | ||
| } else { | ||
| model.loadFromKeyFile(group, key, option, overrides, global); | ||
| } | ||
|
Malika7188 marked this conversation as resolved.
|
||
| }); | ||
| }); | ||
| }); | ||
|
|
@@ -270,6 +291,7 @@ var FlatpakPermissionsModel = GObject.registerClass({ | |
| GObject.signal_handler_block(this, this._notifyHandlerId); | ||
|
|
||
| Object.values(MODELS).forEach(model => model.updateStatusProperty(this)); | ||
| CONDITIONAL_MODELS.forEach(model => model.updateConditionalProperty(this)); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another data point regarding conditionals models list; we add a lot of properties that we don't ever use.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated the implementation so that conditional properties are only created for the four models in CONDITIONAL_MODELS, so we no longer add properties that aren't used. |
||
|
|
||
| GObject.signal_handler_unblock(this, this._notifyHandlerId); | ||
| } | ||
|
|
@@ -394,6 +416,7 @@ var FlatpakPermissionsModel = GObject.registerClass({ | |
| entry['groupStyle'] = model.constructor.getStyle(); | ||
| entry['groupDescription'] = model.constructor.getDescription(); | ||
| entry['statusProperty'] = `${property}-status`; | ||
| entry['conditionalProperty'] = `${property}-conditional`; | ||
| entry['serializeFunc'] = model.constructor.serialize; | ||
| entry['deserializeFunc'] = model.constructor.deserialize; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -150,6 +150,23 @@ var FlatpakSharedModel = GObject.registerClass({ | |
| }); | ||
| } | ||
|
|
||
| /* Flatpak drops a conditional if a bare grant for the same | ||
| * option is applied afterward, so this being set doesn't | ||
| * guarantee the permission is actually granted at runtime. */ | ||
| markConditional(option, rawValue) { | ||
| this._conditionals.set(option, rawValue); | ||
| } | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tested the following scenario:
What I see after that is:
I am wondering about the semantics here; it definitely does make sense to display the "conditional" icon when the original permission is still valid but, after it's negated, does it still make sense to display it? Is it adding useful information ? My first reaction is, probably not. Once we override, the "active" version of that permission is no longer the conditional but an explicit negation (not a conditional negation).
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. I've changed it so the conditional icon is hidden as soon as the permission is overridden, both by the user and globally. So in your PulseAudio example, only the override icon shows now. If the override is removed, the conditional icon comes back. |
||
|
|
||
| updateConditionalProperty(proxy) { | ||
| Object.entries(this.getPermissions()).forEach(([property, permission]) => { | ||
| const {option} = permission; | ||
| const conditionalProperty = `${property}-conditional`; | ||
| const value = this._conditionals.get(option) || ''; | ||
|
|
||
| proxy.set_property(conditionalProperty, value); | ||
| }); | ||
| } | ||
|
|
||
| updateProxyProperty(proxy) { | ||
| const originals = [...this._originals] | ||
| .filter(o => !this.constructor._isOverriden(this._globals, o)) | ||
|
|
@@ -190,6 +207,10 @@ var FlatpakSharedModel = GObject.registerClass({ | |
| const group = this.constructor.getGroup(); | ||
| const key = this.constructor.getKey(); | ||
|
|
||
| /* This only writes from _overrides (the on/off state), it does | ||
| * not write out anything from _conditionals. Saving can | ||
| * therefore drop an existing conditional entry from the | ||
| * override file. Write-back isn't implemented yet. */ | ||
| this._overrides.forEach(value => { | ||
| let _value = value; | ||
|
|
||
|
|
@@ -208,5 +229,6 @@ var FlatpakSharedModel = GObject.registerClass({ | |
| this._overrides = new Set(); | ||
| this._globals = new Set(); | ||
| this._originals = new Set(); | ||
| this._conditionals = new Map(); | ||
| } | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,14 @@ row .status.user { | |
| row .status.global { | ||
| color: @insensitive_fg_color; | ||
| } | ||
| row .conditional { | ||
| padding: 8px; | ||
| background-color: transparent; | ||
| background-image: -gtk-icontheme("dialog-question-symbolic"); | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explore other icons and colors options for this?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here are some of the icon and color options I explored. Let me know what you think about them. Option 1: The
Option 2: The Option 3: The |
||
| background-repeat: no-repeat; | ||
| background-position: center; | ||
| background-size: 16px; | ||
| } | ||
|
|
||
| row .content .bus .info, | ||
| row .content .variable .info, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /* exported FlatsealConditionalStatusIcon */ | ||
|
|
||
| /* conditionalStatusIcon.js | ||
| * | ||
| * Copyright 2026 Malika Odeny Asman | ||
| * | ||
| * This program is free software: you can redistribute it and/or modify | ||
| * it under the terms of the GNU General Public License as published by | ||
| * the Free Software Foundation, either version 3 of the License, or | ||
| * (at your option) any later version. | ||
| * | ||
| * This program is distributed in the hope that it will be useful, | ||
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| * GNU General Public License for more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public License | ||
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| const {GObject, Gtk} = imports.gi; | ||
|
|
||
|
|
||
| var FlatsealConditionalStatusIcon = GObject.registerClass({ | ||
| GTypeName: 'FlatsealConditionalStatusIcon', | ||
| Template: 'resource:///com/github/tchx84/Flatseal/widgets/conditionalStatusIcon.ui', | ||
| Properties: { | ||
| value: GObject.ParamSpec.string( | ||
| 'value', | ||
| 'value', | ||
| 'value', | ||
| GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT, | ||
| '', | ||
| ), | ||
| }, | ||
| }, class FlatsealConditionalStatusIcon extends Gtk.Image { | ||
| _init() { | ||
| super._init({}); | ||
| this._value = ''; | ||
| } | ||
|
|
||
| set value(value) { | ||
| if (this._value === value) | ||
| return; | ||
|
|
||
| this._value = value; | ||
|
|
||
| if (value === '') { | ||
| this.set_tooltip_text(''); | ||
| this.visible = false; | ||
| return; | ||
| } | ||
|
|
||
| const condition = value | ||
| .split(':') | ||
| .slice(2) | ||
| .join(':'); | ||
| this.set_tooltip_text(_('Only granted if: %s').format(condition)); | ||
| this.visible = true; | ||
| } | ||
|
|
||
| get value() { | ||
| return this._value; | ||
| } | ||
| }); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <interface> | ||
| <template class="FlatsealConditionalStatusIcon" parent="GtkImage"> | ||
| <style> | ||
| <class name="conditional"/> | ||
| </style> | ||
| </template> | ||
| </interface> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,3 @@ | ||
| [Context] | ||
| shared=unsupported | ||
| unsupported=always | ||
| unsupported=always;if:teleport:true | ||
|
Malika7188 marked this conversation as resolved.
Outdated
|
||




Uh oh!
There was an error while loading. Please reload this page.