Implement roles and visibility (#128)

* Add migrations to add user roles and party visibility.

* Update schema.rb

* Add admin check in User model

* Implement rudimentary visibility of teams

* Adds checks to Party model
* Hides parties from collection views depending on visibility
* Disallows viewing private parties if you're not the owner

* Add a party's visibility to blueprint

* Add admin mode

The API Controller checks if the user is logged in and whether they are an admin, and checks for the X-Admin-Mode header

* Implement admin mode overrides

* Add admin_mode to authorize

* Note to self: Implement user editing by admins

* Fix syntax error with equality in SQL

* Fix syntax error with method name

* Fix bug in who can see restricted parties

* Add privacy control to user profiles
This commit is contained in:
Justin Edmund 2023-08-25 15:53:56 -07:00 committed by GitHub
parent c79d2717cc
commit 8381c668bc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 600 additions and 525 deletions

View file

@ -35,7 +35,7 @@ module Api
view :minimal do view :minimal do
fields :name, :element, :shortcode, :favorited, :remix, fields :name, :element, :shortcode, :favorited, :remix,
:extra, :full_auto, :clear_time, :auto_guard, :auto_summon, :extra, :full_auto, :clear_time, :auto_guard, :auto_summon,
:created_at, :updated_at :visibility, :created_at, :updated_at
field :guidebooks do |p| field :guidebooks do |p|
{ {

View file

@ -50,9 +50,17 @@ module Api
@current_user @current_user
end end
def admin_mode
if current_user && current_user.admin? && request.headers['X-Admin-Mode']
@admin_mode ||= request.headers['X-Admin-Mode'] == 'true'
end
@admin_mode
end
def edit_key def edit_key
@edit_key ||= request.headers['X-Edit-Key'] if request.headers['X-Edit-Key'] @edit_key ||= request.headers['X-Edit-Key'] if request.headers['X-Edit-Key']
@edit_key @edit_key
end end
@ -96,9 +104,9 @@ module Api
def render_not_found_response(object) def render_not_found_response(object)
render json: ErrorBlueprint.render(nil, error: { render json: ErrorBlueprint.render(nil, error: {
message: "#{object.capitalize} could not be found", message: "#{object.capitalize} could not be found",
code: 'not_found' code: 'not_found'
}), status: :not_found }), status: :not_found
end end
def render_unauthorized_response def render_unauthorized_response

View file

@ -32,6 +32,11 @@ module Api
end end
def show def show
# If a party is private, check that the user is the owner or an admin
if (@party.private? && !current_user) || (@party.private? && not_owner && !admin_mode)
return render_unauthorized_response
end
return render json: PartyBlueprint.render(@party, view: :full, root: :party) if @party return render json: PartyBlueprint.render(@party, view: :full, root: :party) if @party
render_not_found_response('project') render_not_found_response('project')
@ -90,7 +95,7 @@ module Api
conditions = build_filters conditions = build_filters
conditions[:favorites] = { user_id: current_user.id } conditions[:favorites] = { user_id: current_user.id }
query = build_query(conditions, true) query = build_query(conditions, favorites: true)
query = apply_includes(query, params[:includes]) if params[:includes].present? query = apply_includes(query, params[:includes]) if params[:includes].present?
query = apply_excludes(query, params[:excludes]) if params[:excludes].present? query = apply_excludes(query, params[:excludes]) if params[:excludes].present?
@ -104,7 +109,11 @@ module Api
private private
def authorize def authorize
render_unauthorized_response if @party.user != current_user || @party.edit_key != edit_key render_unauthorized_response if (not_owner && !admin_mode) || (@party.edit_key != edit_key && !admin_mode)
end
def not_owner
current_user && @party.user != current_user
end end
def build_filters def build_filters
@ -152,11 +161,12 @@ module Api
value.to_i unless value.blank? || value.to_i == -1 value.to_i unless value.blank? || value.to_i == -1
end end
def build_query(conditions, favorites = false) def build_query(conditions, favorites: false)
query = Party.distinct query = Party.distinct
.joins(weapons: [:object], summons: [:object], characters: [:object]) .joins(weapons: [:object], summons: [:object], characters: [:object])
.group('parties.id') .group('parties.id')
.where(conditions) .where(conditions)
.where(privacy(favorites: favorites))
.where(name_quality) .where(name_quality)
.where(user_quality) .where(user_quality)
.where(original) .where(original)
@ -242,6 +252,16 @@ module Api
}) })
end end
def privacy(favorites: false)
return if admin_mode
if favorites
'visibility < 3'
else
'visibility = 1'
end
end
def user_quality def user_quality
'user_id IS NOT NULL' unless request.params[:user_quality].blank? || request.params[:user_quality] == 'false' 'user_id IS NOT NULL' unless request.params[:user_quality].blank? || request.params[:user_quality] == 'false'
end end
@ -329,6 +349,7 @@ module Api
:description, :description,
:raid_id, :raid_id,
:job_id, :job_id,
:visibility,
:accessory_id, :accessory_id,
:skill0_id, :skill0_id,
:skill1_id, :skill1_id,

View file

@ -41,6 +41,8 @@ module Api
render_validation_error_response(@user) render_validation_error_response(@user)
end end
# TODO: Allow admins to update other users
def update def update
render json: UserBlueprint.render(@user, view: :minimal) if @user.update(user_params) render json: UserBlueprint.render(@user, view: :minimal) if @user.update(user_params)
end end
@ -57,13 +59,14 @@ module Api
conditions[:user_id] = @user.id conditions[:user_id] = @user.id
parties = Party parties = Party
.where(conditions) .where(conditions)
.where(name_quality) .where(name_quality)
.where(user_quality) .where(user_quality)
.where(original) .where(original)
.order(created_at: :desc) .where(privacy)
.paginate(page: request.params[:page], per_page: COLLECTION_PER_PAGE) .order(created_at: :desc)
.each do |party| .paginate(page: request.params[:page], per_page: COLLECTION_PER_PAGE)
.each do |party|
party.favorited = current_user ? party.is_favorited(current_user) : false party.favorited = current_user ? party.is_favorited(current_user) : false
end end
@ -98,7 +101,7 @@ module Api
unless params['recency'].blank? unless params['recency'].blank?
start_time = (DateTime.current - params['recency'].to_i.seconds) start_time = (DateTime.current - params['recency'].to_i.seconds)
.to_datetime.beginning_of_day .to_datetime.beginning_of_day
end end
min_characters_count = params['characters_count'].blank? ? DEFAULT_MIN_CHARACTERS : params['characters_count'].to_i min_characters_count = params['characters_count'].blank? ? DEFAULT_MIN_CHARACTERS : params['characters_count'].to_i
@ -113,9 +116,18 @@ module Api
hash[:created_at] = start_time..DateTime.current unless params['recency'].blank? hash[:created_at] = start_time..DateTime.current unless params['recency'].blank?
# Advanced filters: Team parameters # Advanced filters: Team parameters
hash[:full_auto] = params['full_auto'].to_i unless params['full_auto'].blank? || params['full_auto'].to_i == -1 unless params['full_auto'].blank? || params['full_auto'].to_i == -1
hash[:auto_guard] = params['auto_guard'].to_i unless params['auto_guard'].blank? || params['auto_guard'].to_i == -1 hash[:full_auto] =
hash[:charge_attack] = params['charge_attack'].to_i unless params['charge_attack'].blank? || params['charge_attack'].to_i == -1 params['full_auto'].to_i
end
unless params['auto_guard'].blank? || params['auto_guard'].to_i == -1
hash[:auto_guard] =
params['auto_guard'].to_i
end
unless params['charge_attack'].blank? || params['charge_attack'].to_i == -1
hash[:charge_attack] =
params['charge_attack'].to_i
end
# Turn count of 0 will not be displayed, so disallow on the frontend or set default to 1 # Turn count of 0 will not be displayed, so disallow on the frontend or set default to 1
# How do we do the same for button count since that can reasonably be 1? # How do we do the same for button count since that can reasonably be 1?
@ -131,38 +143,44 @@ module Api
end end
def original def original
unless params.key?('original') || params['original'].blank? || params['original'] == '0' return if params.key?('original') || params['original'].blank? || params['original'] == '0'
"source_party_id IS NULL"
end 'source_party_id IS NULL'
end end
def user_quality def user_quality
unless params.key?('user_quality') || params[:user_quality].nil? || params[:user_quality] == "0" return if params.key?('user_quality') || params[:user_quality].nil? || params[:user_quality] == '0'
"user_id IS NOT NULL"
end 'user_id IS NOT NULL'
end end
def name_quality def name_quality
low_quality = [ low_quality = [
"Untitled", 'Untitled',
"Remix of Untitled", 'Remix of Untitled',
"Remix of Remix of Untitled", 'Remix of Remix of Untitled',
"Remix of Remix of Remix of Untitled", 'Remix of Remix of Remix of Untitled',
"Remix of Remix of Remix of Remix of Untitled", 'Remix of Remix of Remix of Remix of Untitled',
"Remix of Remix of Remix of Remix of Remix of Untitled", 'Remix of Remix of Remix of Remix of Remix of Untitled',
"無題", '無題',
"無題のリミックス", '無題のリミックス',
"無題のリミックスのリミックス", '無題のリミックスのリミックス',
"無題のリミックスのリミックスのリミックス", '無題のリミックスのリミックスのリミックス',
"無題のリミックスのリミックスのリミックスのリミックス", '無題のリミックスのリミックスのリミックスのリミックス',
"無題のリミックスのリミックスのリミックスのリミックスのリミックス" '無題のリミックスのリミックスのリミックスのリミックスのリミックス'
] ]
joined_names = low_quality.map { |name| "'#{name}'" }.join(',') joined_names = low_quality.map { |name| "'#{name}'" }.join(',')
unless params.key?('name_quality') || params[:name_quality].nil? || params[:name_quality] == "0" return if params.key?('name_quality') || params[:name_quality].nil? || params[:name_quality] == '0'
"name NOT IN (#{joined_names})"
end "name NOT IN (#{joined_names})"
end
def privacy
return if admin_mode
'visibility = 1' if current_user != @user
end end
# Specify whitelisted properties that can be modified. # Specify whitelisted properties that can be modified.

View file

@ -105,17 +105,29 @@ class Party < ApplicationRecord
end end
def is_remix def is_remix
self.source_party != nil !source_party.nil?
end end
def remixes def remixes
Party.where(source_party_id: self.id) Party.where(source_party_id: id)
end end
def blueprint def blueprint
PartyBlueprint PartyBlueprint
end end
def public?
visibility == 1
end
def unlisted?
visibility == 2
end
def private?
visibility == 3
end
private private
def set_shortcode def set_shortcode
@ -123,9 +135,9 @@ class Party < ApplicationRecord
end end
def set_edit_key def set_edit_key
if !self.user return if user
self.edit_key = Digest::SHA1.hexdigest([Time.now, rand].join)
end self.edit_key = Digest::SHA1.hexdigest([Time.now, rand].join)
end end
def random_string def random_string

View file

@ -43,6 +43,10 @@ class User < ApplicationRecord
favorites.map(&:party) favorites.map(&:party)
end end
def admin?
role == 9
end
def blueprint def blueprint
UserBlueprint UserBlueprint
end end

View file

@ -0,0 +1,5 @@
class AddRoleToUsers < ActiveRecord::Migration[7.0]
def change
add_column :users, :role, :integer, default: 1, null: false
end
end

View file

@ -0,0 +1,5 @@
class AddVisibilityToParties < ActiveRecord::Migration[7.0]
def change
add_column :parties, :visibility, :integer, default: 1, null: false
end
end

File diff suppressed because it is too large Load diff