Skip to content

Conversation

@landongrindheim
Copy link
Member

Until now, we've allowed a Rubygem to be owned by users (via Ownerships) and an Organization. This has some downsides. For example, we've had to explicitly check both ownerships and organization membership in our gem-related policies.

I've opted to push this logic into Rubygem and enforce the ownership check to be organization-specific if the gem belongs to an organization OR ownership-specific (i.e. user-specific) if it is not.

Until now, we've allowed a `Rubygem` to be owned by users (via
`Ownership`s) and an `Organization`. This has some downsides. For
example, we've had to explicitly check both ownerships and organization
membership in our gem-related policies.

I've opted to push this logic into `Rubygem` and enforce the ownership
check to be organization-specific if the gem belongs to an organization
OR ownership-specific (i.e. user-specific) if it is not.
@codecov
Copy link

codecov bot commented Jul 25, 2025

Codecov Report

❌ Patch coverage is 98.36066% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 97.22%. Comparing base (8fa3e3d) to head (28ca093).

Files with missing lines Patch % Lines
app/models/gem_permissions.rb 96.42% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #5849      +/-   ##
==========================================
- Coverage   97.24%   97.22%   -0.02%     
==========================================
  Files         476      477       +1     
  Lines        9785     9816      +31     
==========================================
+ Hits         9515     9544      +29     
- Misses        270      272       +2     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

ownerships.user_with_minimum_role(user, minimum_required_role).exists?

if owned_by_organization?
organization.memberships.where(user: user).with_minimum_role(minimum_required_role).exists?
Copy link
Member

Choose a reason for hiding this comment

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

This will leave out the current implementation of Outside Contributors, which uses the ownerships table.

@landongrindheim landongrindheim changed the title Make ownership an organization OR user concern 🛂 Update Handling of Gem Permissions Jul 30, 2025
This variant feels cleaner to me. Hopefully others will agree.
@landongrindheim landongrindheim force-pushed the landon/push-ownership-logic-to-rubygem-model branch from 618894b to 964234a Compare July 30, 2025 12:33
Ownership is proving to be trickier to keep consistent than I had hoped
it would be. I've been migrating to a permissions-based approach, which
has similar concerns beneath the covers, but is more explicit in its
implementation.
Ownership has proven to be more complicated than expected, and these
methods were primarily used by our policy code. The logic has been
extracted into GemPermissions, where we've been able to get a bit more
exact.
@landongrindheim landongrindheim force-pushed the landon/push-ownership-logic-to-rubygem-model branch from bf3166e to f155580 Compare July 31, 2025 17:02
Comment on lines +39 to +54
def org_member_in_role?(minimum_role)
case minimum_role
when :maintainer
rubygem.organization.user_is_member?(user)
when :admin
rubygem.organization.administered_by?(user)
when :owner
rubygem.organization.owned_by?(user)
else
false
end
end

def user_in_role?(minimum_role)
rubygem.ownerships.user_with_minimum_role(user, minimum_role).exists?
end
Copy link
Member Author

Choose a reason for hiding this comment

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

I think it makes sense to leave these here for now, but I foresee a future where org- (and team-) based permissions are checked in a separate class.

@landongrindheim landongrindheim marked this pull request as ready for review August 7, 2025 17:11
@colby-swandale
Copy link
Member

I feel that this Pull Request needs a bit more time to bake before I'm ready to accept it. This is a good step, but I also feel it's not complete either. With Organisation teams coming down the pipeline I feel it would be a good opportunity to spend a bit more time on this?

@landongrindheim
Copy link
Member Author

I feel that this Pull Request needs a bit more time to bake before I'm ready to accept it. This is a good step, but I also feel it's not complete either. With Organisation teams coming down the pipeline I feel it would be a good opportunity to spend a bit more time on this?

@colby-swandale I think you're right to be cautious with core permission logic. The organization teams work will add complexity here.

Would it be helpful if I walked through how this is designed to extend? I can show how the permission methods would handle the additional cases we'll need, or we can schedule a quick call to discuss the technical approach.

Happy to adjust the design based on our conversations, or hold off if you'd prefer to finalize the broader requirements first 😄

@landongrindheim landongrindheim requested review from colby-swandale and jenshenny and removed request for colby-swandale and martinemde October 28, 2025 19:57
Copy link
Member

@jenshenny jenshenny left a comment

Choose a reason for hiding this comment

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

I like that the gem permissions logic is more centralized with this PR.

Comment on lines +49 to +51
def can_delete_gem?(user)
GemPermissions.new(@rubygem, user).can_push?
end
Copy link
Member

Choose a reason for hiding this comment

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

I think delete_gem? would be good to have in GemPermissions


def owns_gem?(rubygem)
rubygem.owned_by?(self)
def can_push?(rubygem)
Copy link
Member

Choose a reason for hiding this comment

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

Do these mean the same? I thought owns_gem? would be like an owner while can_push is like a maintainer with lower permissions

Copy link
Member Author

Choose a reason for hiding this comment

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

As the code is currently written (i.e. in main, but not in this branch), Rubygem#owned_by? only considers whether there is an association. It does not consider role, which is why I've chosen to use a name reflecting lower-permissions.

I've lost track of what I was trying to test, and using a non-existing
role results in an error. Removal seems appropriate.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

3 participants