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

Add Valkyrie-native permission handling #3938

Merged
merged 21 commits into from
Sep 17, 2019
Merged

Add Valkyrie-native permission handling #3938

merged 21 commits into from
Sep 17, 2019

Conversation

no-reply
Copy link
Contributor

@no-reply no-reply commented Aug 31, 2019

Adds a valkyrie-native approach for permission handling.

Valkyrie::Resource::AccessControls is insufficient, since Hyrax previously had support for arbitrary access modes (especially: Hydra::AccessControls heavily used :discover which isn't present in Valkyrie::Resource::AccessControls). The need to round-trip arbitrary access list entries motivated this re-implementation, as a per-requisite to #3584.

In the Valkyrie native implementation Hyrax permissions are managed via Access Control List style permissions. Legacy Hyrax models powered by ActiveFedora linked the ACLs from the repository object itself (as an acl:accessControl link to a container). Valkyrie models jettison that approach in favor of relying on links back from the permissions using access_to. As was the case in the past implementation, we include an object to represent the access list itself (Hyrax::AccessControl). This object's #access_to is the way Hyrax discovers list entries--it MUST match between the AccessControl and its individual Permissions.

The effect of this change is that our AccessControl objects are detached from Hyrax::Resource they can (and usually should) be edited and persisted independently from the resource itself.

Some utilitiy methods are provided for ergonomics in transitioning from ActiveFedora: the #visibility accessor, and the #*_users and #*_group accessors. The main purpose of these is to provide a cached ACL attached to a given Resource instance. However, these will likely be deprecated in the future, and it's advisable to avoid them in favor of Hyrax::AccessControlList, Hyrax::PermissionManager and/or Hyrax::VisibilityWriter (which provide their underlying implementations).

In support of this work, three explicitly different projections on the base-truth ACLs are implemented:

  • visibility supports the generic "public", "institutional", and "restricted" access groups available in the UI.
  • *_users/*_groups supports setting mode/agent pairs with a simple DSL via Hyrax::PermissionManager
  • Hyrax::AccessControlList provides a DSL for managing ACL entries directly.

The relationship between these three approaches is designed to have a clear dependency chain down to the base Permission data.

@samvera/hyrax-code-reviewers

@no-reply no-reply force-pushed the permissions branch 3 times, most recently from 3ec2129 to ee34081 Compare August 31, 2019 01:37
@no-reply no-reply changed the title WIP: Add Valkyrie-native permission handling Add Valkyrie-native permission handling Aug 31, 2019
@no-reply no-reply marked this pull request as ready for review August 31, 2019 01:37
@no-reply
Copy link
Contributor Author

no-reply commented Sep 3, 2019

cc: @cjcolvar

This is the completion of the work we decided to back off of in #3900

app/models/hyrax/access_control.rb Outdated Show resolved Hide resolved
# metadata. This approach also allows policies to be persisted using a
# different database or adapter than is used for curatorial objects.
class Permission < Valkyrie::Resource
include Dry::Equalizer(:access_to, :agent, :mode)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is neat, didn't know about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

pretty cool, right!?

attrs[:updated_at] = modified_date

attrs[:permissions] = permissions.map do |permission|
agent = permission.type == 'group' ? "group/#{permission.agent_name}" : permission.agent_name
Copy link
Contributor

Choose a reason for hiding this comment

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

Would it make sense to consider having the group/ prefix be a constant? The usage seems pretty much isolated to private methods in PermissionManager and PermissionValue#result. Just a thought.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeah, i think it would be a good idea. i didn't pursue it during development because it wasn't clear to me where that constant should be namespaced.

i think it's still not clear, but this is all static enough now that we could probably just pick a place and have it be fine. ideas?

Copy link
Contributor

@mcritchlow mcritchlow Sep 11, 2019

Choose a reason for hiding this comment

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

I wasn't sure either.. I'm tempted to say Hyrax::Group::PREFIX, or something along those lines. At least to me that reads fairly directly and seems in context. That's my gut reaction, I suppose.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh, that seems pretty good. i'll go with that.

elrayle
elrayle previously approved these changes Sep 16, 2019
Tom Johnson added 13 commits September 16, 2019 15:09
The persister spec has a hard expectation on a specific array, but the test
really doesn't care about enumerable format or order. Make this more tolerant by
using the `contain_exactly` matcher.
Some of these tests are order dependent, and fail on repeated runs of just the
ModelTransformer tests. Cleaning up the repository ensures they work as expected
regardless of test order.
If we want to check what object a permission is applied to, there's now a
matcher for that.

`expect(permission).to grant_permission(:read).on(object_id).to(agent.id)`
This is the beginning of a larger effort to establish Valkyrie-native permission
management/ACLs.
Adds support for editing ACLs for a given resource using Valkyrie interfaces.
Adds a simple DSL for adding and removing permissions by mode/user pairs. This
reduces the overhead/possibility for error in manually creating ACL policies by
directly managing `Hyrax::Permission` objects.
This model wraps `Permission` data for an individual object. For many Valkyrie
adapters, this will make it possible for grouped permissions to be saved as a
single unit.
The previous approach for saving valkyrie objects with FactoryBot's `#to_create`
would save them fine, but then returned an unsaved object instance (i.e. one
without an `#id` or create/modified dates). Using a dedicated strategy gives us
a bit more control over what happens when we save a valkyrie object.

This approach can also be extended to better support "associations" and similar
for Valkyrie resources.

See: https://github.com/thoughtbot/factory_bot/blob/master/GETTING_STARTED.md#custom-strategies
Ensure `#access_to` persists, and is saved on ActiveFedora/Hydra Access Controls
`Permission` objects when saved from native `valkyrie` using Wings.
Given a resource, we want to be able to find an AccessControl object for it.

For a typical valkyrie adapter, this is an inverse reference lookup on the
`AccessControl#access_to` property. An initial implementation does a broad
reverse lookup on `#access_to`, then filters the result classes. It assumes the
first `AccessControl` found is the correct one.

For Wings, the story is a bit different. `Hydra::AccessControl` resources don't
know what their target is. When rebuilding valkyrie resources from
`Hydra::AccessControl`/AF objects, we inspect the permissions to assign
`#access_to`, but we can't use this for lookup. Instead, we can rely on the
`ActiveFedora`/`Hydra::AccessControls` pattern of looking up the
`Hydra::AccessControl` object from its target (via its `belongs_to` reference).
Integrate `Hyrax::AccessControl` into the `AccessControlList` service. This
service now saves an `AccessControl` when its save method is called.

Rather than tracking changes manually in the `AccessControlList` service, we can
now use `Valkyrie::ChangeSet` to track them directly against an
`Hyrax::AccessControl` object.
Tom Johnson added 7 commits September 16, 2019 15:09
Allow a `Hyrax::Resource` to cache a `PermissionManager` (and therefore an ACL
`ChangeSet`). Delegate current `#read_groups=` and similar management to it, and
also use it in `VisibilityReader` and `VisibilityWriter`. This allows each
Resource to maintain its own current permission state across the various access
lenses (visibility, `*_users/*groups`, and permissions) in a single place.

When we convert from an `ActiveFedora` object, we ensure that the current
permissions are copied down to the Resource. Notably we don't do this in the
other direction; our implementation of permissions in Valkyrie treats them as
separate resources, pointing back to repository Objects. Changing an ACL, and
then saving the object it points to has no effect: you need to save the ACL
explictly.
`Enumerator#+` was added in Ruby 2.6, so `+=` won't work in Ruby 2.5.
This attribute should have a type that indicates that the members of the set
should all be Permission objects.
This avoids relying on hard coded strings to understand group names as handled
by Valkyrie adapters.
Valkyrie 2.0.0 treats more values as frozen, so we can't do `#map!` in
permission arrays. Instead of converting inline, return a new permission array
when converting permission values.
Copy link
Contributor

@elrayle elrayle left a comment

Choose a reason for hiding this comment

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

The new change for Valkyrie 2.0.0 looks good to go.

@lsitu
Copy link
Contributor

lsitu commented Sep 17, 2019

👍

@no-reply no-reply merged commit 4415e60 into master Sep 17, 2019
@VivianChu
Copy link
Contributor

👍

@hweng
Copy link
Contributor

hweng commented Sep 17, 2019

awesome work 👍

@no-reply no-reply deleted the permissions branch October 8, 2019 06:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants