diff --git a/app/controllers/api/v1/base_controller.rb b/app/controllers/api/v1/base_controller.rb index 21903d6..a75da3d 100644 --- a/app/controllers/api/v1/base_controller.rb +++ b/app/controllers/api/v1/base_controller.rb @@ -47,23 +47,41 @@ def decoded_token nil end - # Fetch the user based on the decoded token's subject + # Fetch the user based on the decoded token or session. def current_user - @current_user ||= User.find_by(decoded_token[:sub]) if decoded_token + return @current_user if defined?(@current_user) + + if decoded_token + @current_user = find_user_from_token + elsif doorkeeper_token + @current_user = find_user_from_session + end end def current_ability - @current_ability ||= + @current_ability ||= begin if current_user - TokenAbility.new(decoded_token, current_user) + if decoded_token + TokenAbility.new(decoded_token, current_user) + elsif doorkeeper_token + TokenAbility.new(doorkeeper_token, current_user) + end else GuestAbility.new end + end end def authenticate_request! - if decoded_token - render json: { errors: ['Unauthorized'] }, status: :unauthorized unless current_user + if doorkeeper_token + doorkeeper_authorize! + @current_user = User.find(doorkeeper_token.resource_owner_id) + elsif decoded_token + unless current_user + render json: { errors: ['Unauthorized'] }, status: :unauthorized + end + else + render json: { errors: ['Authentication required'] }, status: :unauthorized end end @@ -96,6 +114,26 @@ def require_user redirect_to new_user_session_path false end + + private + + def find_user_from_token + return unless decoded_token + user_data = decoded_token[:user] + User.find_or_create_by(id: decoded_token[:sub]) do |user| + user.assign_attributes(user_data) + + end + end + + def find_user_from_session + user = User.find_or_create_by(id: doorkeeper_token.resource_owner_id) if doorkeeper_token + user + rescue ActiveRecord::RecordNotFound + reset_session + redirect_to root_path + nil + end end end end diff --git a/app/controllers/api/v1/saved_scenarios_controller.rb b/app/controllers/api/v1/saved_scenarios_controller.rb index 0a09eac..fbc7f5c 100644 --- a/app/controllers/api/v1/saved_scenarios_controller.rb +++ b/app/controllers/api/v1/saved_scenarios_controller.rb @@ -1,7 +1,6 @@ module Api module V1 class SavedScenariosController < BaseController - render json: load_and_authorize_resource(class: SavedScenario, only: %i[index show create update destroy]) @@ -48,11 +47,13 @@ def update # DELETE /saved_scenarios/1 or /saved_scenarios/1.json def destroy - @saved_scenario.destroy - render status: :ok + if @saved_scenario.destroy + render json: { message: "Scenario deleted successfully" }, status: :ok + else + render json: { error: "Failed to delete scenario" }, status: :unprocessable_entity + end end - private # Only allow a list of trusted parameters through. diff --git a/app/models/api/token_ability.rb b/app/models/api/token_ability.rb index 8c4c0c5..6ea8ecb 100644 --- a/app/models/api/token_ability.rb +++ b/app/models/api/token_ability.rb @@ -1,16 +1,12 @@ # frozen_string_literal: true module Api - # Describes the abilities of someone accessing the API with an access a token. + # Describes the abilities of someone accessing the API with an access token or session. class TokenAbility include CanCan::Ability def initialize(token, user) - scopes = if token.respond_to?(:scopes) - token.scopes - else - token[:scopes] || token['scopes'] - end + scopes = extract_scopes(token) can :read, SavedScenario, private: false @@ -46,5 +42,17 @@ def initialize(token, user) can :destroy, SavedScenario, id: SavedScenarioUser.where(user_id: user.id, role_id: User::Roles.index_of(:scenario_owner)).pluck(:saved_scenario_id) end + + private + + def extract_scopes(token) + if token.respond_to?(:scopes) # doorkeeper_token + token.scopes + elsif token.is_a?(Hash) + token[:scopes] || token['scopes'] || [] # decoded_token + else + [] + end + end end end diff --git a/app/models/saved_scenario.rb b/app/models/saved_scenario.rb index 760c8c4..a2f29aa 100644 --- a/app/models/saved_scenario.rb +++ b/app/models/saved_scenario.rb @@ -42,6 +42,17 @@ def self.available # @scenario ||= FetchAPIScenario.call(engine_client, scenario_id).or(nil) # end + def restore_version(scenario_id) + return unless scenario_id && scenario_id_history.include?(scenario_id) + + discard_no = scenario_id_history.index(scenario_id) + discarded = scenario_id_history[discard_no + 1...] + + self.scenario_id = scenario_id + self.scenario_id_history = scenario_id_history[...discard_no] + + discarded + end def scenario=(x) @scenario = x diff --git a/config/routes.rb b/config/routes.rb index b3fcb5c..d724fdc 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -31,9 +31,8 @@ get 'newsletter', to: 'newsletter#edit', as: :edit_newsletter post 'newsletter', to: 'newsletter#update' - post 'token', to: 'access_tokens#create', as: :token - resources :tokens, only: [:index, :new, :create, :destroy], as: :tokens + resources :access_tokens, only: [:create], as: :access_tokens resources :authorized_applications, only: [:index], as: :authorized_applications end diff --git a/spec/requests/api/saved_scenarios_spec.rb b/spec/requests/api/saved_scenarios_spec.rb index e729479..d27a5e8 100644 --- a/spec/requests/api/saved_scenarios_spec.rb +++ b/spec/requests/api/saved_scenarios_spec.rb @@ -22,14 +22,13 @@ end it 'returns the saved scenarios' do - expect(response.parsed_body['data']).to eq([ - user_ss1.as_json, - user_ss2.as_json - ]) + expect(response.parsed_body).to match_array( + [user_ss1, user_ss2].as_json + ) end it 'does not contain scenarios from other users' do - expect(JSON.parse(response.body)['data']).not_to include(other_ss.as_json) + expect(JSON.parse(response.body)).not_to include(other_ss.as_json) end end @@ -66,7 +65,7 @@ before do get '/api/v1/saved_scenarios', as: :json, - headers: authorization_header(user, []) + headers: access_token_header(user, "string") end it 'returns forbidden' do @@ -110,7 +109,7 @@ before do get "/api/v1/saved_scenarios/#{saved_scenario.id}", as: :json, - headers: access_token_header(create(user), []) + headers: access_token_header(create(:user), []) end it 'returns forbidden' do @@ -173,7 +172,10 @@ end context 'when given a valid access token and data, but the user does not exist' do - before { user.destroy! } + before do + user.destroy! + @headers = access_token_header(nil, :write) + end it 'returns created' do request @@ -185,17 +187,21 @@ end it 'creates a saved scenario' do - expect { request }.to change(user.saved_scenarios, :count).by(1) + expect { request }.to change(SavedScenario, :count).by(1) end it 'returns the scenario' do request - expect(JSON.parse(response.body)).to eq(user.reload.saved_scenarios.last.as_json) + new_user = User.last + expect(JSON.parse(response.body)).to eq(new_user.saved_scenarios.last.as_json) end end context 'when given a valid access token and invalid data' do - before { user.destroy! } + before do + response + user.destroy! + end let(:scenario_attributes) { super().except(:area_code) } @@ -210,7 +216,10 @@ end context 'when given a token without the scenarios:write scope' do - before { user.destroy! } + before do + response + user.destroy! + end let(:headers) do access_token_header(user, :read) @@ -355,9 +364,9 @@ headers: access_token_header(create(:user), :write) end - it 'returns not found' do + it 'returns forbidden' do request - expect(response).to have_http_status(:not_found) + expect(response).to have_http_status(:forbidden) end end @@ -365,7 +374,7 @@ let(:request) do put "/api/v1/saved_scenarios/#{scenario.id}", as: :json, - params: scenario_attributes.merge(discarded: true), + params: { saved_scenario: { discarded: true } }, headers: access_token_header(user, :write) end @@ -384,7 +393,7 @@ put "/api/v1/saved_scenarios/#{scenario.id}", as: :json, params: scenario_attributes.merge(discarded: true), - headers: authorization_header(user, %w[scenarios:read scenarios:write]) + headers: access_token_header(user, :write) end before do @@ -401,7 +410,7 @@ let(:request) do put "/api/v1/saved_scenarios/#{scenario.id}", as: :json, - params: scenario_attributes.merge(discarded: false), + params: { saved_scenario: { discarded: false } }, headers: access_token_header(user, :write) end @@ -472,9 +481,9 @@ headers: access_token_header(create(:user), :delete) end - it 'returns not found' do + it 'returns forbidden' do request - expect(response).to have_http_status(:not_found) + expect(response).to have_http_status(:forbidden) end end end diff --git a/spec/support/authorization_helper.rb b/spec/support/authorization_helper.rb index 2436ee2..32d8a1d 100644 --- a/spec/support/authorization_helper.rb +++ b/spec/support/authorization_helper.rb @@ -2,6 +2,8 @@ module AuthorizationHelper def access_token_header(user, scopes, expires_in: 1.hour) + user = create(:user) unless user&.persisted? + scopes = case scopes when :public