Skip to content

Commit

Permalink
Add validations to exchange action in sessions controller
Browse files Browse the repository at this point in the history
  • Loading branch information
emilykim13 committed Jul 9, 2024
1 parent 8250acf commit 08b2933
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 5 deletions.
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?
24 changes: 23 additions & 1 deletion app/controllers/oauth/sessions_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,25 @@ 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 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

def unsupported_grant_type
Expand Down Expand Up @@ -79,6 +97,10 @@ def revoke_refresh_token

private

def token_params
params.permit(:grant_type, :code, :code_verifier, :resource, :subject_token, :subject_token_type, :client_id)
end

def render_token_request_error(error:, status: :bad_request)
render json: { error: }, status:
end
Expand Down
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
2 changes: 2 additions & 0 deletions spec/models/authorization_grant_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
subject(:method_call) { authorization_grant.redeem(code_verifier:) }

let(:code_verifier) { 'code_verifier' }
let(:resource) { 'resource' }
let(:subject_token_type) { 'subject_token_type' }

context 'when the authorization grant has not been redeemed' do
let_it_be(:authorization_grant) { create(:authorization_grant) }
Expand Down
62 changes: 58 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,70 @@
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)
### need to create expected endpoint tests

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

it 'responds with HTTP status bad request and error invalid_subject_token_type 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 invalid resource is provided' do
let(:resource) { 'foobar' }

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 the resource is not valid' do
let(:resource) { '' }

it 'raises an OAuth::InvalidResourceError' do
expect { raise OAuth::InvalidResourceError }.to raise_error(OAuth::InvalidResourceError)
end
end

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

it 'raises an OAuth::InvalidSubjectTokenTypeError' do
expect { raise OAuth::InvalidSubjectTokenTypeError }.to raise_error(OAuth::InvalidSubjectTokenTypeError)
end
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

0 comments on commit 08b2933

Please sign in to comment.