Skip to content

Authorization

Henne Vogelsang edited this page Oct 25, 2023 · 6 revisions

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

Pundit

We actually have two architectures. The newer one is quickly explained, go read the pundit documentation.

Read on to understand the old system.

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 ``UserorGroup` 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.

Clone this wiki locally