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

Permissions endpoint #559

Merged
merged 7 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -153,11 +153,17 @@ They are based on `config/settings/default.yml`.

Debugging:

Insert a `debugger` statement where you would like the debugger to stop. Alternatively you can enter
a break point after the debugger starts.


```shell
(host) > docker compose exec web bash
(container) > RAILS_ENV='test' rdbg --command -- /home/baw_web/baw-server/bin/rspec /home/baw_web/baw-server/spec/lib/modules/filter/query_spec.rb -e association
```

More information here: https://github.com/ruby/debug

### Tests

The tests are run using rspec:
Expand Down
62 changes: 47 additions & 15 deletions app/controllers/permissions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ class PermissionsController < ApplicationController
def index
do_authorize_class
get_project
do_authorize_instance(:update_permissions, @project)
respond_to do |format|
format.html do
do_authorize_instance(:update_permissions, @project)

result = update_permissions
result = update_permissions

if result === true
flash[:success] = 'Permissions successfully updated.'
elsif result === false
flash[:error] = 'There was an error updating permissions. Please try again or contact us.'
end
case result
when true
flash[:success] = 'Permissions successfully updated.'
when false
flash[:error] = 'There was an error updating permissions. Please try again or contact us.'
end

respond_to do |format|
format.html do
params[:page] ||= 'a-b'
redirect_to project_permissions_path(@project, page: params[:page]) unless result.nil?
@permissions = Permission.where(project: @project)
Expand All @@ -27,7 +28,7 @@ def index
format.json {
@permissions, opts = Settings.api_response.response_advanced(
api_filter_params,
Access::ByPermission.permissions(@project),
Access::ByPermission.permissions(Current.user, project_id: @project.id),
Permission,
Permission.filter_settings
)
Expand Down Expand Up @@ -75,6 +76,19 @@ def create
end
end

# PUT|PATCH /projects/:project_id/permissions/:id
def update
do_load_resource # @permission = Permission.find(params[:id])
get_project # @project = Project.find(params[:project_id])
do_authorize_instance # authorize! :update @permission

if @permission.update(permission_params)
respond_show
else
respond_change_fail
end
end

# DELETE /projects/:project_id/permissions/:id
def destroy
do_load_resource
Expand All @@ -88,20 +102,37 @@ def destroy
end
end

# GET|POST /projects/:project_id/permissions/filter
def filter
do_authorize_class
get_project
filter_response, opts = Settings.api_response.response_advanced(
api_filter_params,
Access::ByPermission.permissions(current_user, project_id: @project.id),
Permission,
Permission.filter_settings
)
respond_filter(filter_response, opts)
end

private

def get_project
@project = Project.find(params[:project_id])

@permission.project = @project if defined?(@permission) && @permission.project.blank?
return unless defined?(@permission)

@permission.project = @project
@permission.project_id = @project.id
end

def permission_params
params.require(:permission).permit(:level, :project_id, :user_id)
params.require(:permission).permit(:level, :project_id, :user_id, :allow_logged_in, :allow_anonymous)
end

def update_permissions_params
params.slice(:project_wide, :per_user).permit(project_wide: [:logged_in, :anonymous], per_user: [:none, :reader, :writer, :owner])
params.slice(:project_wide, :per_user).permit(project_wide: [:logged_in, :anonymous],
per_user: [:none, :reader, :writer, :owner])
end

def update_permissions
Expand All @@ -123,9 +154,10 @@ def update_permissions
new_level = request_params[:project_wide][:anonymous].to_s
elsif request_params.include?(:per_user)
user_id = request_params[:per_user].values.first.to_i
permission = Permission.where(project: @project, user_id: user_id, allow_logged_in: false, allow_anonymous: false).first
permission = Permission.where(project: @project, user_id:, allow_logged_in: false,
allow_anonymous: false).first
if permission.blank?
permission = Permission.new(project: @project, user_id: user_id, allow_logged_in: false, allow_anonymous: false)
permission = Permission.new(project: @project, user_id:, allow_logged_in: false, allow_anonymous: false)
end
new_level = request_params[:per_user].keys.first.to_s
else
Expand Down
13 changes: 5 additions & 8 deletions app/models/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -226,17 +226,14 @@ def to_permission(user)
# GET /projects/:project_id/permissions/:id permissions#show {:format=>"json"}
# DELETE /projects/:project_id/permissions/:id permissions#destroy {:format=>"json"}

# any user, including guest, with reader permissions on project can access #new
can [:new], Permission do |permission|
check_model(permission)
Access::Core.can?(user, :reader, permission.project)
end

# only owners can list, change, or remove permissions
can [:index, :show, :create, :destroy], Permission do |permission|
# only owners can show, change, or remove permissions
can [:show, :create, :update, :destroy], Permission do |permission|
check_model(permission)
Access::Core.can?(user, :owner, permission.project)
end

# available to any user, including guest
can [:index, :filter, :new], Permission
end

def to_region(user, _is_guest)
Expand Down
68 changes: 57 additions & 11 deletions app/models/permission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,10 @@ class Permission < ApplicationRecord
}

# association validations
validates_associated :project
validates_associated :creator
validates_associated :user
# these validations are now redundant i think
#validates_associated :project
#validates_associated :creator
#validates_associated :user

# attribute validations
validates :level, presence: true
Expand Down Expand Up @@ -96,8 +97,18 @@ def self.project_list(project_id)
def self.filter_settings
{
valid_fields: [:id, :project_id, :user_id, :level, :allow_anonymous, :allow_logged_in, :creator_id, :created_at],
render_fields: [:id, :project_id, :user_id, :level, :allow_anonymous, :allow_logged_in],
render_fields: [:id, :project_id, :user_id, :level, :allow_anonymous, :allow_logged_in, :updated_at, :updater_id,
:created_at, :creator_id],
text_fields: [:level],
new_spec_fields: lambda { |_user|
{
project_id: nil,
user_id: nil,
level: nil,
allow_anonymous: false,
allow_logged_in: false
}
},
controller: :permissions,
action: :filter,
defaults: {
Expand All @@ -114,26 +125,60 @@ def self.filter_settings
}
end

def self.schema
{
type: 'object',
additionalProperties: false,
properties: {
id: Api::Schema.id,
**Api::Schema.updater_and_creator_user_stamps,
project_id: Api::Schema.id(read_only: false),
level: Api::Schema.permission_levels,
user_id: Api::Schema.id(nullable: true, read_only: false),
allow_logged_in: { type: 'boolean' },
allow_anonymous: { type: 'boolean' }
},
required: [
:id,
:project_id,
:creator_id,
:created_at,
:updater_id,
:updated_at,
:level,
:user_id,
:allow_anonymous,
:allow_logged_in
]
}.freeze
end

private

# must have only one set
def exclusive_attributes
has_user = user.blank? ? 0 : 1
allows_logged_in = allow_logged_in === true ? 1 : 0
allows_anon = allow_anonymous === true ? 1 : 0
allows_logged_in = allow_logged_in == true ? 1 : 0
allows_anon = allow_anonymous == true ? 1 : 0
exclusive_set = has_user + allows_logged_in + allows_anon

if exclusive_set != 1
error_msg = 'is not exclusive: '
error_msg += 'user is set, ' if has_user == 1
error_msg += 'logged in users is true, ' if allows_logged_in == 1
error_msg += 'anonymous users is true, ' if allows_anon == 1
error_msg += 'nothing was set' if exclusive_set < 1

errors.add(:user_id, error_msg)
errors.add(:user, error_msg)
errors.add(:allow_logged_in, error_msg)
errors.add(:allow_anonymous, error_msg)
if exclusive_set < 1
error_msg = 'nothing was set, at least one is required'
errors.add(:user_id, error_msg)
errors.add(:allow_logged_in, error_msg)
errors.add(:allow_anonymous, error_msg)
return
end

errors.add(:user_id, error_msg) if has_user == 1
errors.add(:allow_logged_in, error_msg) if allow_logged_in
errors.add(:allow_anonymous, error_msg) if allow_anonymous
end
end

Expand All @@ -142,6 +187,7 @@ def additional_levels
errors.add(:level, "must be reader for anonymous user, but was '#{level}'")
errors.add(:allow_anonymous, "level must be reader, but was '#{level}'")
end

if allow_logged_in && !['reader', 'writer'].include?(level.to_s)
errors.add(:level, "must be reader or writer for logged in user, but was '#{level}'")
errors.add(:allow_logged_in, "level must be reader or writer, but was '#{level}'")
Expand Down
5 changes: 2 additions & 3 deletions app/models/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ def self.filter_settings
{
name: nil,
description: nil,
allow_original_download: false,
allow_original_download: nil,
notes: nil
}
},
Expand Down Expand Up @@ -218,8 +218,7 @@ def self.schema

def self.access_level(project, user)
levels = Access::Core.user_levels(user, project)
level = Access::Core.highest(levels)
level.blank? ? nil : Access::Core.get_level_name(level)
Access::Core.highest(levels)
end

private
Expand Down
34 changes: 22 additions & 12 deletions app/modules/access/by_permission.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,21 @@ def projects(user, levels: Access::Core.levels)
end

# Get permissions for this project.
# @param [Project] project
# @return [ActiveRecord::Relation] permissions
def permissions(project)
project = Access::Validate.project(project)
Permission.where(project_id: project.id)
# @param [User] user
# @param [Symbol, Array<Symbol>] levels - defaults to checking owner only
# @param [Integer] project_id
# @return [::ActiveRecord::Relation] permissions
def permissions(user, levels: [:owner], project_id: nil)
query = Permission.all
query = query.where(project_id:)
is_admin, query = permission_admin(user, levels, query)

if is_admin
query
else
permissions = permission_projects(user, levels)
query.joins(:project).where(permissions)
end
end

# Get all regions for which this user has these access levels.
Expand All @@ -49,7 +59,7 @@ def permissions(project)
def regions(user, levels: Access::Core.levels, project_id: nil)
# project can be nil
query = Region.all
query = query.where(project_id: project_id) unless project_id.nil?
query = query.where(project_id:) unless project_id.nil?
is_admin, query = permission_admin(user, levels, query)

if is_admin
Expand All @@ -68,7 +78,7 @@ def regions(user, levels: Access::Core.levels, project_id: nil)
def sites(user, levels: Access::Core.levels, project_ids: nil)
# project can be nil
query = Site.all
permission_sites(user, levels, query, project_ids: project_ids)
permission_sites(user, levels, query, project_ids:)
end

# Get all audio recordings for which this user has these access levels.
Expand All @@ -92,7 +102,7 @@ def audio_events(user, levels: Access::Core.levels, audio_recording: nil)
if audio_recording
query = query.where(audio_recording_id: audio_recording.id)
project_ids = audio_recording.site.projects.pluck(:id)
permission_sites(user, levels, query, project_ids: project_ids)
permission_sites(user, levels, query, project_ids:)
else
permission_sites(user, levels, query)
end
Expand All @@ -110,7 +120,7 @@ def audio_events_tags(user, levels: Access::Core.levels, audio_event: nil)
if audio_event
query = query.where(audio_event_id: audio_event.id)
project_ids = audio_event.audio_recording.site.projects.pluck(:id)
permission_sites(user, levels, query, project_ids: project_ids)
permission_sites(user, levels, query, project_ids:)
else
permission_sites(user, levels, query)
end
Expand All @@ -128,7 +138,7 @@ def audio_event_comments(user, levels: Access::Core.levels, audio_event: nil)
if audio_event
query = query.where(audio_event_id: audio_event.id)
project_ids = audio_event.audio_recording.site.projects.pluck(:id)
permission_sites(user, levels, query, project_ids: project_ids)
permission_sites(user, levels, query, project_ids:)
else
permission_sites(user, levels, query)
end
Expand Down Expand Up @@ -227,7 +237,7 @@ def responses(user, levels: Access::Core.levels, study_id: nil)

is_admin, query = permission_admin(user, levels, query)

query = query.where(study_id: study_id) if study_id
query = query.where(study_id:) if study_id

query = query.where(creator_id: user.id) unless is_admin

Expand Down Expand Up @@ -355,7 +365,7 @@ def permission_sites(user, levels, query, project_ids: nil, _or_conditions: nil)
# )
# include reference audio_events when query is for audio_events or audio_event_comments
model_name = query.model.model_name.name
check_reference_audio_events = model_name == 'AudioEvent' || model_name == 'AudioEventComment'
check_reference_audio_events = ['AudioEvent', 'AudioEventComment'].include?(model_name)

if check_reference_audio_events
ae_refs = Arel::Table.new(:audio_events)
Expand Down
Loading