Skip to content

Commit

Permalink
Configured MyETM to handle both Ideintity session and JWT tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
louispt1 committed Nov 25, 2024
1 parent a0c35b6 commit be19c69
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 37 deletions.
50 changes: 44 additions & 6 deletions app/controllers/api/v1/base_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
9 changes: 5 additions & 4 deletions app/controllers/api/v1/saved_scenarios_controller.rb
Original file line number Diff line number Diff line change
@@ -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])

Expand Down Expand Up @@ -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.
Expand Down
20 changes: 14 additions & 6 deletions app/models/api/token_ability.rb
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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
11 changes: 11 additions & 0 deletions app/models/saved_scenario.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 1 addition & 2 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
47 changes: 28 additions & 19 deletions spec/requests/api/saved_scenarios_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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) }

Expand All @@ -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)
Expand Down Expand Up @@ -355,17 +364,17 @@
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

context 'when discarding a scenario' do
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

Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions spec/support/authorization_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit be19c69

Please sign in to comment.