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
Introduce ACLs for menu entries #5670
Conversation
ProtectionMixin (you also want the option to set it as protected or inheriting) makes perfect sense here. About the special "speaker" principal I'm not sure what's the best solution here, since you can have only one such entry for an ACL. OTOH, being able to grant access to speakers may be interesting in other places as well. I have to think about what's the best approach. What comes to my mind first is just using a column for this special kind of access (ie what we have right now, but to grant access to speakers instead of restricting it to them) and when it's protected we check that plus the ACL. I'll think a bit about it and get back to you one of these days (I'm off today and kind of busy until at least wednesday, so likely later this week). |
So after having given this some thought, I think making it an Principal type in the backend is not a good option, because all other principal types reference something, but this one doesn't have anything useful to reference. I think using a column but integrating it more nicely with the access check logic is the best way to go. You could implement this in And to keep this plugin from becoming overly complex I'd limit it to:
for the migration step you probably want to include this in the same one that also applies the database structure changes so you can first add the new columns and acl table, then migrate the data, and then delete the old unused column (and vice versa in case of downgrade). |
What I am worried about is that this creates some hacks for |
Looks like it won't be too much of an issue, just need to adjust the data structure sent in the form and process it correctly. I have some questions I'll ask inline in a sec. |
# TODO is this just legacy and needs to be merged? | ||
#if not self.can_inherit_protection and not self.is_unlisted_event: | ||
# kwargs['skip'] = {ProtectionMode.inheriting} | ||
super().__init__(*args, enum=ProtectionMode, skip=self.protected_object.disallowed_protection_modes, **kwargs) |
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 question would be good to answer. It sounds like this code might predate the ProtectionMixin, since it is trying to be smart on its own. Shouldn't this just be checking disallowed protection modes on the object itself?
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's not enough because an event can typically have inheriting protection except if it's an unlisted event (which has no category to inherit from).
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'd argue that this should rather be in the protection mixin, maybe with a property effective_disallowed_protection_modes
, or renaming the property already defined on the class to _disallowed_protection_modes
. IIUC when an event is unlisted, it has no protection parent, so the extra check on it being an unlisted even is redundant and this should work:
@property
def effective_disallowed_protection_modes(self):
if self.protection_parent:
return self.disallowed_protection_modes
else:
return self.disallowed_protection_modes - {ProtectionMode.inheriting}
I imagine any additional use of disallowed_protection_modes
would need to reproduce the parent check logic, but given it is the only use right now I don't need to change it in this PR. Let me know what you prefer.
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'd keep it simple in the PR. refactoring and features in the same PR usually make things harder to review
@ThiefMaster in exploring how where to add the "Speakers" option in the UI, I'm wondering if it makes sense to expose all of the built-in event roles in the I could do that as a special case where the built-in event roles are just added to the dropdown next to the custom roles in the UI, or I could make some (to be explored) changes to the principal to accept special values for the builtin event roles and also retrieve them from the API. What do you think? |
TBH I'd keep this PR simple and first of all add it with a separate form field... and then consider improvements in a separate PR. "Event Roles" are user-defined roles (basically event-scoped groups) so I would not mix those. FWIW for improvements I'd consider UI similar to what we have on the event protection page and then maybe have grayed-out lines for special things like speakers, which would become active when you tick them to enable them. But that'd be more of a bigger task to convert all the ACL related UI fields to a nice react-based widget. |
I came to this conclusion since on the "Participant Roles" view in the event there is a mix of the custom event roles and the built-in roles like speaker and author. So there is already a mix going on there? While they are technically separate, from a product point of view they are very much the same. If we don't want to do a major revamp now, what I'd recommend is:
If you'd like to go with a completely separate |
I think the participant roles is special since this isn't really access control related. I'm very much against mixing event roles and other things in the same dropdown. So in this PR I'd go for the completely separate toggle.. There's a good chance I'll merge this PR only after branching off to start development for 3.3 in |
Ok, very well. I did notice after commenting the fancy boxes and icons from the event protection widget aren't in this one, so it would indeed make them hard to differentiate. I should be able to complete this in the next few days, all I have left is the speaker toggle, testing the data migration more elaborately, and some tests. |
@ThiefMaster this one should be ready for review now. Do we do unit tests for migrations in some way? The downgrade seems a bit more complex. Also if you could provide some guidance on the exporters, I really don't know what I am doing there and if it is correct. |
indico/migrations/versions/20230306_0905_e47fc6634291_add_menu_entry_acls.py
Outdated
Show resolved
Hide resolved
No good way to test migrations in unit tests... :/ TBH for something this complex I usually go for the easy way and don't try to do it in pure SQL but simply query it and then update the data using Python code (all the places with a Anyway, I'll giving your migration code a try! If it works in pure SQL even better ;) |
4218535
to
f22d248
Compare
It's stupid to do that, but preventing it from being shown is easier than preventing the user from making such a page the default page or restricting a page that's already the default...
Check for principal first, this is more likely to be optimized while speaker checks always trigger a query. Also make those two methods internal since they are not meant to be used directly from outside.
Those are never part of event exports since such exports contain only events (and users), so it would not be possible to import an export file containing such ACL entries (or the export would already fail since the referenced models cannot be exported)
speaker_allowed and allow_speakers were WAY too similar...
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.
In case you are curious about the things I changed, I split them into many individual commits with descriptive messages.
@@ -221,6 +221,12 @@ export: | |||
fks_out: | |||
- event_role_id | |||
|
|||
MenuEntryPrincipal: |
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.
Also if you could provide some guidance on the exporters, I really don't know what I am doing there and if it is correct.
Since you asked about how the export stuff works some time ago: I'll add some comments below to explain it
@@ -221,6 +221,12 @@ export: | |||
fks_out: | |||
- event_role_id | |||
|
|||
MenuEntryPrincipal: | |||
skipif: ROW.type.name not in ('user', 'event_role', 'registration_form') |
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.
And table row matching this condition won't be exported. This is done to get rid of anything that is not portable between instances (category roles and groups) - only events and users are included in exports.
fks_out: | ||
- registration_form_id | ||
- event_role_id |
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 ensures ordering in the export file: registration forms and event roles must be imported first, because it's impossible to create the menu entry principals without those if they are referenced (they can't be set lazily later, because an event role principal cannot have null event role id)
@@ -391,6 +397,8 @@ export: | |||
# conference menu | |||
MenuEntry: | |||
skipif: ROW.type.name == 'plugin_link' | |||
fks: | |||
- MenuEntryPrincipal.menu_entry_id |
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 triggers the export if MenuEntryPrincipal objects if they are referenced in a MenuEntry that has been exported
@@ -477,6 +485,7 @@ import: | |||
ContributionPrincipal.user_id: skip | |||
AttachmentPrincipal.user_id: skip | |||
AttachmentFolderPrincipal.user_id: skip | |||
MenuEntryPrincipal.user_id: skip |
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.
When the user chooses to not import missing users during the event import, then this skips the MenuEntryPrincipal object altogether (it's pointless to import a user acl entry if that user does not exist on the target indico instance)
Hey @ThiefMaster, I'd love to get some feedback on this approach, as it is turning out to be massively more complicated than I initially expected. There are still quite a few loose ends, but I want to see if I am going into the right direction.
Is your feature request related to a problem? Please describe.
We sometimes have sponsored attendees at our events. We'd like to show them a page in the sidebar with the expense policies. We don't want this page to be visible to everyone else.
Describe the solution you'd like
I'd like Menu Entries to have ACLs so I can restrict viewing the and their visibility in the menu to people who have registered using a specific form.
Describe alternatives you've considered
Right now, all we can do is send them the link to an unlisted page, or open up the expense policy page to everyone (non-sponsored attendees, random remote participants)
Current Status
I've hooked up UI using the permission list field and have taken a stab at the backend database. A few features are still missing:
Questions:
SpeakerPrincipal
so that I can reference the event though.ProtectionMixin
since it provides many convenience functions. Is this the right path, or is it going overboard for a simple yes/no type of access?Any other comments you might have are appreciated.
Screenshots