Skip to content

Authorization

Dani Donisa edited this page Oct 26, 2023 · 6 revisions

This is a description of the OBS authorization (who can do what) architecture.

Role

A User or a Group can have many Role.

A Role can be global

[1] pry(main)> Role.where(global: true).map(&:title)
=> ["Admin", "Staff", "Moderator"]

A Role can be local

[2] pry(main)> Role.where(global: false).map(&:title)
=> ["maintainer", "bugowner", "downloader", "reviewer", "reader"]

That distinction will make more sense later, read on.

StaticPermission

A Role has many StaticPermission. A StaticPermission is an "action" like:

[3] pry(main)> StaticPermission.all.map(&:title).first(3)
=> ["access", "change_package", "change_project"]

Relationship

The association between Project or Package and User or Group is called Relationship

  • A Project or a Package can have many Relationship
  • A Relationship can have one User or Group
  • A Relationship has one Role (that is local)

Flag

Many aspects of a Project be configured by attaching Flags to it.

A Flag has a position, flag (name) and status

[4] pry(main)> Flag.last.slice(:position, :flag, :status)
=> {"position"=>2, "flag"=>"publish", "status"=>"disable"}

Two of those Flag configure authorization aspects of a Project.

access

If a Project has a Flag.where(flag: :access, status: :disable) then it's only accessible for the User or Group that have a Relationship with it.

sourceaccess

If a Project has a Flag.where(flag: :sourceaccess, status: :disable) then files (sources) of all the Package in the Project are only accessible for the User or Group that have a Relationship with it.

Authorization checks

With all of the above we can do the various authorization checks we do throughout the app.

Global Role check

if @user.roles.exists?(title: 'Admin')...

Global "action" (StaticPermission) check

if @user.roles.any? { |role| role.static_permissions.find_by(title: 'change_project') }
  ...
end

Local Role check

[5] pry(main)> role = Role.find_by(title: 'maintainer'); nil
=> nil
[6] pry(main)> user = User.find_by(login: 'hennevogel'); nil
=> nil
[7] pry(main)> Project.find_by(name: 'home:hennevogel').relationships.exists?(role_id: role.id, user_id: user.id)
=> true

Flag Check

if project.flags.any? { |flag| flag.flag == 'sourceaccess' && flag.status == 'disable' }
  ...
end

Project.default_scope

This is slightly more complicated. We apply a default_scope to avoid instantiating Project that have an "access" Flag.

default_scope { where.not('projects.id' => Relationship.forbidden_project_ids) }

Where Relationship.forbidden_project_ids is a method that runs (and caches) a complicated query per User of all the Project with Flag.where(flag: :access, status: :disable) that they are NOT having a Relationship with.

Pundit policies

Most of our pundit policies make use of the above checks too. Go read the pundit documentation to understand the rest.

Clone this wiki locally