From c4ae38a432947e6567ee8274ba44d51858c045c5 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 31 Jan 2023 00:28:58 -0800 Subject: [PATCH 1/7] Add edit key to Parties table --- db/migrate/20230131082521_add_edit_key_to_parties.rb | 5 +++++ db/schema.rb | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20230131082521_add_edit_key_to_parties.rb diff --git a/db/migrate/20230131082521_add_edit_key_to_parties.rb b/db/migrate/20230131082521_add_edit_key_to_parties.rb new file mode 100644 index 0000000..02cfe66 --- /dev/null +++ b/db/migrate/20230131082521_add_edit_key_to_parties.rb @@ -0,0 +1,5 @@ +class AddEditKeyToParties < ActiveRecord::Migration[7.0] + def change + add_column :parties, :edit_key, :string, unique: true, null: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 258a826..1df67f8 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,13 +10,12 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_01_30_114432) do +ActiveRecord::Schema[7.0].define(version: 2023_01_31_082521) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "pg_trgm" enable_extension "pgcrypto" enable_extension "plpgsql" - enable_extension "timescaledb" create_table "app_updates", primary_key: "updated_at", id: :datetime, force: :cascade do |t| t.string "update_type", null: false @@ -226,6 +225,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_30_114432) do t.uuid "accessory_id" t.integer "characters_count" t.integer "summons_count" + t.string "edit_key" t.index ["accessory_id"], name: "index_parties_on_accessory_id" t.index ["job_id"], name: "index_parties_on_job_id" t.index ["skill0_id"], name: "index_parties_on_skill0_id" From bedd72a7532a082bc4865e2e2d2d97af5d1c163b Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 31 Jan 2023 00:29:29 -0800 Subject: [PATCH 2/7] Add edit_key to Parties output on create We create a new view `created` that includes the `full` view but adds the `edit_key` param --- app/blueprints/api/v1/party_blueprint.rb | 5 +++++ app/controllers/api/v1/parties_controller.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/app/blueprints/api/v1/party_blueprint.rb b/app/blueprints/api/v1/party_blueprint.rb index 7424320..6235428 100644 --- a/app/blueprints/api/v1/party_blueprint.rb +++ b/app/blueprints/api/v1/party_blueprint.rb @@ -86,6 +86,11 @@ module Api include_view :preview end + view :created do + include_view :full + fields :edit_key + end + view :destroyed do fields :name, :description, :created_at, :updated_at end diff --git a/app/controllers/api/v1/parties_controller.rb b/app/controllers/api/v1/parties_controller.rb index 76c456f..4de7a7b 100644 --- a/app/controllers/api/v1/parties_controller.rb +++ b/app/controllers/api/v1/parties_controller.rb @@ -26,7 +26,7 @@ module Api # end if party.save! - return render json: PartyBlueprint.render(party, view: :full, root: :party), + return render json: PartyBlueprint.render(party, view: :created, root: :party), status: :created end From cce33ebf082cd7305bfdf644fd7673689e32fdaf Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 31 Jan 2023 00:54:17 -0800 Subject: [PATCH 3/7] Add local_id to Party object We can't save in the user_id column because it is an association, but that's okay --- app/blueprints/api/v1/party_blueprint.rb | 5 +++-- app/controllers/api/v1/parties_controller.rb | 1 + db/migrate/20230131084343_add_local_id_to_parties.rb | 5 +++++ db/schema.rb | 3 ++- 4 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20230131084343_add_local_id_to_parties.rb diff --git a/app/blueprints/api/v1/party_blueprint.rb b/app/blueprints/api/v1/party_blueprint.rb index 6235428..61bb005 100644 --- a/app/blueprints/api/v1/party_blueprint.rb +++ b/app/blueprints/api/v1/party_blueprint.rb @@ -68,9 +68,10 @@ module Api include_view :characters include_view :job_skills + fields :local_id, :description, :charge_attack, :button_count, :turn_count, :chain_count + association :accessory, blueprint: JobAccessoryBlueprint - fields :description, :charge_attack, :button_count, :turn_count, :chain_count association :source_party, blueprint: PartyBlueprint, @@ -90,7 +91,7 @@ module Api include_view :full fields :edit_key end - + view :destroyed do fields :name, :description, :created_at, :updated_at end diff --git a/app/controllers/api/v1/parties_controller.rb b/app/controllers/api/v1/parties_controller.rb index 4de7a7b..35b45da 100644 --- a/app/controllers/api/v1/parties_controller.rb +++ b/app/controllers/api/v1/parties_controller.rb @@ -173,6 +173,7 @@ module Api params.require(:party).permit( :user_id, + :local_id, :extra, :name, :description, diff --git a/db/migrate/20230131084343_add_local_id_to_parties.rb b/db/migrate/20230131084343_add_local_id_to_parties.rb new file mode 100644 index 0000000..ef363ab --- /dev/null +++ b/db/migrate/20230131084343_add_local_id_to_parties.rb @@ -0,0 +1,5 @@ +class AddLocalIdToParties < ActiveRecord::Migration[7.0] + def change + add_column :parties, :local_id, :uuid, null: true, unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 1df67f8..96513a7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_01_31_082521) do +ActiveRecord::Schema[7.0].define(version: 2023_01_31_084343) do # These are extensions that must be enabled in order to support this database enable_extension "btree_gin" enable_extension "pg_trgm" @@ -226,6 +226,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_01_31_082521) do t.integer "characters_count" t.integer "summons_count" t.string "edit_key" + t.uuid "local_id" t.index ["accessory_id"], name: "index_parties_on_accessory_id" t.index ["job_id"], name: "index_parties_on_job_id" t.index ["skill0_id"], name: "index_parties_on_skill0_id" From e45dabf39acda0328559a34596b70d9af5c51914 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 31 Jan 2023 00:55:17 -0800 Subject: [PATCH 4/7] Save edit key for parties with no user ID --- app/models/party.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/models/party.rb b/app/models/party.rb index 6e52a5d..a988c27 100644 --- a/app/models/party.rb +++ b/app/models/party.rb @@ -62,6 +62,7 @@ class Party < ApplicationRecord has_many :favorites before_create :set_shortcode + before_save :set_edit_key ##### Amoeba configuration amoeba do @@ -100,6 +101,12 @@ class Party < ApplicationRecord self.shortcode = random_string end + def set_edit_key + if !self.user + self.edit_key = Digest::SHA1.hexdigest([Time.now, rand].join) + end + end + def random_string num_chars = 6 o = [('a'..'z'), ('A'..'Z'), (0..9)].map(&:to_a).flatten From 6b24970c549112ce1cb20a5dee8b15e8c79f0a4a Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 31 Jan 2023 02:51:01 -0800 Subject: [PATCH 5/7] Edit key should create before create, not save --- app/models/party.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/party.rb b/app/models/party.rb index a988c27..fda7ef1 100644 --- a/app/models/party.rb +++ b/app/models/party.rb @@ -62,7 +62,7 @@ class Party < ApplicationRecord has_many :favorites before_create :set_shortcode - before_save :set_edit_key + before_create :set_edit_key ##### Amoeba configuration amoeba do From 4b7b48cbd30ad1dd05e0ff6af8626a09554c5f00 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 31 Jan 2023 02:51:13 -0800 Subject: [PATCH 6/7] Create edit_key global in ApiController --- app/controllers/api/v1/api_controller.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/controllers/api/v1/api_controller.rb b/app/controllers/api/v1/api_controller.rb index 48aa741..fec9ce4 100644 --- a/app/controllers/api/v1/api_controller.rb +++ b/app/controllers/api/v1/api_controller.rb @@ -50,6 +50,12 @@ module Api @current_user end + def edit_key + @edit_key ||= request.headers['X-Edit-Key'] if request.headers['X-Edit-Key'] + + @edit_key + end + # Set the response content-type def content_type(content_type) response.headers['Content-Type'] = content_type From 7d576c34851beaf17ebc6b91bf4d52760bfa7424 Mon Sep 17 00:00:00 2001 From: Justin Edmund Date: Tue, 31 Jan 2023 02:51:38 -0800 Subject: [PATCH 7/7] Update controllers to properly authorize For some of these, they weren't authorizing at all, so this is a good safety improvement --- .../api/v1/grid_characters_controller.rb | 10 +++++--- .../api/v1/grid_summons_controller.rb | 14 ++++++++--- .../api/v1/grid_weapons_controller.rb | 23 +++++++++++++------ app/controllers/api/v1/jobs_controller.rb | 5 ++++ app/controllers/api/v1/parties_controller.rb | 9 +++++--- 5 files changed, 45 insertions(+), 16 deletions(-) diff --git a/app/controllers/api/v1/grid_characters_controller.rb b/app/controllers/api/v1/grid_characters_controller.rb index 376cc46..a3b2da9 100644 --- a/app/controllers/api/v1/grid_characters_controller.rb +++ b/app/controllers/api/v1/grid_characters_controller.rb @@ -7,7 +7,7 @@ module Api before_action :find_party, only: :create before_action :set, only: %i[update destroy] - before_action :check_authorization, only: %i[update destroy] + before_action :authorize, only: %i[create update destroy] before_action :find_incoming_character, only: :create before_action :find_current_characters, only: :create @@ -135,8 +135,12 @@ module Api render_unauthorized_response if current_user && (party.user != current_user) end - def check_authorization - render_unauthorized_response if @character.party.user != current_user + def authorize + # Create + unauthorized_create = @party && (@party.user != current_user || @party.edit_key != edit_key) + unauthorized_update = @character && @character.party && (@character.party.user != current_user || @character.party.edit_key != edit_key) + + render_unauthorized_response if unauthorized_create || unauthorized_update end # Specify whitelisted properties that can be modified. diff --git a/app/controllers/api/v1/grid_summons_controller.rb b/app/controllers/api/v1/grid_summons_controller.rb index 9d5f32e..132a016 100644 --- a/app/controllers/api/v1/grid_summons_controller.rb +++ b/app/controllers/api/v1/grid_summons_controller.rb @@ -3,12 +3,12 @@ module Api module V1 class GridSummonsController < Api::V1::ApiController - before_action :set, only: %w[update destroy] - attr_reader :party, :incoming_summon - + + before_action :set, only: %w[update destroy] before_action :find_party, only: :create before_action :find_incoming_summon, only: :create + before_action :authorize, only: %i[create update destroy] def create # Create the GridSummon with the desired parameters @@ -94,6 +94,14 @@ module Api meta: { replaced: conflict_position }) end + def authorize + # Create + unauthorized_create = @party && (@party.user != current_user || @party.edit_key != edit_key) + unauthorized_update = @summon && @summon.party && (@summon.party.user != current_user || @summon.party.edit_key != edit_key) + + render_unauthorized_response if unauthorized_create || unauthorized_update + end + def set @summon = GridSummon.where('id = ?', params[:id]).first end diff --git a/app/controllers/api/v1/grid_weapons_controller.rb b/app/controllers/api/v1/grid_weapons_controller.rb index 50f3dae..76b14f8 100644 --- a/app/controllers/api/v1/grid_weapons_controller.rb +++ b/app/controllers/api/v1/grid_weapons_controller.rb @@ -3,12 +3,12 @@ module Api module V1 class GridWeaponsController < Api::V1::ApiController - before_action :set, except: %w[create update_uncap_level] - attr_reader :party, :incoming_weapon + before_action :set, except: %w[create update_uncap_level] before_action :find_party, only: :create before_action :find_incoming_weapon, only: :create + before_action :authorize, only: %i[create update destroy] def create # Create the GridWeapon with the desired parameters @@ -121,15 +121,15 @@ module Api # Render the conflict view as a string def render_conflict_view(conflict_weapon, incoming_weapon, incoming_position) ConflictBlueprint.render(nil, view: :weapons, - conflict_weapon: conflict_weapon, - incoming_weapon: incoming_weapon, - incoming_position: incoming_position) + conflict_weapon: conflict_weapon, + incoming_weapon: incoming_weapon, + incoming_position: incoming_position) end def render_grid_weapon_view(grid_weapon, conflict_position) GridWeaponBlueprint.render(grid_weapon, view: :full, - root: :grid_weapon, - meta: { replaced: conflict_position }) + root: :grid_weapon, + meta: { replaced: conflict_position }) end def save_weapon(weapon) @@ -183,6 +183,15 @@ module Api @weapon = GridWeapon.where('id = ?', params[:id]).first end + def authorize + # Create + ap @party + unauthorized_create = @party && (@party.user != current_user || @party.edit_key != edit_key) + unauthorized_update = @weapon && @weapon.party && (@weapon.party.user != current_user || @weapon.party.edit_key != edit_key) + + render_unauthorized_response if unauthorized_create || unauthorized_update + end + # Specify whitelisted properties that can be modified. def weapon_params params.require(:weapon).permit( diff --git a/app/controllers/api/v1/jobs_controller.rb b/app/controllers/api/v1/jobs_controller.rb index 6983d27..40276f8 100644 --- a/app/controllers/api/v1/jobs_controller.rb +++ b/app/controllers/api/v1/jobs_controller.rb @@ -4,6 +4,7 @@ module Api module V1 class JobsController < Api::V1::ApiController before_action :set, only: %w[update_job update_job_skills] + before_action :authorize, only: %w[update_job update_job_skills] def all render json: JobBlueprint.render(Job.all) @@ -165,6 +166,10 @@ module Api end end + def authorize + render_unauthorized_response if @party.user != current_user || @party.edit_key != edit_key + end + def set @party = Party.where('id = ?', params[:id]).first end diff --git a/app/controllers/api/v1/parties_controller.rb b/app/controllers/api/v1/parties_controller.rb index 35b45da..6906aff 100644 --- a/app/controllers/api/v1/parties_controller.rb +++ b/app/controllers/api/v1/parties_controller.rb @@ -6,6 +6,7 @@ module Api before_action :set_from_slug, except: %w[create destroy update index favorites] before_action :set, only: %w[update destroy] + before_action :authorize, only: %w[update destroy] def create party = Party.new @@ -40,8 +41,6 @@ module Api end def update - render_unauthorized_response if @party.user != current_user - @party.attributes = party_params.except(:skill1_id, :skill2_id, :skill3_id) # TODO: Validate accessory with job @@ -52,7 +51,6 @@ module Api end def destroy - render_unauthorized_response if @party.user != current_user return render json: PartyBlueprint.render(@party, view: :destroyed, root: :checkin) if @party.destroy end @@ -123,6 +121,10 @@ module Api private + def authorize + render_unauthorized_response if @character.party.user != current_user || @party.edit_key != edit_key + end + def build_conditions(params) unless params['recency'].blank? start_time = (DateTime.current - params['recency'].to_i.seconds) @@ -174,6 +176,7 @@ module Api params.require(:party).permit( :user_id, :local_id, + :edit_key, :extra, :name, :description,