diff --git a/spec/std/http/spec_helper.cr b/spec/std/http/spec_helper.cr new file mode 100644 index 000000000000..db1b3d8eb5f7 --- /dev/null +++ b/spec/std/http/spec_helper.cr @@ -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 diff --git a/spec/std/oauth2/client_spec.cr b/spec/std/oauth2/client_spec.cr index 1d8be78efe7b..4a30c2b8d2a0 100644 --- a/spec/std/oauth2/client_spec.cr +++ b/spec/std/oauth2/client_spec.cr @@ -1,5 +1,6 @@ require "spec" require "oauth2" +require "../http/spec_helper" describe OAuth2::Client do describe "authorization uri" do @@ -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 diff --git a/src/oauth2/client.cr b/src/oauth2/client.cr index 0380a6eaa088..86e941d13d1d 100644 --- a/src/oauth2/client.cr +++ b/src/oauth2/client.cr @@ -105,8 +105,8 @@ 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") @@ -114,26 +114,37 @@ class OAuth2::Client 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) @@ -141,7 +152,8 @@ class OAuth2::Client 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)