-
Notifications
You must be signed in to change notification settings - Fork 57
Add service module to write PolKit agents #302
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?
Conversation
|
While I am not opposed to the addition of a polkit integration, I would rather avoid all KDE dependencies as including them leads to massive dependency trees on most distros, and they're usually pretty replaceable. polkit-qt-1 appears to be a very thin wrapper around the polkit api that doesn't provide much. I would prefer to avoid it unless you have a good reason to pull it in. |
|
polkit-qt-1 is indeed a very thin wrapper. The 'good' reason for using it is that it deals with some of the more error-prone aspects of the upstream libraries and doesn't really bring anything that we don't need. While it is maintained by the KDE team, the package doesn't actually pull in anything but Polkit itself and Qt on the distros I checked (Debian, Ubuntu, Arch, Fedora, RHEL, NixOS, or Gentoo). I find it unlikely that others would have wildly different dependencies. That said, it should also be fairly easy to not use it at the cost of a few hundred LOC, which would have the benefit of me getting to correct one of the limitations of polkit-qt-1. |
|
It sounds like one of the more reasonable ones if we had to use it then, though I would personally still prefer not to. |
|
Alright. I'll see what I can do. |
|
I removed the dependency on polkit-qt, interacting with the PolKit libraries directly, instead. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Partial review on the interface.
Before continuing further, I've noticed the polkit agent interface appears to be available directly over dbus. Is there a reason to use any library at all for it? (Especially a gobject library)
|
Thanks for the review! I don't like using a gobject library here either, but yes, there is a good reason for using it instead of interacting directly over DBus. PolKit requires the client that sends the confirmation message to be privileged. To this end, To avoid libpolkit, Quickshell would either need to run as root or we would need our own setuid helper. Besides the implementation overhead, this would break simply running it from your Flake via Nix or installing it via Home-Manager. I imagine (although I don't positively know) many distros might also look more sceptically at any setuid binaries. Finally, the DBus interface isn't actually stable because it is currently considered an implementation detail. I don't think it has changed significantly in recent years, but I would still prefer the more stable solution. |
|
That's horrible on so many levels. |
|
I... yeah. But that's Polkit for you. There's many things I wish I didn't know about it. If, given these circumstances, you'd rather not have the code upstream, I'm just going to package it up as a separate library. |
|
I'm fine with the gobject code since its an actual requirement, and this is a feature many people want. I'll finish the review tomorrow probably. |
|
Thank you! I'm not in any rush. Also, fair warning: this is the first 'not insignificant' thing I've done with Qt, so while I tried to do things right, some things might be the way they are because I didn't know any better. I'll happily take any pointers. |
|
Thats fine. One further thing that would be helpful would be to add a manual test qml file similar to what exists in other parts of the source. Just cover a reasonable amount of the api for a simple manual regression test |
|
I got started on some of your comments. Here's an additional thing we might consider: PolKit can send either 'show-info' or 'show-error'. I am currently storing both into the same property with an One thing I'm a bit unsure on is object destruction/cleanup and its behavior in Qt. When an authentication request finishes, we need to clean up all objects, but does this cause the UI to flicker, if we animate a window out, for example? |
Does the api provide any sort of hint as to if both should be present or not? If it does something like sending a new version of the error message thats an empty string to reset thats a pretty clear signal we could have multiple, but if not I'm less sure. If the messages are coming from pam, our pam module currently replaces the last message with the new one so it probably makes sense to keep that design.
This is a bit of a mess, Quickshell in particular has a hack for it called Retainable which you can see here https://quickshell.org/docs/v0.2.1/types/Quickshell/Retainable/. Retainable objects generally hold their last state and become immutable while retained to make it easier to write reactive UI against them. Note: I tested the dialog and see you have a lot of |
Unfortunately not. The messages are indeed forwarded from PAM. I am therefore inclined to leave it as is.
Thanks! I'll look into that to figure out how best to use it in this scenario.
That's exactly what I was looking for. I'll migrate to that. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not super in depth as I've been busy, but I gave it a look-over.
src/services/polkit/qml.cpp
Outdated
| // First, destroy all but the first request in the queue, so that the cancel | ||
| // method doesn't start a new one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would think its not unreasonable to handle a request right after cancelling a different one, especially if they're for different programs.
Also, when reloading the graph, preserving the old polkit session may be the most reasonable case, especially if the user wants to do something like edit their auth dialog with a real app running to test with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Handling one request after another is the default behavior in finishAuthenticationRequest. The cancelAuthenticationRequest method calls finishAuthenticationRequest and therefore would start another request immediately.
However, we are in the destructor of the agent here, so there will be no-one to handle the queued requests after the destructor has run. Therefore I want to reject all currently queued requests and not start processing them. To this end, I start by rejecting all requests after the current one, so that when the cancel method runs, it won't have any more to start.
In the case of reloading, I agree, preservation would be ideal. I'll look into how I can solve that cleanly only for the case of the reload, but still dismiss everything when actually destroying the agent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As for reloading see my comment below about using an internal singleton. you can wait for the destruction of the last engine generation with a ref to the backing agent to cancel all requests and deregister the agent, or some similar pattern.
src/services/polkit/qml.cpp
Outdated
| // In case of config reloads new objects are constructed before the old ones | ||
| // are destroyed. Therefore the new agent cannot take over the path still in | ||
| // use by the old agent. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can as long as its negotiated. I think we established it doesn't make sense to have more than one agent, so a single agent per process solves this nicely.
More than one polkit agent object at a time in a single generation however does not make sense and should print an error message and render any agent objects >1 inoperable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It can as long as its negotiated.
Are you referring to some particular Quickshell interface or is this a general remark? If the former, do you have a pointer? If the latter, this code is the early version of this negotiation. I meant to express 'it cannot take over the path without doing something to support it explicitly'. In the old version using polkit-qt, it wasn't possible to transfer the listener over, so closing the path and then opening it again was necessary. Now it might be possible and cleaner to transfer the listener. I'll look into it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure I fully understand the particular problem here, but singleton resources that live longer than a single engine generation are used in a few places in Quickshell.
Since the qml engine controls the lifecycle of all objects created in it, Quickshell often has an internal singleton object with a thin wrapper object owned by the qml engine instead, so it doesn't neeto die with the generation.
With the restriction that we can only reasonably have one polkit agent at once, it seems logical to me to represent it this way.
What I meant by negotiation was only allowing one qml owner of the polkit agent per generation, but keeping tha actual agent object the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we agree conceptually, I'm just not sure of the exact API surface that permits me to do this cleanly. I can obviously put the listener itself into a singleton and introduce a thin wrapper. Since we want to permit only one such wrapper per generation, I need to add a check to this effect: If a new PolkitAgent is constructed, I can check this singleton and see one already exists and set an error state.
In the case of a config reload, I will also see the listener is already in use, but in this case I want to take ownership of it. I could attempt to use currentGeneration to detect this, but this seems mildly heuristic. Alternatively, I could extend Reloadable and do it in Reloadable#onReload. Do you have a preference for any of these approaches or is there some other idiomatic way I'm not seeing currently?
Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Theres a couple differences in the approaches you could take:
- Reloadable uses heuristics to try to match an object in the old generation to one in the new generation. This is the system that allows allocated windows to be reused. You could use this if you wanted to only persist the polkit agent if the user placed it in the same spot in the same file across generations.
- PostReloadHook fires unconditionally after Reloadable and has no concept of the scene graph. Combining this with
findObjectGenerationon the user facing wrapper objects from the current and previous generations will tell you if we're switching generations or the user created a second invalid agent. AvoidcurrentGeneration.
Either of these interfaces you would implement on the wrapper object and not the internal singleton.
I recommend the PostReloadHook approach but would accept either.
|
I performed a fairly significant refactoring to accommodate the changes you recommended.
I'll look at the lint errors soon. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Re-reviewed the pr as a whole
| /// The icon to present to the user in association with the message. | ||
| /// | ||
| /// The icon name follows the [FreeDesktop icon naming specification](https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html). | ||
| Q_PROPERTY(QString iconName READ iconName CONSTANT); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Best to link to @@Quickshell.Quickshell.iconPath() to display it in an image
src/services/polkit/flow.hpp
Outdated
| /// Setting this will abort any ongoing authentication conversations and start a new one. | ||
| Q_PROPERTY(Identity* selectedIdentity READ selectedIdentity WRITE setSelectedIdentity NOTIFY selectedIdentityChanged); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Setting" -> "Changing"
Reactive changes can cause a lot of "Setting" so this wording could be confusing
src/services/polkit/flow.cpp
Outdated
| emit completedChanged(); | ||
| emit authenticationSucceeded(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this-> on method calls including emit, as well as members. ci should be catching this one if its not
src/services/polkit/flow.hpp
Outdated
| Identity* mSelectedIdentity = nullptr; | ||
| bool mIsResponseRequired = false; | ||
| QString mResponseMessage; | ||
| bool mResponseVisible = false; | ||
| QString mExtraMessage; | ||
| bool mExtraIsError = false; | ||
| bool mIsCompleted = false; | ||
| bool mIsSuccessful = false; | ||
| bool mIsCancelled = false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While acceptable as-is, properties like this are better handled with QObject bindable properties, which are basically C++-side reactive properties.
src/services/polkit/qml.cpp
Outdated
| void PolkitAgent::classBegin() { | ||
| // Nothing to do here. | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
just leave classbegin () {} in the header
src/services/polkit/qml.cpp
Outdated
| namespace qs::service::polkit { | ||
| PolkitAgent::PolkitAgent(QObject* parent): PostReloadHook(parent) {} | ||
|
|
||
| PolkitAgent::~PolkitAgent() = default; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this can also go in the header
src/services/polkit/qml.cpp
Outdated
| emit isRegisteredChanged(); | ||
| emit isActiveChanged(); | ||
| emit flowChanged(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another good site for bindable properties
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on my current understanding of bindable properties (at least as far as the Q_OBJECT_BINDABLE_PROPERTY) is concerned, are backed by a field in the object. This is not the case for the properties that are associated with these signals. I am sure they can be hooked up to work anyway, but I'm not sure if that would still be simpler?
I think I've figured it out. Will hopefully push something later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They are field backed but when used with a binding callback the field acts as more of a cache than anything else. It also compares old/new values to see if anything changed and emit signals or not.
There are cases where it improves readability, generally the more values that depend on eachother the greater the improvement.
Here's an example of it being used with a binding:
| this->bcExclusiveZone.setBinding([this]() -> qint32 { |
And heres an example of it being used as a field that just so happens to emit signals automatically on prop change:
| Qt::beginPropertyUpdateGroup(); |
Up to you where you use it but it does generally make things clearer.
|
Thank you for the extensive review! I addressed most of your comments. I also introduced a RAII helper I selectively turned off some lints in specific instances where I felt they were not helpful or actively wrong (e.g. it won't let me forward declare any structs from the Polkit libraries). There should always be an accompanying comment explaining why the specific rule doesn't make sense in that case. Let me know if you disagree with any of my reasons. |
|
I do love a good reactivity system. I switched over to bindable properties where I figured it makes sense. With that I believe I have addressed everything you pointed out. I appreciate your patience, this has been quite the learning experience. |
|
I rebased onto current master, squashed and fixed a small bug in the reloading logic. I have also integrated this into Noctalia in a branch to validate in a more complex config. |
This implements a new service module that enables users to write PolKit agents by integrating with
libpolkitandlibpolkitagent. A test configuration is added to the source.Status: I've done everything I have planned.
Original description
This implements a new service module that enables users to write PolKit agents. To this end, I'm integrating with the existing
polkit-qt-1library that is also used by the KDE agent.A relatively minimal example configuration using this new service can be found here.
There's a minor TODO left around cleanup of the
Identityobjects that I haven't been able to get quite right due to my inexperience with Qt. I'm hoping you might have a clean solution for this.Closes #271.