Skip to content
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
14 changes: 13 additions & 1 deletion .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,19 @@ Style/TrailingCommaInHashLiteral:
EnforcedStyleForMultiline: comma

Metrics/BlockLength:
Max: 45
Max: 60

Layout/FirstArrayElementIndentation:
EnforcedStyle: consistent

Style/TrailingCommaInArrayLiteral:
EnforcedStyleForMultiline: comma

Style/TrailingCommaInArguments:
EnforcedStyleForMultiline: comma

Style/StringConcatenation:
Enabled: false

Layout/MultilineOperationIndentation:
Enabled: false
3 changes: 1 addition & 2 deletions lib/vauth.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,5 @@
require_relative "vauth/version"

module Vauth
class Error < StandardError; end
# Your code goes here...
class StateMismatchError < StandardError; end
end
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
require "vauth/identity_token"

module Vauth
class AuthCodeGrant # :nodoc:
def initialize(client, code)
@client = client
class AuthorizationCodeGrant # :nodoc:
def initialize(request, code, state)
@request = request
@code = code

verify_state!(request.state, state)
end

def identity_token
Expand All @@ -17,7 +19,11 @@ def identity_token

private

attr_reader :client, :code
attr_reader :request, :code

def client
request.client
end

def parsed_response
@parsed_response = JSON.parse(
Expand All @@ -26,8 +32,12 @@ def parsed_response
client_secret: client.secret,
scope: "openid",
code: code,
}).body
}).body,
)
end

def verify_state!(sent, received)
raise ::Vauth::StateMismatchError, "Cannot grant access due to state mismatch!" unless sent == received
end
end
end
36 changes: 36 additions & 0 deletions lib/vauth/authorization_request.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

require "uri"

module Vauth
class AuthorizationRequest # :nodoc:
def initialize(client, state = nil)
@client = client
@state = state
end

attr_reader :client

def url(redirect_to:)
authorization_uri.query = URI.encode_www_form([
%w[response_type code],
["client_id", client.id],
["redirect_uri", redirect_to],
%w[scope openid],
["state", state],
])

String(authorization_uri)
end

def state
@state ||= SecureRandom.hex(16)
end

private

def authorization_uri
@authorization_uri ||= client.authorization_uri
end
end
end
6 changes: 5 additions & 1 deletion lib/vauth/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
require "uri"

module Vauth
Client = Struct.new(:id, :secret, :token_uri) do
Client = Struct.new(:id, :secret, :authorization_uri, :token_uri) do
def authorization_uri
URI(self[:authorization_uri])
end

def token_uri
URI(self[:token_uri])
end
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,40 @@

require "test_helper"
require "vauth/client"
require "vauth/auth_code_grant"
require "vauth/authorization_code_grant"
require "vauth/identity_token"
require "vauth/authorization_request"

describe ::Vauth::AuthCodeGrant do
subject { ::Vauth::AuthCodeGrant.new(client, code) }
describe ::Vauth::AuthorizationCodeGrant do
subject { ::Vauth::AuthorizationCodeGrant.new(request, code, state) }

let(:client) { ::Vauth::Client.new(client_id, client_secret, token_uri) }
let(:request) { ::Vauth::AuthorizationRequest.new(client) }
let(:client) { ::Vauth::Client.new(client_id, client_secret, authorization_uri, token_uri) }
let(:code) { "code-for-auth-code-grant" }
let(:state) { request.state }

let(:authorization_uri) { "https://oauth2-server.com/auth" }
let(:token_uri) { "https://oauth2-server.com/auth/token" }
let(:client_id) { "oauth2-server-given-client-id" }
let(:client_secret) { "oauth2-server-given-client-secret" }
let(:identity_token) { subject.identity_token }

describe ".new" do
it "doesn't throw an error when state is valid" do
request = ::Vauth::AuthorizationRequest.new(client)
received_state = request.state

_ { ::Vauth::AuthorizationCodeGrant.new(request, code, received_state) }.must_be_silent
end

it "raises an error when the state is mismatched" do
request = ::Vauth::AuthorizationRequest.new(client)
received_state = "abc123"

_ { ::Vauth::AuthorizationCodeGrant.new(request, code, received_state) }.must_raise ::Vauth::StateMismatchError
end
end

describe "#identity_token" do
it "returns the Identity Token with the issuer and the subject" do
response_mock = Minitest::Mock.new
Expand All @@ -32,7 +53,7 @@
client_secret: client_secret,
scope: "openid",
code: code,
}
},
])

::Net::HTTP.stub :post_form, net_http_mock do
Expand Down
60 changes: 60 additions & 0 deletions test/vauth/authorization_request_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

require "test_helper"
require "vauth/authorization_request"

describe ::Vauth::AuthorizationRequest do
subject { ::Vauth::AuthorizationRequest.new(client) }

let(:client) do
::Vauth::Client.new(
"oauth2-server-client-id",
"oauth2-server-client-secret",
"https://oauth2-server.com/auth",
"https://oauth2-server.com/token",
)
end

describe "#url" do
it "returns the URL that the Resource Owner needs to navigate to" do
expected = "https://oauth2-server.com/auth?" +
URI.encode_www_form([
["response_type", "code"],
["client_id", "oauth2-server-client-id"],
["redirect_uri", "https://example.com/session"],
["scope", "openid"],
["state", subject.state],
])

_(subject.url(redirect_to: "https://example.com/session")).must_equal(expected)
end
end

describe "#client" do
it "returns the client used" do
_(subject.client).must_be_same_as client
end
end

describe "#state" do
it "remains constant for the request" do
request = ::Vauth::AuthorizationRequest.new(client)

_(request.state).must_equal request.state
end

it "varies between different requests" do
first_request = ::Vauth::AuthorizationRequest.new(client)
second_request = ::Vauth::AuthorizationRequest.new(client)

_(second_request.state).wont_equal first_request.state
end

it "can be overriden to represent a particular earlier request" do
first_request = ::Vauth::AuthorizationRequest.new(client)
second_request = ::Vauth::AuthorizationRequest.new(client, first_request.state)

_(second_request.state).must_equal first_request.state
end
end
end
9 changes: 8 additions & 1 deletion test/vauth/client_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@
::Vauth::Client.new(
"oauth2-server-given-client-id",
"oauth2-server-given-client-secret",
"https://oauth2-server.com/token"
"https://oauth2-server.com/auth",
"https://oauth2-server.com/token",
)
end

describe "#authorization_uri" do
it "returns the passed Authorization URI" do
_(subject.authorization_uri).must_equal URI("https://oauth2-server.com/auth")
end
end

describe "#token_uri" do
it "returns the passed Token URI" do
_(subject.token_uri).must_equal URI("https://oauth2-server.com/token")
Expand Down