Skip to content

Check Oauth user token against Hydra #21

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

Merged
merged 4 commits into from
Mar 1, 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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ ALLOWED_ORIGINS=localhost:3002,localhost:3000
POSTGRES_HOST=changeme
POSTGRES_USER=changeme
POSTGRES_PASSWORD=changeme

HYDRA_ADMIN_URL=http://docker.for.mac.localhost:9001
HYDRA_SECRET=
2 changes: 2 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '3.0.3'

gem 'bootsnap', require: false
gem 'faraday'
gem 'importmap-rails'
gem 'jbuilder'
gem 'pg', '~> 1.1'
Expand All @@ -32,4 +33,5 @@ end

group :test do
gem 'shoulda-matchers', '~> 5.0'
gem 'webmock'
end
17 changes: 17 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,17 @@ GEM
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
ast (2.4.2)
bootsnap (1.9.3)
msgpack (~> 1.0)
builder (3.2.4)
byebug (11.1.3)
coderay (1.1.3)
concurrent-ruby (1.1.9)
crack (0.4.5)
rexml
crass (1.0.6)
diff-lcs (1.4.4)
docile (1.4.0)
Expand All @@ -82,8 +86,13 @@ GEM
railties (>= 5.0.0)
faker (2.19.0)
i18n (>= 1.6, < 2)
faraday (2.2.0)
faraday-net_http (~> 2.0)
ruby2_keywords (>= 0.0.4)
faraday-net_http (2.0.1)
globalid (1.0.0)
activesupport (>= 5.0)
hashdiff (1.0.1)
i18n (1.8.11)
concurrent-ruby (~> 1.0)
importmap-rails (1.0.1)
Expand Down Expand Up @@ -120,6 +129,7 @@ GEM
pry-byebug (3.9.0)
byebug (~> 11.0)
pry (~> 0.13.0)
public_suffix (4.0.6)
puma (5.5.2)
nio4r (~> 2.0)
racc (1.6.0)
Expand Down Expand Up @@ -199,6 +209,7 @@ GEM
rubocop-rspec (2.8.0)
rubocop (~> 1.19)
ruby-progressbar (1.11.0)
ruby2_keywords (0.0.5)
shoulda-matchers (5.0.0)
activesupport (>= 5.2.0)
simplecov (0.21.2)
Expand All @@ -223,6 +234,10 @@ GEM
tzinfo (2.0.4)
concurrent-ruby (~> 1.0)
unicode-display_width (2.1.0)
webmock (3.14.0)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
websocket-driver (0.7.5)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
Expand All @@ -238,6 +253,7 @@ DEPENDENCIES
dotenv-rails
factory_bot_rails
faker
faraday
importmap-rails
jbuilder
pg (~> 1.1)
Expand All @@ -256,6 +272,7 @@ DEPENDENCIES
sprockets-rails
stimulus-rails
turbo-rails
webmock

RUBY VERSION
ruby 3.0.3p157
Expand Down
14 changes: 7 additions & 7 deletions app/concepts/project/operation/create_remix.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,29 @@ module Operation
class CreateRemix
require 'operation_response'

def self.call(params)
def self.call(params, user_id)
response = OperationResponse.new

validate_params(response, params)
validate_params(response, params, user_id)
return response if response.failure?

remix_project(response, params)
remix_project(response, params, user_id)
response
end

class << self
private

def validate_params(response, params)
valid = params[:phrase_id].present? && params[:remix][:user_id].present?
def validate_params(response, params, user_id)
valid = params[:phrase_id].present? && user_id.present?
response[:error] = 'Invalid parameters' unless valid
end

def remix_project(response, params)
def remix_project(response, params, user_id)
original_project = Project.find_by!(identifier: params[:phrase_id])

response[:project] = original_project.dup.tap do |proj|
proj.user_id = params[:remix][:user_id]
proj.user_id = user_id
proj.components = original_project.components.map(&:dup)
end

Expand Down
6 changes: 4 additions & 2 deletions app/controllers/api/projects/remixes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
module Api
module Projects
class RemixesController < ApiController
before_action :require_oauth_user

def create
result = Project::Operation::CreateRemix.call(remix_params)
result = Project::Operation::CreateRemix.call(remix_params, oauth_user_id)

if result.success?
@project = result[:project]
Expand All @@ -17,7 +19,7 @@ def create
private

def remix_params
params.permit(:phrase_id, remix: [:user_id])
params.permit(:phrase_id)
end
end
end
Expand Down
15 changes: 15 additions & 0 deletions app/controllers/api_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
# frozen_string_literal: true

class ApiController < ActionController::API
include OauthUser

unless Rails.application.config.consider_all_requests_local
rescue_from ActiveRecord::RecordNotFound, with: -> { return404 }
end

private

def require_oauth_user
head :unauthorized unless oauth_user_id
end

def return404
render json: { error: '404 Not found' }, status: :not_found
end
end
27 changes: 27 additions & 0 deletions app/helpers/oauth_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module OauthUser
def oauth_user_id
@oauth_user_id ||= fetch_oauth_user_id
end

private

def fetch_oauth_user_id
return nil if request.headers['Authorization'].blank?

json = hydra_request
json['sub']
end

def hydra_request
con = Faraday.new ENV.fetch('HYDRA_ADMIN_URL')
res = con.post do |req|
req.url '/oauth2/introspect'
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
req.headers['Authorization'] = "Basic #{ENV.fetch('HYDRA_SECRET')}"
req.body = { token: request.headers['Authorization'] }
end
JSON.parse(res.body)
end
end
2 changes: 1 addition & 1 deletion config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
}

# Show full error reports and disable caching.
config.consider_all_requests_local = true
config.consider_all_requests_local = false
config.action_controller.perform_caching = false
config.cache_store = :null_store

Expand Down
7 changes: 4 additions & 3 deletions spec/concepts/project/operation/create_remix_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
require 'rails_helper'

RSpec.describe Project::Operation::CreateRemix, type: :unit do
subject(:create_remix) { described_class.call(params) }
subject(:create_remix) { described_class.call(params, user_id) }

let(:user_id) { 'e0675b6c-dc48-4cd6-8c04-0f7ac05af51a' }
let!(:original_project) { create(:project, :with_components) }
Expand All @@ -14,7 +14,7 @@

describe '.call' do
context 'when all params valid' do
let(:params) { { phrase_id: original_project.identifier, remix: { user_id: user_id } } }
let(:params) { { phrase_id: original_project.identifier } }

it 'returns success' do
result = create_remix
Expand Down Expand Up @@ -53,7 +53,8 @@
end

context 'when user_id is not present' do
let(:params) { { phrase_id: original_project.identifier, remix: { user_id: '' } } }
let(:user_id) { nil }
let(:params) { { phrase_id: original_project.identifier } }

it 'returns failure' do
result = create_remix
Expand Down
5 changes: 4 additions & 1 deletion spec/rails_helper.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
# frozen_string_literal: true

# This file is copied to spec/ when you run 'rails generate rspec:install'
require 'simplecov'
SimpleCov.start 'rails' do
enable_coverage :branch
Expand All @@ -9,10 +8,13 @@
require 'spec_helper'
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../config/environment', __dir__)

# Prevent database truncation if the environment is production
abort('The Rails environment is running in production mode!') if Rails.env.production?
require 'rspec/rails'

# Add additional requires below this line. Rails is not loaded until this point!
require 'webmock/rspec'

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

Expand Down Expand Up @@ -71,6 +73,7 @@
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
config.include PhraseIdentifierMock
config.include OauthUserMock
end

Shoulda::Matchers.configure do |config|
Expand Down
28 changes: 19 additions & 9 deletions spec/request/projects/remix_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,29 @@
mock_phrase_generation
end

it 'returns expected response' do
post "/api/projects/phrases/#{original_project.identifier}/remix",
params: { remix: { user_id: user_id } }
context 'when auth is correct' do
before do
mock_oauth_user
end

it 'returns success response' do
post "/api/projects/phrases/#{original_project.identifier}/remix"

expect(response.status).to eq(200)
end

it 'returns 404 response if invalid project' do
post '/api/projects/phrases/no-such-project/remix'

expect(response.status).to eq(200)
expect(response.status).to eq(404)
end
end

context 'when request is invalid' do
it 'returns error response' do
post "/api/projects/phrases/#{original_project.identifier}/remix",
params: { remix: { user_id: '' } }
context 'when auth is invalid' do
it 'returns unauthorized' do
post "/api/projects/phrases/#{original_project.identifier}/remix"

expect(response.status).to eq(400)
expect(response.status).to eq(401)
end
end
end
Expand Down
11 changes: 11 additions & 0 deletions spec/support/oauth_user_mock.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# frozen_string_literal: true

module OauthUserMock
def mock_oauth_user(user_id = nil)
user_id ||= SecureRandom.uuid

# rubocop:disable RSpec/AnyInstance
allow_any_instance_of(OauthUser).to receive(:oauth_user_id).and_return(user_id)
# rubocop:enable RSpec/AnyInstance
end
end