Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: d3814ecf56d2
Choose a base ref
...
head repository: crystal-lang/crystal
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 89da3a794f3c
Choose a head ref
  • 3 commits
  • 5 files changed
  • 1 contributor

Commits on Oct 13, 2016

  1. Expose HTTP::Params::Builder for better docs

    Ary Borenszweig committed Oct 13, 2016
    Copy the full SHA
    6c52bef View commit details
  2. OAuth and OAuth2: added block form to get_authorize_uri to add extr…

    …a form params
    Ary Borenszweig committed Oct 13, 2016
    Copy the full SHA
    9ebc468 View commit details
  3. Copy the full SHA
    89da3a7 View commit details
Showing with 131 additions and 24 deletions.
  1. +17 −0 spec/std/oauth/consumer_spec.cr
  2. +16 −0 spec/std/oauth2/client_spec.cr
  3. +6 −2 src/http/params.cr
  4. +51 −18 src/oauth/consumer.cr
  5. +41 −4 src/oauth2/client.cr
17 changes: 17 additions & 0 deletions spec/std/oauth/consumer_spec.cr
Original file line number Diff line number Diff line change
@@ -23,6 +23,23 @@ describe OAuth::Consumer do
uri = consumer.get_authorize_uri request_token
uri.should eq("https://example.com/foo?oauth_token=request_token")
end

it "without block" do
consumer = OAuth::Consumer.new "example.com", "consumer_key", "consumer_secret"
request_token = OAuth::RequestToken.new "request_token", "request_secret"
uri = consumer.get_authorize_uri(request_token) do |form|
form.add "baz", "qux"
end
uri.should eq("https://example.com/oauth/authorize?oauth_token=request_token&baz=qux")
end

it "with absolute uri" do
consumer = OAuth::Consumer.new "example.com", "consumer_key", "consumer_secret",
authorize_uri: "https://example2.com:1234/foo?bar=baz"
request_token = OAuth::RequestToken.new "request_token", "request_secret"
uri = consumer.get_authorize_uri request_token
uri.should eq("https://example2.com:1234/foo?oauth_token=request_token&bar=baz")
end
end

typeof(begin
16 changes: 16 additions & 0 deletions spec/std/oauth2/client_spec.cr
Original file line number Diff line number Diff line change
@@ -20,6 +20,22 @@ describe OAuth2::Client do
uri = client.get_authorize_uri(scope: "foo bar", state: "xyz")
uri.should eq("https://localhost/oauth2/authorize?client_id=client_id&redirect_uri=uri&response_type=code&scope=foo+bar&state=xyz")
end

it "gets with block" do
client = OAuth2::Client.new "localhost", "client_id", "client_secret", redirect_uri: "uri"
uri = client.get_authorize_uri(scope: "foo bar") do |form|
form.add "baz", "qux"
end
uri.should eq("https://localhost/oauth2/authorize?client_id=client_id&redirect_uri=uri&response_type=code&scope=foo+bar&baz=qux")
end

it "gets with absolute uri" do
client = OAuth2::Client.new "localhost", "client_id", "client_secret",
redirect_uri: "uri",
authorize_uri: "https://example2.com:1234/foo?bar=baz"
uri = client.get_authorize_uri(scope: "foo bar")
uri.should eq("https://example2.com:1234/foo?client_id=client_id&redirect_uri=uri&response_type=code&scope=foo+bar&bar=baz")
end
end

typeof(begin
8 changes: 6 additions & 2 deletions src/http/params.cr
Original file line number Diff line number Diff line change
@@ -88,7 +88,7 @@ module HTTP
# end
# params # => "color=black&name=crystal&year=2012%20-%20today"
# ```
def self.build : String
def self.build(&block : Builder ->) : String
form_builder = Builder.new
yield form_builder
form_builder.to_s
@@ -282,7 +282,10 @@ module HTTP
URI.unescape_one query, bytesize, i, byte, char, buffer, true
end

# :nodoc:
# HTTP params builder.
#
# Every parameter added is directly written to an IO,
# where keys and values are properly escaped.
class Builder
@io : IO
@first : Bool
@@ -291,6 +294,7 @@ module HTTP
@first = true
end

# Adds a key-value pair to the params being built.
def add(key, value)
@io << '&' unless @first
@first = false
69 changes: 51 additions & 18 deletions src/oauth/consumer.cr
Original file line number Diff line number Diff line change
@@ -17,6 +17,7 @@
#
# # Create consumer, optionally pass custom URIs if needed,
# # if the request, authorize or access_token URIs are not the standard ones
# # (they can also be absolute URLs)
# consumer = OAuth::Consumer.new("api.example.com", consumer_key, consumer_secret)
#
# # Get a request token.
@@ -52,6 +53,12 @@
class OAuth::Consumer
@tls : Bool

# Creates an OAuth consumer.
#
# Any or all of the customizable URIs *request_token_uri*, *authorize_uri* and
# *access_token_uri* can be relative or absolute.
# If they are relative, the given *host*, *port* and *scheme* will be used.
# If they are absolute, the absolute URL will be used.
def initialize(@host : String, @consumer_key : String, @consumer_secret : String,
@port : Int32 = 443,
@scheme : String = "https",
@@ -65,27 +72,45 @@ class OAuth::Consumer
# [RFC5849, Section 2.1](https://tools.ietf.org/html/rfc5849#section-2.1)
#
# Raises `OAuth::Error` if there was an error getting the request token.
def get_request_token(oauth_callback = "oob") : RequestToken
with_new_http_client(nil, nil, {"oauth_callback" => oauth_callback}) do |client|
response = client.post @request_token_uri
handle_response(response) do
RequestToken.from_response(response.body)
end
def get_request_token(oauth_callback = "oob") # : RequestToken
post(nil, nil, {"oauth_callback" => oauth_callback}, @request_token_uri) do |response|
RequestToken.from_response(response.body)
end
end

# Returns an authorize URI from a given request token to redirect the user
# to obtain an access token, as specified by
# [RFC5849, Section 2.2](https://tools.ietf.org/html/rfc5849#section-2.2)
def get_authorize_uri(request_token, oauth_callback = nil) : String
query = HTTP::Params.build do |form|
get_authorize_uri(request_token, oauth_callback) { }
end

# Returns an authorize URI from a given request token to redirect the user
# to obtain an access token, as specified by
# [RFC5849, Section 2.2](https://tools.ietf.org/html/rfc5849#section-2.2)
#
# Yields an `HTTP::Params::Builder` to add extra parameters other than those
# defined by the standard.
def get_authorize_uri(request_token, oauth_callback = nil, &block : HTTP::Params::Builder ->) : String
uri = URI.parse(@authorize_uri)

# Use the default URI if it's not an absolute one
unless uri.host
uri = URI.new(@scheme, @host, @port, @authorize_uri)
end

uri.query = HTTP::Params.build do |form|
form.add "oauth_token", request_token.token
if oauth_callback
form.add "oauth_callback", oauth_callback
form.add "oauth_callback", oauth_callback if oauth_callback
if query = uri.query
HTTP::Params.parse(query).each do |key, value|
form.add key, value
end
end
yield form
end

URI.new(@scheme, @host, @port, @authorize_uri, query).to_s
uri.to_s
end

# Gets an access token from a previously obtained request token and an *oauth_verifier*
@@ -96,11 +121,8 @@ class OAuth::Consumer
def get_access_token(request_token, oauth_verifier, extra_params = nil) : AccessToken
extra_params ||= {} of String => String
extra_params["oauth_verifier"] = oauth_verifier
with_new_http_client(request_token.token, request_token.secret, extra_params) do |client|
response = client.post @access_token_uri
handle_response(response) do
AccessToken.from_response(response.body)
end
post(request_token.token, request_token.secret, extra_params, @access_token_uri) do |response|
AccessToken.from_response(response.body)
end
end

@@ -110,11 +132,22 @@ class OAuth::Consumer
authenticate client, token.token, token.secret, nil
end

private def with_new_http_client(oauth_token, token_shared_secret, extra_params)
client = HTTP::Client.new @host, @port, tls: @tls
private def post(oauth_token, token_shared_secret, extra_params, target_uri)
uri = URI.parse(target_uri)

# If the target uri is absolute, we use that instead of the default values
if uri.host
client = HTTP::Client.new(uri)
target_uri = "#{uri.path}?#{uri.query}"
else
client = HTTP::Client.new @host, @port, tls: @tls
end

authenticate client, oauth_token, token_shared_secret, extra_params

begin
yield client
response = client.post target_uri
yield response
ensure
client.close
end
45 changes: 41 additions & 4 deletions src/oauth2/client.cr
Original file line number Diff line number Diff line change
@@ -18,6 +18,7 @@
#
# # Create oauth client, optionally pass custom URIs if needed,
# # if the authorize or token URIs are not the standard ones
# # (they can also be absolute URLs)
# oauth2_client = OAuth2::Client.new("api.example.com", client_id, client_secret,
# redirect_uri: redirect_uri)
#
@@ -53,6 +54,12 @@
# You can also use an `OAuth2::Session` to automatically refresh expired
# tokens before each request.
class OAuth2::Client
# Creates an OAuth client.
#
# Any or all of the customizable URIs *authorize_uri* and
# *token_uri* can be relative or absolute.
# If they are relative, the given *host*, *port* and *scheme* will be used.
# If they are absolute, the absolute URL will be used.
def initialize(@host : String, @client_id : String, @client_secret : String,
@port = 443,
@scheme = "https",
@@ -64,15 +71,37 @@ class OAuth2::Client
# Builds an authorize URI, as specified by
# [RFC6749, Section 4.1.1](https://tools.ietf.org/html/rfc6749#section-4.1.1)
def get_authorize_uri(scope = nil, state = nil) : String
query = HTTP::Params.build do |form|
get_authorize_uri(scope, state) { }
end

# Builds an authorize URI, as specified by
# [RFC6749, Section 4.1.1](https://tools.ietf.org/html/rfc6749#section-4.1.1)
#
# Yields an `HTTP::Params::Builder` to add extra parameters other than those
# defined by the standard.
def get_authorize_uri(scope = nil, state = nil, &block : HTTP::Params::Builder ->) : String
uri = URI.parse(@authorize_uri)

# Use the default URI if it's not an absolute one
unless uri.host
uri = URI.new(@scheme, @host, @port, @authorize_uri)
end

uri.query = HTTP::Params.build do |form|
form.add "client_id", @client_id
form.add "redirect_uri", @redirect_uri
form.add "response_type", "code"
form.add "scope", scope unless scope.nil?
form.add "state", state unless state.nil?
if query = uri.query
HTTP::Params.parse(query).each do |key, value|
form.add key, value
end
end
yield form
end

URI.new(@scheme, @host, @port, @authorize_uri, query).to_s
uri.to_s
end

# Gets an access token using an authorization code, as specified by
@@ -97,7 +126,7 @@ class OAuth2::Client

# 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) : AccessToken
def get_access_token_using_client_credentials(scope = nil)
get_access_token do |form|
form.add("grant_type", "client_credentials")
form.add("scope", scope) unless scope.nil?
@@ -121,6 +150,14 @@ class OAuth2::Client
end

private def token_uri
URI.new(@scheme, @host, @port, @token_uri).to_s
uri = URI.parse(@token_uri)

if uri.host
# If it's an absolute URI, use that one
@token_uri
else
# Otherwise use the default one
URI.new(@scheme, @host, @port, @token_uri).to_s
end
end
end