Skip to content
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

Add validations to exchange action in sessions controller #52

Merged
merged 1 commit into from
Jul 10, 2024
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
4 changes: 4 additions & 0 deletions .reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,7 @@ detectors:
exclude:
- AccessToken#valid_exp?
- API::BaseController#user_from_token
UtilityFunction:
exclude:
- OAuthSessionExchangeable#valid_resource?
- OAuthSessionExchangeable#valid_subject_token_type?
20 changes: 19 additions & 1 deletion app/controllers/oauth/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,14 @@ def refresh
end

def exchange
head :ok
oauth_session = oauth_session_from_subject_token
resource = params[:resource]
subject_token_type = params[:subject_token_type]
oauth_session.validate_params_for_exchange!(resource:, subject_token_type:)

render json: { resource:, subject_token_type: }
rescue OAuth::InvalidResourceError, OAuth::InvalidSubjectTokenTypeError
render_token_request_error(error: 'invalid_request')
end

def unsupported_grant_type
Expand Down Expand Up @@ -86,5 +93,16 @@ def render_token_request_error(error:, status: :bad_request)
def render_unsupported_token_type_error
render json: { error: 'unsupported_token_type' }, status: :bad_request
end

def oauth_session_from_subject_token
access_token = AccessToken.new(JsonWebToken.decode(params[:subject_token]))
raise OAuth::UnauthorizedAccessTokenError unless access_token.valid?

OAuthSession.find_by!(access_token_jti: access_token.jti)
rescue JWT::DecodeError
raise OAuth::InvalidAccessTokenError
rescue ActiveRecord::RecordNotFound
raise OAuth::OAuthSessionNotFound
end
end
end
28 changes: 28 additions & 0 deletions app/models/concerns/oauth_session_exchangeable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# frozen_string_literal: true

##
# Provides support for validating token claims
module OAuthSessionExchangeable
extend ActiveSupport::Concern

VALID_URIS = ['/api/v1/users/current'].freeze
VALID_TOKEN_TYPES = ['urn:ietf:params:oauth:token-type:access_token'].freeze

def validate_params_for_exchange!(resource:, subject_token_type:)
raise OAuth::InvalidResourceError unless valid_resource?(resource)

return if valid_subject_token_type?(subject_token_type)

raise OAuth::InvalidSubjectTokenTypeError
end

private

def valid_resource?(resource)
resource.present? && VALID_URIS.include?(resource)
end

def valid_subject_token_type?(subject_token_type)
subject_token_type.present? && VALID_TOKEN_TYPES.include?(subject_token_type)
end
end
1 change: 1 addition & 0 deletions app/models/oauth_session.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# Models an oauth session with minimal data from session.
class OAuthSession < ApplicationRecord
include OAuthSessionCreatable
include OAuthSessionExchangeable

STATUS_ENUM_VALUES = {
created: 'created',
Expand Down
12 changes: 12 additions & 0 deletions lib/oauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,22 @@ class AccessDenied < StandardError; end
# Error for when client provides an unsupported grant type param.
class UnsupportedGrantTypeError < StandardError; end

##
# Error for when OAuth Session is not found.
class OAuthSessionNotFound < StandardError; end

##
# Error for when client provides a code that does not map to an valid authorization grant.
class InvalidGrantError < StandardError; end

##
# Error for when the resource probided fails validation.
class InvalidResourceError < StandardError; end

##
# Error for when the subject token type fails validation.
class InvalidSubjectTokenTypeError < StandardError; end

##
# Error for when client provides a code verifier that fails validation.
class InvalidCodeVerifierError < StandardError; end
Expand Down
54 changes: 50 additions & 4 deletions spec/requests/oauth/sessions_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -217,16 +217,62 @@
describe 'POST /token (grant_type="urn:ietf:params:oauth:grant-type:token-exchange")' do
let_it_be(:user) { create(:user) }
let_it_be(:authorization_grant) { create(:authorization_grant, user:) }
let(:params) { { grant_type: } }
let(:params) { { grant_type:, subject_token:, resource:, subject_token_type:, client_id: } }
let!(:oauth_session) { create(:oauth_session, authorization_grant:) }
let(:subject_token) { JsonWebToken.encode(attributes_for(:access_token, oauth_session:)) }
let(:grant_type) { 'urn:ietf:params:oauth:grant-type:token-exchange' }
let(:resource) { '/api/v1/users/current' }
let(:subject_token_type) { 'urn:ietf:params:oauth:token-type:access_token' }
let(:client_id) { 'democlient' }

include_context 'with an authenticated client', :post, :oauth_create_session_path

it_behaves_like 'an endpoint that requires client authentication'

it 'creates an OAuth session and serializes the token data' do
call_endpoint
expect(response).to have_http_status(:ok)
shared_examples 'returns invalid request response' do
it 'responds with HTTP status bad request and error invalid_resource as JSON' do
call_endpoint
aggregate_failures do
expect(response).to have_http_status(:bad_request)
expect(response.parsed_body).to eq({ 'error' => 'invalid_request' })
end
end
end

context 'when an empty resource is provided' do
let(:resource) { '' }

include_examples 'returns invalid request response'
end

context 'when an invalid resource is provided' do
let(:resource) { 'foobar' }

include_examples 'returns invalid request response'
end

context 'when an empty subject token type is provided' do
let(:subject_token_type) { '' }

include_examples 'returns invalid request response'
end

context 'when the subject token type is not valid' do
let(:subject_token_type) { 'foobar' }

include_examples 'returns invalid request response'
end

context 'when valid params are provided' do
it 'returns resource and subject_token_type' do
call_endpoint
aggregate_failures do
expect(response).to have_http_status(:ok)
expect(response.parsed_body).to eq(
{ 'resource' => resource, 'subject_token_type' => subject_token_type }
)
end
end
end
end

Expand Down
Loading