Skip to content

Commit

Permalink
Implement resource owner credentials (#7424)
Browse files Browse the repository at this point in the history
* Implement resource owner credentials

* Fix link name

* get_access_token_using_*

* remove headers from response

* Address comments

* Display timeout value in exception
  • Loading branch information
Blacksmoke16 authored and asterite committed Feb 23, 2019
1 parent 25218a5 commit 2225609
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 11 deletions.
46 changes: 46 additions & 0 deletions spec/std/http/spec_helper.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
require "spec"

private def wait_for(timeout = 5.seconds)
now = Time.monotonic

until yield
Fiber.yield

if (Time.monotonic - now) > timeout
raise "server failed to start within #{timeout}"
end
end
end

# Helper method which runs *server*
# 1. Spawns `server.listen` in a new fiber.
# 2. Waits until `server.listening?`.
# 3. Yields to the given block.
# 4. Ensures the server is closed.
# 5. After returning from the block, it waits for the server to gracefully
# shut down before continuing execution in the current fiber.
# 6. If the listening fiber raises an exception, it is rescued and re-raised
# in the current fiber.
def run_server(server)
server_done = Channel(Exception?).new

spawn do
server.listen
rescue exc
server_done.send exc
else
server_done.send nil
end

begin
wait_for { server.listening? }

yield server_done
ensure
server.close unless server.closed?

if exc = server_done.receive
raise exc
end
end
end
81 changes: 81 additions & 0 deletions spec/std/oauth2/client_spec.cr
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require "spec"
require "oauth2"
require "../http/spec_helper"

describe OAuth2::Client do
describe "authorization uri" do
Expand Down Expand Up @@ -38,11 +39,91 @@ describe OAuth2::Client do
end
end

describe "get_access_token_using_*" do
it "#get_access_token_using_authorization_code" do
server = HTTP::Server.new do |context|
body = context.request.body.not_nil!.gets_to_end
response = {access_token: "access_token", body: body}
context.response.print response.to_json
end

expected = %("client_id=client_id&client_secret=client_secret&redirect_uri=&grant_type=authorization_code&code=SDFhw39fwfg23flSfpawbef")
address = server.bind_unused_port "::1"

run_server(server) do
client = OAuth2::Client.new "[::1]", "client_id", "client_secret", port: address.port, scheme: "http"

token = client.get_access_token_using_authorization_code(authorization_code: "SDFhw39fwfg23flSfpawbef")
token.extra.not_nil!["body"].should eq expected
token.access_token.should eq "access_token"
end
end

it "#get_access_token_using_resource_owner_credentials" do
server = HTTP::Server.new do |context|
body = context.request.body.not_nil!.gets_to_end
response = {access_token: "access_token", body: body}
context.response.print response.to_json
end

expected = %("client_id=client_id&client_secret=client_secret&grant_type=password&username=user123&password=monkey&scope=read_posts")
address = server.bind_unused_port "::1"

run_server(server) do
client = OAuth2::Client.new "[::1]", "client_id", "client_secret", port: address.port, scheme: "http"

token = client.get_access_token_using_resource_owner_credentials(username: "user123", password: "monkey", scope: "read_posts")
token.extra.not_nil!["body"].should eq expected
token.access_token.should eq "access_token"
end
end

it "#get_access_token_using_client_credentials" do
server = HTTP::Server.new do |context|
body = context.request.body.not_nil!.gets_to_end
response = {access_token: "access_token", body: body}
context.response.print response.to_json
end

expected = %("client_id=client_id&client_secret=client_secret&grant_type=client_credentials&scope=read_posts")
address = server.bind_unused_port "::1"

run_server(server) do
client = OAuth2::Client.new "[::1]", "client_id", "client_secret", port: address.port, scheme: "http"

token = client.get_access_token_using_client_credentials(scope: "read_posts")
token.extra.not_nil!["body"].should eq expected
token.access_token.should eq "access_token"
end
end

it "#get_access_token_using_refresh_token" do
server = HTTP::Server.new do |context|
body = context.request.body.not_nil!.gets_to_end
response = {access_token: "access_token", body: body}
context.response.print response.to_json
end

expected = %("client_id=client_id&client_secret=client_secret&grant_type=refresh_token&refresh_token=some_refresh_token&scope=read_posts")
address = server.bind_unused_port "::1"

run_server(server) do
client = OAuth2::Client.new "[::1]", "client_id", "client_secret", port: address.port, scheme: "http"

token = client.get_access_token_using_refresh_token(scope: "read_posts", refresh_token: "some_refresh_token")
token.extra.not_nil!["body"].should eq expected
token.access_token.should eq "access_token"
end
end
end

typeof(begin
client = OAuth2::Client.new "localhost", "client_id", "client_secret", redirect_uri: "uri", authorize_uri: "/baz"
client.get_access_token_using_authorization_code("some_code")
client.get_access_token_using_refresh_token("some_refresh_token")
client.get_access_token_using_refresh_token("some_refresh_token", scope: "some scope")
client.get_access_token_using_client_credentials(scope: "some scope")
client.get_access_token_using_resource_owner_credentials(username: "user123", password: "monkey")
client.get_access_token_using_resource_owner_credentials(username: "user123", password: "monkey", scope: "foo")
end)
end
34 changes: 23 additions & 11 deletions src/oauth2/client.cr
Original file line number Diff line number Diff line change
Expand Up @@ -105,43 +105,55 @@ class OAuth2::Client
end

# Gets an access token using an authorization code, as specified by
# [RFC 6749, Section 4.1.1](https://tools.ietf.org/html/rfc6749#section-4.1.3).
def get_access_token_using_authorization_code(authorization_code) : AccessToken
# [RFC 6749, Section 4.1.3](https://tools.ietf.org/html/rfc6749#section-4.1.3).
def get_access_token_using_authorization_code(authorization_code : String) : AccessToken
get_access_token do |form|
form.add("redirect_uri", @redirect_uri)
form.add("grant_type", "authorization_code")
form.add("code", authorization_code)
end
end

# Gets an access token using a refresh token, as specified by
# [RFC 6749, Section 6](https://tools.ietf.org/html/rfc6749#section-6).
def get_access_token_using_refresh_token(refresh_token, scope = nil) : AccessToken
# Gets an access token using the resource owner credentials, as specified by
# [RFC 6749, Section 4.3.2](https://tools.ietf.org/html/rfc6749#section-4.3.2).
def get_access_token_using_resource_owner_credentials(username : String, password : String, scope = nil) : AccessToken
get_access_token do |form|
form.add("grant_type", "refresh_token")
form.add("refresh_token", refresh_token)
form.add "scope", scope unless scope.nil?
form.add("grant_type", "password")
form.add("username", username)
form.add("password", password)
form.add("scope", scope) unless scope.nil?
end
end

# Gets an access token using client credentials, as specified by
# [RFC 6749, Section 4.4.2](https://tools.ietf.org/html/rfc6749#section-4.4.2).
def get_access_token_using_client_credentials(scope = nil)
def get_access_token_using_client_credentials(scope = nil) : AccessToken
get_access_token do |form|
form.add("grant_type", "client_credentials")
form.add("scope", scope) unless scope.nil?
end
end

private def get_access_token
# Gets an access token using a refresh token, as specified by
# [RFC 6749, Section 6](https://tools.ietf.org/html/rfc6749#section-6).
def get_access_token_using_refresh_token(refresh_token, scope = nil) : AccessToken
get_access_token do |form|
form.add("grant_type", "refresh_token")
form.add("refresh_token", refresh_token)
form.add "scope", scope unless scope.nil?
end
end

private def get_access_token : AccessToken
body = HTTP::Params.build do |form|
form.add("client_id", @client_id)
form.add("client_secret", @client_secret)
yield form
end

headers = HTTP::Headers{
"Accept" => "application/json",
"Accept" => "application/json",
"Content-Type" => "application/x-www-form-urlencoded",
}

response = HTTP::Client.post(token_uri, form: body, headers: headers)
Expand Down

0 comments on commit 2225609

Please sign in to comment.