signet-0.14.0/0000755000004100000410000000000013673221365013125 5ustar www-datawww-datasignet-0.14.0/README.md0000644000004100000410000000331013673221365014401 0ustar www-datawww-data# Signet
Homepage
https://github.com/googleapis/signet/
Author
Bob Aman
Copyright
Copyright © 2010 Google, Inc.
License
Apache 2.0
[![Gem Version](https://badge.fury.io/rb/signet.svg)](https://badge.fury.io/rb/signet) ## Description Signet is an OAuth 1.0 / OAuth 2.0 implementation. ## Reference - {Signet::OAuth1} - {Signet::OAuth1::Client} - {Signet::OAuth1::Credential} - {Signet::OAuth1::Server} - {Signet::OAuth2} - {Signet::OAuth2::Client} ## Example Usage for Google # Initialize the client ``` ruby require 'signet/oauth_2/client' client = Signet::OAuth2::Client.new( :authorization_uri => 'https://accounts.google.com/o/oauth2/auth', :token_credential_uri => 'https://oauth2.googleapis.com/token', :client_id => '44410190108-74nkm6jc5e3vvjqis803frkvmu88cu3a.apps.googleusercontent.com', :client_secret => 'X1NUhvO-rQr9sm8uUSMY8i7v', :scope => 'email profile', :redirect_uri => 'https://example.client.com/oauth' ) ``` # Request an authorization code ``` redirect_to(client.authorization_uri) ``` # Obtain an access token ``` client.code = request.query['code'] client.fetch_access_token! ``` ## Install `gem install signet` Be sure `https://rubygems.org` is in your gem sources. ## Supported Ruby Versions This library requires Ruby 2.4 or later. In general, this library supports Ruby versions that are considered current and supported by Ruby Core (that is, Ruby versions that are either in normal maintenance or in security maintenance). See https://www.ruby-lang.org/en/downloads/branches/ for further details. signet-0.14.0/spec/0000755000004100000410000000000013673221365014057 5ustar www-datawww-datasignet-0.14.0/spec/signet_spec.rb0000644000004100000410000000620413673221365016711 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "signet/oauth_2" describe Signet do describe "when parsing an auth param list" do it "should correctly handle commas" do parameters = Signet.parse_auth_param_list( 'a="1, 2" , b="3,4",c="5 , 6" ,d="7 ,8"' ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["a"]).to eq "1, 2" expect(parameters["b"]).to eq "3,4" expect(parameters["c"]).to eq "5 , 6" expect(parameters["d"]).to eq "7 ,8" end it "should correctly handle backslash-escaped pairs" do parameters = Signet.parse_auth_param_list( 'token="\t\o\k\e\n" sigalg="\s\i\g\a\l\g" data="\d\a\t\a"' ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["token"]).to eq "token" expect(parameters["sigalg"]).to eq "sigalg" expect(parameters["data"]).to eq "data" end it "should liberally handle space-separated auth-param lists" do parameters = Signet.parse_auth_param_list( 'token="token" sigalg="sigalg" data="data" sig="sig"' ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["token"]).to eq "token" expect(parameters["sigalg"]).to eq "sigalg" expect(parameters["data"]).to eq "data" expect(parameters["sig"]).to eq "sig" end it "should liberally handle single-quoted auth-param lists" do parameters = Signet.parse_auth_param_list( "token='token' sigalg='sigalg' data='data' sig='sig'" ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["token"]).to eq "token" expect(parameters["sigalg"]).to eq "sigalg" expect(parameters["data"]).to eq "data" expect(parameters["sig"]).to eq "sig" end it "should liberally handle unquoted auth-param lists" do parameters = Signet.parse_auth_param_list( "token=token sigalg=sigalg data=data sig=sig" ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["token"]).to eq "token" expect(parameters["sigalg"]).to eq "sigalg" expect(parameters["data"]).to eq "data" expect(parameters["sig"]).to eq "sig" end it "should liberally handle auth-param lists with empty sections" do parameters = Signet.parse_auth_param_list( "token=token, , sigalg=sigalg,, data=data, sig=sig" ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["token"]).to eq "token" expect(parameters["sigalg"]).to eq "sigalg" expect(parameters["data"]).to eq "data" expect(parameters["sig"]).to eq "sig" end end end signet-0.14.0/spec/spec.opts0000644000004100000410000000004013673221365015712 0ustar www-datawww-data--colour --format documentation signet-0.14.0/spec/signet/0000755000004100000410000000000013673221365015350 5ustar www-datawww-datasignet-0.14.0/spec/signet/oauth_1/0000755000004100000410000000000013673221365016710 5ustar www-datawww-datasignet-0.14.0/spec/signet/oauth_1/signature_methods/0000755000004100000410000000000013673221365022434 5ustar www-datawww-datasignet-0.14.0/spec/signet/oauth_1/signature_methods/hmac_sha1_spec.rb0000644000004100000410000000503313673221365025620 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "signet" require "signet/oauth_1" require "signet/oauth_1/signature_methods/hmac_sha1" describe Signet::OAuth1::HMACSHA1 do it "should correctly generate a signature" do method = "GET" uri = "http://photos.example.net/photos" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "file" => "vacation.jpg", "size" => "original" } client_credential_secret = "kd94hf93k423kf44" token_credential_secret = "pfkkdhi9sl3r4s00" base_string = Signet::OAuth1.generate_base_string method, uri, parameters expect(Signet::OAuth1::HMACSHA1.generate_signature( base_string, client_credential_secret, token_credential_secret )).to eq "tR3+Ty81lMeYAr/Fid0kMTYa/WM=" end it "should correctly generate a signature" do method = "GET" uri = "http://photos.example.net/photos" parameters = { "oauth_consumer_key" => "www.example.com", "oauth_token" => "4/QL2GT6b5uznYem1ZGH6v+-9mMvRL", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "file" => "vacation.jpg", "size" => "original" } client_credential_secret = "Kv+o2XXL/9RxkQW3lO3QTVlH" token_credential_secret = "QllSuL9eQ5FXFO1Z/HcgL4ON" base_string = Signet::OAuth1.generate_base_string method, uri, parameters expect(Signet::OAuth1::HMACSHA1.generate_signature( base_string, client_credential_secret, token_credential_secret )).to eq "G/nkdbmbpEA+6RD1Sc5uIefhFfQ=" end end signet-0.14.0/spec/signet/oauth_1/signature_methods/plaintext_spec.rb0000644000004100000410000000511013673221365026000 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "signet" require "signet/oauth_1" require "signet/oauth_1/signature_methods/plaintext" describe Signet::OAuth1::PLAINTEXT do it "should correctly generate a signature" do method = "GET" uri = "http://photos.example.net/photos" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "file" => "vacation.jpg", "size" => "original" } client_credential_secret = "kd94hf93k423kf44" token_credential_secret = "pfkkdhi9sl3r4s00" base_string = Signet::OAuth1.generate_base_string method, uri, parameters expect(Signet::OAuth1::PLAINTEXT.generate_signature( base_string, client_credential_secret, token_credential_secret )).to eq "kd94hf93k423kf44%26pfkkdhi9sl3r4s00" end it "should correctly generate a signature" do method = "GET" uri = "http://photos.example.net/photos" parameters = { "oauth_consumer_key" => "www.example.com", "oauth_token" => "4/QL2GT6b5uznYem1ZGH6v+-9mMvRL", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "file" => "vacation.jpg", "size" => "original" } client_credential_secret = "Kv+o2XXL/9RxkQW3lO3QTVlH" token_credential_secret = "QllSuL9eQ5FXFO1Z/HcgL4ON" base_string = Signet::OAuth1.generate_base_string method, uri, parameters expect(Signet::OAuth1::PLAINTEXT.generate_signature( base_string, client_credential_secret, token_credential_secret )).to eq "Kv%252Bo2XXL%252F9RxkQW3lO3QTVlH%26QllSuL9eQ5FXFO1Z%252FHcgL4ON" end end signet-0.14.0/spec/signet/oauth_1/signature_methods/rsa_sha1_spec.rb0000644000004100000410000001432713673221365025503 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "signet" require "signet/oauth_1" require "signet/oauth_1/signature_methods/rsa_sha1" describe Signet::OAuth1::RSASHA1 do it "should correctly generate a signature" do method = "GET" uri = "http://term.ie/oauth/example/request_token.php" parameters = { "oauth_consumer_key" => "key", "oauth_signature_method" => "RSA-SHA1", "oauth_timestamp" => "1377815426", "oauth_nonce" => "c3839c47cb204a20e042b11a5cc9f971", "oauth_version" => "1.0" } client_credential_secret = "-----BEGIN PRIVATE KEY----- MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d 7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ 3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 Lw03eHTNQghS0A== -----END PRIVATE KEY-----" token_credential_secret = "pfkkdhi9sl3r4s00" base_string = Signet::OAuth1.generate_base_string method, uri, parameters expect(Signet::OAuth1::RSASHA1.generate_signature( base_string, client_credential_secret, token_credential_secret )).to eq "P72T4RS8dVBneQPJSY71D3iLEjge2tiivxEasPVoaoDldDgPdwpQfhS1q0th19jB3B3+9P6tBWjpWaVPxrNZe3ssBCiwS/EmXZ/6VCJGU3YoDHMtz+0jCd36NjHj5I6TpLVQ8/rtfy6+EzpdUMz7ydnhKXYqJFPOWnNv8HM1W7I=" end end describe Signet::OAuth1::RSASHA1 do it "should correctly generate a signature" do method = "GET" uri = "http://photos.example.net/photos" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_signature_method" => "RSA-SHA1", "oauth_timestamp" => "1196666512", "oauth_nonce" => "13917289812797014437", "oauth_version" => "1.0", "file" => "vacaction.jpg", "size" => "original" } client_credential_secret = "-----BEGIN PRIVATE KEY----- MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d 7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ 3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 Lw03eHTNQghS0A== -----END PRIVATE KEY-----" token_credential_secret = "pfkkdhi9sl3r4s00" base_string = Signet::OAuth1.generate_base_string method, uri, parameters expect(Signet::OAuth1::RSASHA1.generate_signature( base_string, client_credential_secret, token_credential_secret )).to eq "jvTp/wX1TYtByB1m+Pbyo0lnCOLIsyGCH7wke8AUs3BpnwZJtAuEJkvQL2/9n4s5wUmUl4aCI4BwpraNx4RtEXMe5qg5T1LVTGliMRpKasKsW//e+RinhejgCuzoH26dyF8iY2ZZ/5D1ilgeijhV/vBka5twt399mXwaYdCwFYE=" end it "should correctly generate a signature" do method = "GET" uri = "http://term.ie/oauth/example/access_token.php" parameters = { "oauth_consumer_key" => "key", "oauth_token" => "requestkey", "oauth_signature_method" => "RSA-SHA1", "oauth_timestamp" => "1377815426", "oauth_nonce" => "8ae9ac8192dd3cd7372e0324bf879602", "oauth_version" => "1.0" } client_credential_secret = "-----BEGIN PRIVATE KEY----- MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALRiMLAh9iimur8V A7qVvdqxevEuUkW4K+2KdMXmnQbG9Aa7k7eBjK1S+0LYmVjPKlJGNXHDGuy5Fw/d 7rjVJ0BLB+ubPK8iA/Tw3hLQgXMRRGRXXCn8ikfuQfjUS1uZSatdLB81mydBETlJ hI6GH4twrbDJCR2Bwy/XWXgqgGRzAgMBAAECgYBYWVtleUzavkbrPjy0T5FMou8H X9u2AC2ry8vD/l7cqedtwMPp9k7TubgNFo+NGvKsl2ynyprOZR1xjQ7WgrgVB+mm uScOM/5HVceFuGRDhYTCObE+y1kxRloNYXnx3ei1zbeYLPCHdhxRYW7T0qcynNmw rn05/KO2RLjgQNalsQJBANeA3Q4Nugqy4QBUCEC09SqylT2K9FrrItqL2QKc9v0Z zO2uwllCbg0dwpVuYPYXYvikNHHg+aCWF+VXsb9rpPsCQQDWR9TT4ORdzoj+Nccn qkMsDmzt0EfNaAOwHOmVJ2RVBspPcxt5iN4HI7HNeG6U5YsFBb+/GZbgfBT3kpNG WPTpAkBI+gFhjfJvRw38n3g/+UeAkwMI2TJQS4n8+hid0uus3/zOjDySH3XHCUno cn1xOJAyZODBo47E+67R4jV1/gzbAkEAklJaspRPXP877NssM5nAZMU0/O/NGCZ+ 3jPgDUno6WbJn5cqm8MqWhW1xGkImgRk+fkDBquiq4gPiT898jusgQJAd5Zrr6Q8 AO/0isr/3aa6O6NLQxISLKcPDk2NOccAfS/xOtfOz4sJYM3+Bs4Io9+dZGSDCA54 Lw03eHTNQghS0A== -----END PRIVATE KEY-----" token_credential_secret = "QllSuL9eQ5FXFO1Z/HcgL4ON" base_string = Signet::OAuth1.generate_base_string method, uri, parameters expect(Signet::OAuth1::RSASHA1.generate_signature( base_string, client_credential_secret, token_credential_secret )).to eq "Q1O7Ovi0jdacl/OTJoH3MAyOO/9H/tTXmoJzP/YqiKEJ+/wfShXo1RXX0xmlcjDR1XYxB1RMgHkFWQYYwz1qGCUhkXlH1c/to2qxPksptfPHRe7PJTxRClrdqLFOlhN7w2kO7tHVCeEp8IJIKON9q7cdXroTP7ctPPS+Q883SS0=" end end signet-0.14.0/spec/signet/oauth_1/server_spec.rb0000644000004100000410000007631613673221365021572 0ustar www-datawww-data# Copyright (C) 2011 The Yakima Herald-Republic. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "signet/oauth_1/server" require "signet/oauth_1/client" require "addressable/uri" require "stringio" def merge_body chunked_body merged_body = StringIO.new chunked_body.each do |chunk| merged_body.write chunk end merged_body.string end def make_oauth_signature_header real_headers = {} [oauth_headers({ "oauth_signature" => "oauth_signature" }.merge(real_headers))] end def make_oauth_token_header real_headers = {} [oauth_headers({ "oauth_token" => "oauth_token" }.merge(real_headers))] end def oauth_headers real_headers = {} headers = {} %w[oauth_consumer_key oauth_timestamp oauth_nonce].each do |key| headers[key] = key end headers["oauth_signature_method"] = "HMAC-SHA1" headers["oauth_version"] = "1.0" headers.merge! real_headers ["Authorization", ::Signet::OAuth1.generate_authorization_header(headers, nil)] end def make_temporary_credential_request client, callback = nil, uri = nil, realm = nil client.callback = callback if callback client.temporary_credential_uri = uri || "http://photos.example.net/initiate" client.generate_temporary_credential_request realm: realm end def make_token_credential_request client, verifier = nil, realm = nil, uri = nil client.token_credential_uri = uri || "http://photos.example.net/token" client.generate_token_credential_request(verifier: verifier || "12345", realm: realm) end def make_resource_request client, real_request = {}, realm = nil client.generate_authenticated_request( method: real_request[:method] || "GET", uri: real_request[:uri] || "http://photos.example.net/photos", body: real_request[:body], headers: real_request[:headers], realm: realm ) end describe Signet::OAuth1::Server, "unconfigured" do before do @server = Signet::OAuth1::Server.new end it "should not have a client_credential Proc" do expect(@server.client_credential).to eq nil end it "should not have a token_credential Proc" do expect(@server.token_credential).to eq nil end it "should not have a nonce_timestamp Proc" do expect(@server.nonce_timestamp).to eq nil end it "should not have a verifier Proc" do expect(@server.verifier).to eq nil end end describe Signet::OAuth1::Server, "configured" do before do @server = Signet::OAuth1::Server.new @client_credential_key = "dpf43f3p2l4k3l03" @client_credential_secret = "kd94hf93k423kf44" @token_credential_key = "nnch734d00sl2jdk" @token_credential_secret = "pfkkdhi9sl3r4s00" @temporary_credential_key = "hh5s93j4hdidpola" @temporary_credential_secret = "hdhd0244k9j7ao03" @verifier = "hfdp7dh39dks9884" @server.client_credential = lambda do |x| x.nil? ? nil : Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret) end @server.token_credential = lambda do |x| x.nil? ? nil : Signet::OAuth1::Credential.new(@token_credential_key, @token_credential_secret) end @server.temporary_credential = lambda do |x| x.nil? ? nil : Signet::OAuth1::Credential.new(@temporary_credential_key, @temporary_credential_secret) end @server.nonce_timestamp = lambda do |nonce, timestamp| !(nonce.nil? && timestamp.nil?) end @server.verifier = ->(x) { x == @verifier } end it "should raise an error if the client credential Proc is not set" do @server.client_credential = nil expect(lambda do @server.authenticate_resource_request end).to raise_error(ArgumentError) end it "should raise an error if the token credential Proc is not set" do @server.token_credential = nil expect(lambda do @server.authenticate_resource_request end).to raise_error(ArgumentError) end it "should raise an error if the temporary token credential Proc is not set" do @server.temporary_credential = nil expect(lambda do @server.authenticate_token_credential_request end).to raise_error(ArgumentError) end it "should raise an error if the verifier Proc is not set for a token request" do @server.verifier = nil expect(lambda do @server.authenticate_token_credential_request end).to raise_error(ArgumentError) end it "should raise an error if no request is provided" do expect(lambda do @server.authenticate_resource_request end).to raise_error(ArgumentError) end it "should raise an error if a bogus request is provided" do expect(lambda do @server.authenticate_resource_request( request: [] ) end).to raise_error(ArgumentError) end it "should raise an error if no Authentication header is provided" do expect(lambda do @server.authenticate_resource_request( method: "GET", uri: "https://photos.example.net/photos", headers: [["Authorization", ""]], body: "" ) end).to raise_error(Signet::MalformedAuthorizationError) end it "should raise an error if no URI is provided" do expect(lambda do @server.authenticate_resource_request( method: "GET", headers: [], body: "" ) end).to raise_error(ArgumentError) end it "should reject a request with the wrong signature method" do bad_method = "FOO" expect(lambda do @server.authenticate_resource_request( method: "GET", uri: "http://photos.example.net/photos", headers: make_oauth_token_header("oauth_signature_method"=>bad_method) ) end).to raise_error(NotImplementedError, "Unsupported signature method: #{bad_method}") end describe "calling find_temporary_credential" do it "should return a Signet credential if the Proc provides one" do @server.temporary_credential = lambda do |x| x.nil? ? nil : Signet::OAuth1::Credential.new( @temporary_credential_key, @temporary_credential_secret ) end expect(@server.find_temporary_credential(@temporary_credential_key)).to eq( Signet::OAuth1::Credential.new(@temporary_credential_key, @temporary_credential_secret) ) end it "should return a Signet credential if the Proc provides a key/secret pair" do @server.temporary_credential = lambda do |_x| { key: @temporary_credential_key, secret: @temporary_credential_secret } end expect(@server.find_temporary_credential(@temporary_credential_key)).to eq( Signet::OAuth1::Credential.new(@temporary_credential_key, @temporary_credential_secret) ) end it "should return a Signet credential if the Proc provides " \ "a key/secret Enumerable" do @server.temporary_credential = lambda do |_x| [@temporary_credential_key, @temporary_credential_secret] end expect(@server.find_temporary_credential(@temporary_credential_key)).to eq( Signet::OAuth1::Credential.new(@temporary_credential_key, @temporary_credential_secret) ) end it "should return nil if the Proc does not provide a usable response" do @server.temporary_credential = ->(_x) { nil } expect(@server.find_temporary_credential(@temporary_credential_key)).to eq nil end end describe "calling find_client_credential" do it "should return a Signet credential if the Proc provides one" do @server.client_credential = lambda do |x| x.nil? ? nil : Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret) end expect(@server.find_client_credential(@client_credential_key)).to eq( Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret) ) end it "should return a Signet credential if the Proc provides a key/secret pair" do @server.client_credential = lambda do |_x| { key: @client_credential_key, secret: @client_credential_secret } end expect(@server.find_client_credential(@client_credential_key)).to eq( Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret) ) end it "should return a Signet credential if the Proc provides " \ "a key/secret Enumerable" do @server.client_credential = lambda do |_x| [@client_credential_key, @client_credential_secret] end expect(@server.find_client_credential(@client_credential_key)).to eq( Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret) ) end it "should return nil if the Proc does not provide a usable response" do @server.client_credential = ->(_x) { nil } expect(@server.find_client_credential(@client_credential_key)).to be_nil end end describe "calling find_token_credential" do it "should return a Signet credential if the Proc provides one" do @server.token_credential = lambda do |x| x.nil? ? nil : Signet::OAuth1::Credential.new(@token_credential_key, @token_credential_secret) end expect(@server.find_token_credential(@token_credential_key)).to eq( Signet::OAuth1::Credential.new(@token_credential_key, @token_credential_secret) ) end it "should return a Signet credential if the Proc provides a key/secret pair" do @server.token_credential = lambda do |_x| { key: @token_credential_key, secret: @token_credential_secret } end expect(@server.find_token_credential(@token_credential_key)).to eq( Signet::OAuth1::Credential.new(@token_credential_key, @token_credential_secret) ) end it "should return a Signet credential if the Proc provides " \ "a key/secret Enumerable" do @server.token_credential = lambda do |_x| [@token_credential_key, @token_credential_secret] end expect(@server.find_token_credential(@token_credential_key)).to eq( Signet::OAuth1::Credential.new(@token_credential_key, @token_credential_secret) ) end it "should return nil if the Proc does not provide a usable response" do @server.token_credential = ->(_x) { nil } expect(@server.find_token_credential(@token_credential_key)).to be_nil end end describe "calling find_verifier" do it "should return false if server verifier returns false" do @server.verifier = ->(_x) { false } expect(@server.find_verifier(@verifier)).to eq false end it "should return false if server verifier returns nil" do @server.verifier = ->(_x) { nil } expect(@server.find_verifier(@verifier)).to eq false end it "should return true if server verifier returns a random object" do @server.verifier = ->(x) { x.succ } expect(@server.find_verifier(@verifier)).to eq true end end describe "calling validate_nonce_timestamp" do it "should return false if nonce_timestamp Proc returns false" do @server.nonce_timestamp = ->(_n, _t) { false } expect(@server.validate_nonce_timestamp("nonce", "timestamp")).to be false end it "should return false if nonce_timestamp Proc returns nil" do @server.nonce_timestamp = ->(_n, _t) { nil } expect(@server.validate_nonce_timestamp("nonce", "timestamp")).to be false end it "should return true if nonce_timestamp Proc returns a random object" do @server.nonce_timestamp = ->(n, t) { n + t.to_s } expect(@server.validate_nonce_timestamp("nonce", "timestamp")).to be true end end describe "expecting a request for a temporary credential" do before do @client = Signet::OAuth1::Client.new( client_credential_key: @client_credential_key, client_credential_secret: @client_credential_secret, temporary_credential_uri: "http://photos.example.net/initiate" ) end it "should raise an error if the client credential Proc is not set" do @server.client_credential = nil expect(lambda do @server.authenticate_temporary_credential_request( request: make_temporary_credential_request(@client) ) end).to raise_error(ArgumentError) end it "should reject an malformed request" do bad_request = make_temporary_credential_request @client, nil, "https://photos.example.net/photos" bad_request.headers["Authorization"].gsub!(/(OAuth)(.+)/, (Regexp.last_match 1).to_s) expect(lambda do @server.authenticate_temporary_credential_request( request: bad_request ) end).to raise_error(Signet::MalformedAuthorizationError) end it "should call a user-supplied Proc to validate a nonce/timestamp pair" do nonce_callback = double "nonce" expect(nonce_callback).to receive(:call).once.with( an_instance_of(String), an_instance_of(String) ).and_return(true) @server.nonce_timestamp = nonce_callback @server.authenticate_temporary_credential_request( request: make_temporary_credential_request(@client) ) end it "should return 'oob' for a valid request without an oauth_callback" do bad_request = make_temporary_credential_request @client expect(@server.authenticate_temporary_credential_request( request: bad_request )).to eq "oob" end it "should return the oauth_callback for a valid request " \ "with an oauth_callback" do callback = "http://printer.example.com/ready" expect(@server.authenticate_temporary_credential_request( request: make_temporary_credential_request(@client, callback) )).to eq callback end it "should return false for an unauthenticated request" do bad_request = make_temporary_credential_request @client bad_request.headers["Authorization"].gsub!(/oauth_signature=\".+\"/, "oauth_signature=\"foobar\"") expect(@server.authenticate_temporary_credential_request( request: bad_request )).to eq false end it "should return nil from #request_realm if no realm is provided" do req = make_temporary_credential_request @client expect(@server.request_realm( request: req )).to eq nil end describe "with a Realm provided" do it "should return the realm from #request_realm" do req = make_temporary_credential_request @client, nil, nil, "Photos" expect(@server.request_realm( request: req )).to eq "Photos" end it 'should return "oob" with a valid request without an oauth_callback' do req = make_temporary_credential_request @client, nil, nil, "Photos" expect(@server.authenticate_temporary_credential_request( request: req )).to eq "oob" end end end describe "expecting a request for a token credential" do before do @client = Signet::OAuth1::Client.new( client_credential_key: @client_credential_key, client_credential_secret: @client_credential_secret, temporary_credential_key: @temporary_credential_key, temporary_credential_secret: @temporary_credential_secret, token_credential_uri: "http://photos.example.net/token" ) @return_hash = { client_credential: Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret), temporary_credential: Signet::OAuth1::Credential.new(@temporary_credential_key, @temporary_credential_secret), realm: nil } end it "should reject an malformed request" do bad_request = make_token_credential_request @client bad_request.headers["Authorization"].gsub!(/(OAuth)(.+)/, (Regexp.last_match 1).to_s) expect(lambda do @server.authenticate_token_credential_request( request: bad_request ) end).to raise_error(Signet::MalformedAuthorizationError) end it "should call a user-supplied Proc to validate a nonce/timestamp pair" do nonce_callback = double "nonce" expect(nonce_callback).to receive(:call).once.with( an_instance_of(String), an_instance_of(String) ).and_return(true) @server.nonce_timestamp = nonce_callback @server.authenticate_token_credential_request( request: make_token_credential_request(@client) ) end it "should return an informational hash for a valid request" do expect(@server.authenticate_token_credential_request( request: make_token_credential_request(@client) )).to eq @return_hash end it "should return nil for an unauthenticated request" do bad_request = make_token_credential_request @client bad_request.headers["Authorization"].gsub!(/oauth_signature=\".+\"/, "oauth_signature=\"foobar\"") expect(@server.authenticate_token_credential_request( request: bad_request )).to eq nil end it "should call a user-supplied Proc to fetch the client credential" do client_cred = Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret) key_callback = double "client_cred" expect(key_callback).to receive(:call).at_least(:once).with( @client_credential_key ).and_return(client_cred) @server.client_credential = key_callback @server.authenticate_token_credential_request( request: make_token_credential_request(@client) ) end it "should call a user-supplied Proc to fetch the temporary token credential" do temp_cred = Signet::OAuth1::Credential.new(@temporary_credential_key, @temporary_credential_secret) temp_callback = double "temp_cred" expect(temp_callback).to receive(:call).at_least(:once).with( @temporary_credential_key ).and_return(temp_cred) @server.temporary_credential = temp_callback @server.authenticate_token_credential_request( request: make_token_credential_request(@client) ) end it "should return nil from #request_realm if no realm is provided" do req = make_token_credential_request @client expect(@server.request_realm( request: req )).to eq nil end describe "with a Realm provided" do before do @realm = "Photos" @return_hash[:realm] = @realm end it "should return the realm from #request_realm" do req = make_token_credential_request @client, nil, @realm expect(@server.request_realm( request: req )).to eq @realm end it "should an informational hash with a valid request" do req = make_token_credential_request @client, nil, @realm expect(@server.authenticate_token_credential_request( request: req )).to eq @return_hash end end end describe "expecting a request for a protected resource" do before :each do @client = Signet::OAuth1::Client.new( client_credential_key: @client_credential_key, client_credential_secret: @client_credential_secret, token_credential_key: @token_credential_key, token_credential_secret: @token_credential_secret ) @return_hash = { client_credential: Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret), token_credential: Signet::OAuth1::Credential.new(@token_credential_key, @token_credential_secret), realm: nil } end it "should not raise an error if a request body is chunked(as Array)" do approved = @server.authenticate_resource_request( method: "POST", uri: "https://photos.example.net/photos", body: ["A chunked body."], headers: make_oauth_signature_header ) expect(approved).to eq nil end it "should not raise an error if a request body is chunked(as StringIO)" do chunked_body = StringIO.new chunked_body.write "A chunked body." chunked_body.rewind approved = @server.authenticate_resource_request( method: "POST", uri: "https://photos.example.net/photos", body: chunked_body, headers: make_oauth_signature_header ) expect(approved).to eq nil end it "should raise an error if a request body is of a bogus type" do expect(lambda do @server.authenticate_resource_request( method: "POST", uri: "https://photos.example.net/photos", body: 42, headers: make_oauth_signature_header ) end).to raise_error(TypeError) end it "should use form parameters in signature if request is a POSTed form" do req = make_resource_request( @client, method: "POST", headers: { "Content-Type"=>"application/x-www-form-urlencoded" }, body: "c2&a3=2+q" ) expect(@server.authenticate_resource_request(request: req)).to eq @return_hash end it "should raise an error if signature is x-www-form-encoded " \ "but does not send form parameters in header" do # Make a full request so that we can sign against the form parameters # that will be removed. req = make_resource_request( @client, method: "POST", headers: { "Content-Type"=>"application/x-www-form-urlencoded" }, body: "c2&a3=2+q" ) req.headers["Authorization"].gsub!(/c2=\"\", a3=\"2%20q\", /, "") expect(lambda do @server.authenticate_resource_request request: req end).to raise_error(Signet::MalformedAuthorizationError, "Request is of type application/x-www-form-urlencoded but " \ "Authentication header did not include form values") end it "should call a user-supplied Proc to validate a nonce/timestamp pair" do nonce_callback = double "nonce" expect(nonce_callback).to receive(:call).once.with( an_instance_of(String), an_instance_of(String) ).and_return(true) @server.nonce_timestamp = nonce_callback @server.authenticate_resource_request( request: make_resource_request(@client) ) end it "should call a user-supplied Proc to fetch the client credential" do client_cred = Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret) key_callback = double "client_cred" expect(key_callback).to receive(:call).at_least(:once).with( @client_credential_key ).and_return(client_cred) @server.client_credential = key_callback @server.authenticate_resource_request( request: make_resource_request(@client) ) end it "should call a user-supplied Proc to fetch the token credential" do token_cred = Signet::OAuth1::Credential.new(@token_credential_key, @token_credential_secret) key_callback = double "token_cred" expect(key_callback).to receive(:call).at_least(:once).with( @token_credential_key ).and_return(token_cred) @server.token_credential = key_callback @server.authenticate_resource_request( request: make_resource_request(@client) ) end it "should return a Hash for a valid request" do expect(@server.authenticate_resource_request( request: make_resource_request(@client) )).to eq @return_hash end it "should return nil for a unauthenticated request" do bad_request = make_resource_request @client bad_request.headers["Authorization"].gsub!(/oauth_signature=\".+\"/, "oauth_signature=\"foobar\"") expect(@server.authenticate_resource_request(request: bad_request)).to eq nil end it "should return nil from #request_realm if no realm is provided" do req = make_resource_request @client expect(@server.request_realm( request: req )).to eq nil end describe "with a Realm provided" do before do @realm = "Photos" @return_hash[:realm] = @realm end it "should return the realm from #request_realm" do req = make_resource_request(@client, {}, @realm) expect(@server.request_realm( request: req )).to eq @realm end it "should return a hash containing the realm with a valid request" do req = make_resource_request(@client, {}, @realm) expect(@server.authenticate_resource_request( request: req )).to eq @return_hash end end end describe "expecting a two-legged request for a protected resource" do before do @client = Signet::OAuth1::Client.new( client_credential_key: @client_credential_key, client_credential_secret: @client_credential_secret, two_legged: true ) @return_hash = { client_credential: Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret), token_credential: nil, realm: nil } end it "should not raise an error if a request body is chunked(as Array)" do approved = @server.authenticate_resource_request( method: "POST", uri: "https://photos.example.net/photos", body: ["A chunked body."], headers: make_oauth_signature_header, two_legged: true ) expect(approved).to eq nil end it "should not raise an error if a request body is chunked(as StringIO)" do chunked_body = StringIO.new chunked_body.write "A chunked body." chunked_body.rewind approved = @server.authenticate_resource_request( method: "POST", uri: "https://photos.example.net/photos", body: chunked_body, headers: make_oauth_signature_header, two_legged: true ) expect(approved).to eq nil end it "should raise an error if a request body is of a bogus type" do expect(lambda do @server.authenticate_resource_request( method: "POST", uri: "https://photos.example.net/photos", body: 42, headers: make_oauth_signature_header, two_legged: true ) end).to raise_error(TypeError) end it "should use form parameters in signature if request is a POSTed form" do req = make_resource_request( @client, method: "POST", headers: { "Content-Type"=>"application/x-www-form-urlencoded" }, body: "c2&a3=2+q" ) expect(@server.authenticate_resource_request( request: req, two_legged: true )).to eq @return_hash end it "should raise an error if signature is x-www-form-encoded " \ "but does not send form parameters in header" do # Make a full request so that we can sign against the form parameters # that will be removed. req = make_resource_request( @client, method: "POST", headers: { "Content-Type"=>"application/x-www-form-urlencoded" }, body: "c2&a3=2+q" ) req.headers["Authorization"].gsub!(/c2=\"\", a3=\"2%20q\", /, "") expect(lambda do @server.authenticate_resource_request request: req, two_legged: true end).to raise_error(Signet::MalformedAuthorizationError, "Request is of type application/x-www-form-urlencoded but " \ "Authentication header did not include form values") end it "should call a user-supplied Proc to validate a nonce/timestamp pair" do nonce_callback = double "nonce" expect(nonce_callback).to receive(:call).once.with( an_instance_of(String), an_instance_of(String) ).and_return(true) @server.nonce_timestamp = nonce_callback @server.authenticate_resource_request( request: make_resource_request(@client), two_legged: true ) end it "should call a user-supplied Proc to fetch the client credential" do client_cred = Signet::OAuth1::Credential.new(@client_credential_key, @client_credential_secret) key_callback = double "client_cred" expect(key_callback).to receive(:call).at_least(:once).with( @client_credential_key ).and_return(client_cred) @server.client_credential = key_callback @server.authenticate_resource_request( request: make_resource_request(@client), two_legged: true ) end it "should return a informational hash for a valid request" do expect(@server.authenticate_resource_request( request: make_resource_request(@client), two_legged: true )).to eq @return_hash end it "should return false for a unauthenticated request" do bad_request = make_resource_request @client bad_request.headers["Authorization"].gsub!(/oauth_signature=\".+\"/, "oauth_signature=\"foobar\"") expect(@server.authenticate_resource_request(request: bad_request)).to eq nil end it "should return nil from #request_realm if no realm is provided" do req = make_resource_request @client expect(@server.request_realm( request: req )).to eq nil end describe "with a Realm provided" do before do @realm = "Photos" @return_hash[:realm] = @realm end it "should return the realm from #request_realm" do req = make_resource_request(@client, {}, @realm) expect(@server.request_realm( request: req, two_legged: true )).to eq @realm end it "should return a hash containing the realm with a valid request" do req = make_resource_request(@client, {}, @realm) expect(@server.authenticate_resource_request( request: req, two_legged: true )).to eq @return_hash end end end end signet-0.14.0/spec/signet/oauth_1/client_spec.rb0000644000004100000410000007400113673221365021527 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "multi_json" require "signet/oauth_1/client" require "addressable/uri" require "stringio" conn = Faraday.default_connection def merge_body chunked_body raise ArgumentError, "Expected chunked body, got nil." if chunked_body == nil merged_body = StringIO.new chunked_body.each do |chunk| merged_body.write chunk end merged_body.string end describe Signet::OAuth1::Client, "unconfigured" do before do @client = Signet::OAuth1::Client.new end it "should have no temporary_credential_uri" do expect(@client.temporary_credential_uri).to be_nil end it "should allow the temporary_credential_uri to be set to a String" do @client.temporary_credential_uri = "http://example.com/" expect(@client.temporary_credential_uri.to_s).to eq "http://example.com/" end it "should allow the temporary_credential_uri to be set to a URI" do @client.temporary_credential_uri = Addressable::URI.parse "http://example.com/" expect(@client.temporary_credential_uri.to_s).to eq "http://example.com/" end it "should have no authorization_uri" do expect(@client.authorization_uri).to be_nil end it "should allow the authorization_uri to be set to a String" do @client.authorization_uri = "http://example.com/authorize" expect(@client.authorization_uri.to_s).to include( "http://example.com/authorize" ) end it "should allow the authorization_uri to be set to a Hash" do @client.authorization_uri = { scheme: "http", host: "example.com", path: "/authorize" } expect(@client.authorization_uri.to_s).to include( "http://example.com/authorize" ) end it "should allow the authorization_uri to be set to a URI" do @client.authorization_uri = Addressable::URI.parse "http://example.com/authorize" expect(@client.authorization_uri.to_s).to include( "http://example.com/authorize" ) end it "should have no token_credential_uri" do expect(@client.token_credential_uri).to be_nil end it "should allow the token_credential_uri to be set to a String" do @client.token_credential_uri = "http://example.com/" expect(@client.token_credential_uri.to_s).to eq "http://example.com/" end it "should allow the token_credential_uri to be set to a Hash" do @client.token_credential_uri = { scheme: "http", host: "example.com", path: "/token" } expect(@client.token_credential_uri.to_s).to eq "http://example.com/token" end it "should allow the token_credential_uri to be set to a URI" do @client.token_credential_uri = Addressable::URI.parse "http://example.com/" expect(@client.token_credential_uri.to_s).to eq "http://example.com/" end it "should have no client_credential" do expect(@client.client_credential).to be_nil end it "should raise an error for partially set client credentials" do @client.client_credential_key = "12345" @client.client_credential_secret = nil expect(lambda do @client.client_credential end).to raise_error(ArgumentError) end it "should raise an error for partially set client credentials" do @client.client_credential_key = nil @client.client_credential_secret = "54321" expect(lambda do @client.client_credential end).to raise_error(ArgumentError) end it "should allow the client_credential to be set to a " \ "Signet::OAuth1::Credential" do @client.client_credential = Signet::OAuth1::Credential.new "12345", "54321" expect(@client.client_credential_key).to eq "12345" expect(@client.client_credential_secret).to eq "54321" expect(@client.client_credential).to eq Signet::OAuth1::Credential.new("12345", "54321") end it "should allow the client_credential to be set to nil" do @client.client_credential_key = "12345" @client.client_credential_secret = "54321" expect(@client.client_credential_key).to eq "12345" expect(@client.client_credential_secret).to eq "54321" @client.client_credential = nil expect(@client.client_credential).to be_nil expect(@client.client_credential_key).to be_nil expect(@client.client_credential_secret).to be_nil end it "should not allow the client_credential to be set to a bogus value" do expect(lambda do @client.client_credential = 42 end).to raise_error(TypeError) end it "should have no client_credential_key" do expect(@client.client_credential_key).to be_nil end it "should allow the client_credential_key to be set to a String" do @client.client_credential_key = "12345" expect(@client.client_credential_key).to eq "12345" end it "should not allow the client_credential_key to be set to a non-String" do expect(lambda do @client.client_credential_key = 12_345 end).to raise_error(TypeError) end it "should have no client_credential_secret" do expect(@client.client_credential_secret).to be_nil end it "should allow the client_credential_secret to be set to a String" do @client.client_credential_secret = "54321" expect(@client.client_credential_secret).to eq "54321" end it "should not allow the client_credential_secret " \ "to be set to a non-String" do expect(lambda do @client.client_credential_secret = 54_321 end).to raise_error(TypeError) end it "should have an out-of-band callback" do expect(@client.callback).to eq ::Signet::OAuth1::OUT_OF_BAND end it "should allow the callback to be set to a String" do @client.callback = "http://example.com/callback" expect(@client.callback).to eq "http://example.com/callback" end it "should allow the callback to be set to a URI" do @client.callback = Addressable::URI.parse "http://example.com/callback" expect(@client.callback).to eq "http://example.com/callback" end it "should not allow the callback to be set to a non-String" do expect(lambda do @client.callback = 12_345 end).to raise_error(TypeError) end it "should raise an error if the temporary credentials URI is not set" do @client.client_credential_key = "dpf43f3p2l4k3l03" @client.client_credential_secret = "kd94hf93k423kf44" expect(lambda do @client.generate_temporary_credential_request end).to raise_error(ArgumentError) end it "should raise an error if the client credential key is not set" do @client.temporary_credential_uri = "http://example.com/temporary_credentials" @client.client_credential_secret = "kd94hf93k423kf44" expect(lambda do @client.generate_temporary_credential_request end).to raise_error(ArgumentError) end it "should raise an error if the client credential secret is not set" do @client.temporary_credential_uri = "http://example.com/temporary_credentials" @client.client_credential_key = "dpf43f3p2l4k3l03" expect(lambda do @client.generate_temporary_credential_request end).to raise_error(ArgumentError) end it "should have no temporary_credential" do expect(@client.temporary_credential).to be_nil end it "should raise an error for partially set temporary credentials" do @client.temporary_credential_key = "12345" @client.temporary_credential_secret = nil expect(lambda do @client.temporary_credential end).to raise_error(ArgumentError) end it "should raise an error for partially set temporary credentials" do @client.temporary_credential_key = nil @client.temporary_credential_secret = "54321" expect(lambda do @client.temporary_credential end).to raise_error(ArgumentError) end it "should allow the temporary_credential to be set to a " \ "Signet::OAuth1::Credential" do @client.temporary_credential = Signet::OAuth1::Credential.new "12345", "54321" expect(@client.temporary_credential_key).to eq "12345" expect(@client.temporary_credential_secret).to eq "54321" expect(@client.temporary_credential).to eq Signet::OAuth1::Credential.new("12345", "54321") end it "should allow the temporary_credential to be set to nil" do @client.temporary_credential_key = "12345" @client.temporary_credential_secret = "54321" expect(@client.temporary_credential_key).to eq "12345" expect(@client.temporary_credential_secret).to eq "54321" @client.temporary_credential = nil expect(@client.temporary_credential).to be_nil expect(@client.temporary_credential_key).to be_nil expect(@client.temporary_credential_secret).to be_nil end it "should not allow the temporary_credential to be set to a bogus value" do expect(lambda do @client.temporary_credential = 42 end).to raise_error(TypeError) end it "should have no temporary_credential_key" do expect(@client.temporary_credential_key).to be_nil end it "should allow the temporary_credential_key to be set to a String" do @client.temporary_credential_key = "12345" expect(@client.temporary_credential_key).to eq "12345" end it "should not allow the temporary_credential_key " \ "to be set to a non-String" do expect(lambda do @client.temporary_credential_key = 12_345 end).to raise_error(TypeError) end it "should have no temporary_credential_secret" do expect(@client.temporary_credential_secret).to be_nil end it "should allow the temporary_credential_secret to be set to a String" do @client.temporary_credential_secret = "54321" expect(@client.temporary_credential_secret).to eq "54321" end it "should not allow the temporary_credential_secret " \ "to be set to a non-String" do expect(lambda do @client.temporary_credential_secret = 54_321 end).to raise_error(TypeError) end it "should have no token_credential" do expect(@client.token_credential).to be_nil end it "should raise an error for partially set token credentials" do @client.token_credential_key = "12345" @client.token_credential_secret = nil expect(lambda do @client.token_credential end).to raise_error(ArgumentError) end it "should raise an error for partially set token credentials" do @client.token_credential_key = nil @client.token_credential_secret = "54321" expect(lambda do @client.token_credential end).to raise_error(ArgumentError) end it "should allow the token_credential to be set to a " \ "Signet::OAuth1::Credential" do @client.token_credential = Signet::OAuth1::Credential.new "12345", "54321" expect(@client.token_credential_key).to eq "12345" expect(@client.token_credential_secret).to eq "54321" expect(@client.token_credential).to eq Signet::OAuth1::Credential.new("12345", "54321") end it "should allow the token_credential to be set to nil" do @client.token_credential_key = "12345" @client.token_credential_secret = "54321" expect(@client.token_credential_key).to eq "12345" expect(@client.token_credential_secret).to eq "54321" @client.token_credential = nil expect(@client.token_credential).to be_nil expect(@client.token_credential_key).to be_nil expect(@client.token_credential_secret).to be_nil end it "should not allow the token_credential to be set to a bogus value" do expect(lambda do @client.token_credential = 42 end).to raise_error(TypeError) end it "should have no token_credential_key" do expect(@client.token_credential_key).to be_nil end it "should allow the token_credential_key to be set to a String" do @client.token_credential_key = "12345" expect(@client.token_credential_key).to eq "12345" end it "should not allow the token_credential_key " \ "to be set to a non-String" do expect(lambda do @client.token_credential_key = 12_345 end).to raise_error(TypeError) end it "should have no token_credential_secret" do expect(@client.token_credential_secret).to be_nil end it "should allow the token_credential_secret to be set to a String" do @client.token_credential_secret = "54321" expect(@client.token_credential_secret).to eq "54321" end it "should not allow the token_credential_secret " \ "to be set to a non-String" do expect(lambda do @client.token_credential_secret = 54_321 end).to raise_error(TypeError) end it "should not allow the two_legged flag " \ "to be set to a non-Boolean" do expect(lambda do @client.two_legged = 42 end).to raise_error(TypeError) end end describe Signet::OAuth1::Client, "configured" do before do @client = Signet::OAuth1::Client.new @client.temporary_credential_uri = "http://example.com/temporary_credentials" @client.authorization_uri = "http://example.com/authorize" @client.token_credential_uri = "http://example.com/token_credentials" @client.callback = "http://example.com/callback" @client.client_credential_key = "dpf43f3p2l4k3l03" @client.client_credential_secret = "kd94hf93k423kf44" @client.temporary_credential_key = "hh5s93j4hdidpola" @client.temporary_credential_secret = "hdhd0244k9j7ao03" @client.token_credential_key = "nnch734d00sl2jdk" @client.token_credential_secret = "pfkkdhi9sl3r4s00" end it "should generate a JSON representation of the client" do json = @client.to_json expect(json).not_to be_nil deserialized = MultiJson.load json expect(deserialized["temporary_credential_uri"]).to eq "http://example.com/temporary_credentials" expect(deserialized["authorization_uri"]).to include( "http://example.com/authorize" ) expect(deserialized["token_credential_uri"]).to eq "http://example.com/token_credentials" expect(deserialized["callback"]).to eq "http://example.com/callback" expect(deserialized["client_credential_key"]).to eq "dpf43f3p2l4k3l03" expect(deserialized["client_credential_secret"]).to eq "kd94hf93k423kf44" expect(deserialized["temporary_credential_key"]).to eq "hh5s93j4hdidpola" expect(deserialized["temporary_credential_secret"]).to eq "hdhd0244k9j7ao03" expect(deserialized["token_credential_key"]).to eq "nnch734d00sl2jdk" expect(deserialized["token_credential_secret"]).to eq "pfkkdhi9sl3r4s00" end it "should generate an authorization URI with a callback" do @client.temporary_credential_key = nil expect(@client.authorization_uri.to_s).to eq "http://example.com/authorize?oauth_callback=http://example.com/callback" end it "should generate an authorization URI with a temporary credential" do @client.callback = nil expect(@client.authorization_uri.to_s).to include( "oauth_token=hh5s93j4hdidpola" ) end it "should generate an authorization URI both a callback and " \ "a temporary credential" do expect(@client.authorization_uri.to_s).to include( "oauth_callback=http://example.com/callback" ) expect(@client.authorization_uri.to_s).to include( "oauth_token=hh5s93j4hdidpola" ) end it "should generate an authorization URI with additional parameters" do authorization_uri = @client.authorization_uri( additional_parameters: { domain: "www.example.com" } ) expect(authorization_uri.to_s).to include( "oauth_callback=http://example.com/callback" ) expect(authorization_uri.to_s).to include( "oauth_token=hh5s93j4hdidpola" ) expect(authorization_uri.to_s).to include( "domain=www.example.com" ) end it "should raise an error if the verifier is not provided" do expect(lambda do @client.generate_token_credential_request end).to raise_error(ArgumentError) expect(lambda do @client.generate_token_credential_request verifier: nil end).to raise_error(ArgumentError) end it "should raise an error if the token credentials URI is not set" do @client.token_credential_uri = nil expect(lambda do @client.generate_token_credential_request verifier: "12345" end).to raise_error(ArgumentError) end it "should raise an error if the client credential key is not set" do @client.client_credential_key = nil expect(lambda do @client.generate_token_credential_request verifier: "12345" end).to raise_error(ArgumentError) end it "should raise an error if the client credential secret is not set" do @client.client_credential_secret = nil expect(lambda do @client.generate_token_credential_request verifier: "12345" end).to raise_error(ArgumentError) end it "should raise an error if the temporary credential key is not set" do @client.temporary_credential_key = nil expect(lambda do @client.generate_token_credential_request verifier: "12345" end).to raise_error(ArgumentError) end it "should raise an error if the temporary credential secret is not set" do @client.temporary_credential_secret = nil expect(lambda do @client.generate_token_credential_request verifier: "12345" end).to raise_error(ArgumentError) end it "should raise an error if the client credential key is not set" do @client.client_credential_key = nil expect(lambda do @client.generate_authenticated_request end).to raise_error(ArgumentError) end it "should raise an error if the client credential secret is not set" do @client.client_credential_secret = nil expect(lambda do @client.generate_authenticated_request end).to raise_error(ArgumentError) end it "should raise an error if the token credential key is not set" do @client.token_credential_key = nil expect(lambda do @client.generate_authenticated_request end).to raise_error(ArgumentError) end it "should raise an error if the token credential secret is not set" do @client.token_credential_secret = nil expect(lambda do @client.generate_authenticated_request end).to raise_error(ArgumentError) end it "should raise an error if no request is provided" do expect(lambda do @client.generate_authenticated_request end).to raise_error(ArgumentError) end it "should raise an error if a bogus request is provided" do expect do @client.generate_authenticated_request( request: [] ) end.to raise_error(ArgumentError) end it "should not raise an error if a request is "\ "provided without a connection" do request = @client.generate_authenticated_request( request: conn.build_request(:get) do |req| req.url "http://www.example.com/" end ) end it "should raise an error if no URI is provided" do expect(lambda do @client.generate_authenticated_request( :method => "GET", headers: [], :body => "" ) end).to raise_error(ArgumentError) end it "should not raise an error if a request body is chunked" do request = @client.generate_authenticated_request( method: "POST", :uri => "https://photos.example.net/photos", :body => ["A chunked body."] ) expect(request).to be_kind_of(Faraday::Request) expect(request.body).to eq "A chunked body." end it "should not raise an error if a request body is chunked" do chunked_body = StringIO.new chunked_body.write "A chunked body." chunked_body.rewind request = @client.generate_authenticated_request( method: "POST", :uri => "https://photos.example.net/photos", :body => chunked_body ) expect(request).to be_kind_of(Faraday::Request) expect(request.body).to eq "A chunked body." end it "should raise an error if a request body is of a bogus type" do expect(lambda do @client.generate_authenticated_request( method: "POST", :uri => "https://photos.example.net/photos", :body => 42 ) end).to raise_error(TypeError) end it "should correctly fetch the temporary credentials" do # Repeat this because signatures change from test to test 10.times do request = @client.generate_temporary_credential_request expect(request.method).to eq :post expect(request.path).to eq "http://example.com/temporary_credentials" authorization_header = request.headers["Authorization"] parameters = ::Signet::OAuth1.parse_authorization_header( authorization_header ).inject({}) { |h, (k, v)| h[k] = v; h } expect(parameters).not_to have_key("oauth_client_credential_key") expect(parameters).not_to have_key("oauth_temporary_credential_key") expect(parameters).not_to have_key("oauth_token") expect(parameters["oauth_nonce"]).to match(/^\w+$/) expect(parameters["oauth_callback"]).to eq @client.callback expect(parameters["oauth_timestamp"]).to match(/^\d+$/) expect(parameters["oauth_signature_method"]).to eq "HMAC-SHA1" expect(parameters["oauth_consumer_key"]).to eq @client.client_credential_key expect(parameters["oauth_signature"]).to match(%r{^[a-zA-Z0-9\=/\+]+$}) expect(parameters["oauth_version"]).to eq "1.0" end end it "should correctly fetch the token credentials" do # Repeat this because signatures change from test to test 10.times do request = @client.generate_token_credential_request( verifier: "473f82d3" ) expect(request.method).to eq :post expect(request.path).to eq "http://example.com/token_credentials" authorization_header = request.headers["Authorization"] parameters = ::Signet::OAuth1.parse_authorization_header( authorization_header ).inject({}) { |h, (k, v)| h[k] = v; h } expect(parameters).not_to have_key("oauth_client_credential_key") expect(parameters).not_to have_key("oauth_temporary_credential_key") expect(parameters).not_to have_key("oauth_callback") expect(parameters["oauth_nonce"]).to match(/^\w+$/) expect(parameters["oauth_timestamp"]).to match(/^\d+$/) expect(parameters["oauth_signature_method"]).to eq "HMAC-SHA1" expect(parameters["oauth_consumer_key"]).to eq @client.client_credential_key expect(parameters["oauth_token"]).to eq @client.temporary_credential_key expect(parameters["oauth_signature"]).to match(%r{^[a-zA-Z0-9\=/\+]+$}) expect(parameters["oauth_verifier"]).to eq "473f82d3" expect(parameters["oauth_version"]).to eq "1.0" end end it "should correctly fetch the protected resource" do # Repeat this because signatures change from test to test 10.times do original_request = [ "GET", "https://photos.example.net/photos?file=vacation.jpg&size=original", [["Host", "photos.example.net"]], [""] ] signed_request = @client.generate_authenticated_request( request: original_request ) expect(signed_request.method).to eq :get expect(signed_request.path).to eq "https://photos.example.net/photos" expect(signed_request.params).to eq({ "file" => "vacation.jpg", "size" => "original" }) authorization_header = signed_request.headers["Authorization"] expect(signed_request.body).to eq "" parameters = ::Signet::OAuth1.parse_authorization_header( authorization_header ).inject({}) { |h, (k, v)| h[k] = v; h } expect(parameters).not_to have_key("oauth_client_credential_key") expect(parameters).not_to have_key("oauth_temporary_credential_key") expect(parameters).not_to have_key("oauth_token_credential_key") expect(parameters).not_to have_key("oauth_callback") expect(parameters["oauth_nonce"]).to match(/^\w+$/) expect(parameters["oauth_timestamp"]).to match(/^\d+$/) expect(parameters["oauth_signature_method"]).to eq "HMAC-SHA1" expect(parameters["oauth_consumer_key"]).to eq @client.client_credential_key expect(parameters["oauth_token"]).to eq @client.token_credential_key expect(parameters["oauth_signature"]).to match(%r{^[a-zA-Z0-9\=/\+]+$}) expect(parameters["oauth_version"]).to eq "1.0" end end it "should correctly fetch the protected resource" do # Repeat this because signatures change from test to test 10.times do original_request = [ "POST", "https://photos.example.net/photos", [ ["Host", "photos.example.net"], ["Content-Type", "application/x-www-form-urlencoded; charset=utf-8"], ["Content-Length", "31"] ], ["file=vacation.jpg&size=original"] ] signed_request = @client.generate_authenticated_request( request: original_request ) expect(signed_request.method).to eq :post expect(signed_request.path).to eq "https://photos.example.net/photos" authorization_header = signed_request.headers["Authorization"] expect(signed_request.body).to eq "file=vacation.jpg&size=original" parameters = ::Signet::OAuth1.parse_authorization_header( authorization_header ).inject({}) { |h, (k, v)| h[k] = v; h } expect(parameters).not_to have_key("oauth_client_credential_key") expect(parameters).not_to have_key("oauth_temporary_credential_key") expect(parameters).not_to have_key("oauth_token_credential_key") expect(parameters).not_to have_key("oauth_callback") expect(parameters["oauth_nonce"]).to match(/^\w+$/) expect(parameters["oauth_timestamp"]).to match(/^\d+$/) expect(parameters["oauth_signature_method"]).to eq "HMAC-SHA1" expect(parameters["oauth_consumer_key"]).to eq @client.client_credential_key expect(parameters["oauth_token"]).to eq @client.token_credential_key expect(parameters["oauth_signature"]).to match(%r{^[a-zA-Z0-9\=/\+]+$}) expect(parameters["oauth_version"]).to eq "1.0" end end describe "with Faraday requests" do it "should correctly get the protected resource" do # Repeat this because signatures change from test to test 10.times do original_request = conn.build_request :get do |req| req.url( "https://photos.example.net/photos?file=vacation.jpg&size=original" ) req.headers = Faraday::Utils::Headers.new( [["Host", "photos.example.net"]] ) req.body = "" end signed_request = @client.generate_authenticated_request( request: original_request ) # Should be same request object expect(original_request["Authorization"]).to eq signed_request["Authorization"] expect(signed_request.method).to eq :get expect(signed_request.path).to eq "https://photos.example.net/photos" expect(signed_request.params).to eq ({ "file" => "vacation.jpg", "size" => "original" }) authorization_header = signed_request.headers["Authorization"] expect(signed_request.body).to eq "" parameters = ::Signet::OAuth1.parse_authorization_header( authorization_header ).inject({}) { |h, (k, v)| h[k] = v; h } expect(parameters).not_to have_key("oauth_client_credential_key") expect(parameters).not_to have_key("oauth_temporary_credential_key") expect(parameters).not_to have_key("oauth_token_credential_key") expect(parameters).not_to have_key("oauth_callback") expect(parameters["oauth_nonce"]).to match(/^\w+$/) expect(parameters["oauth_timestamp"]).to match(/^\d+$/) expect(parameters["oauth_signature_method"]).to eq "HMAC-SHA1" expect(parameters["oauth_consumer_key"]).to eq @client.client_credential_key expect(parameters["oauth_token"]).to eq @client.token_credential_key expect(parameters["oauth_signature"]).to match(%r{^[a-zA-Z0-9\=/\+]+$}) expect(parameters["oauth_version"]).to eq "1.0" end end it "should correctly post the protected resource" do # Repeat this because signatures change from test to test 10.times do original_request = conn.build_request :post do |req| req.url "https://photos.example.net/photos" req.headers = Faraday::Utils::Headers.new([ ["Host", "photos.example.net"], ["Content-Type", "application/x-www-form-urlencoded; charset=utf-8"], ["Content-Length", "31"] ]) req.body = { "size" => "original", "file" => "vacation.jpg" } end signed_request = @client.generate_authenticated_request( request: original_request ) # Should be same request object expect(original_request["Authorization"]).to eq signed_request["Authorization"] expect(signed_request.method).to eq :post expect(signed_request.path).to eq "https://photos.example.net/photos" authorization_header = signed_request.headers["Authorization"] # Can't rely on the order post parameters are encoded in. expect(signed_request.body).to include("file=vacation.jpg") expect(signed_request.body).to include("size=original") parameters = ::Signet::OAuth1.parse_authorization_header( authorization_header ).inject({}) { |h, (k, v)| h[k] = v; h } expect(parameters).not_to have_key("oauth_client_credential_key") expect(parameters).not_to have_key("oauth_temporary_credential_key") expect(parameters).not_to have_key("oauth_token_credential_key") expect(parameters).not_to have_key("oauth_callback") expect(parameters["oauth_nonce"]).to match(/^\w+$/) expect(parameters["oauth_timestamp"]).to match(/^\d+$/) expect(parameters["oauth_signature_method"]).to eq "HMAC-SHA1" expect(parameters["oauth_consumer_key"]).to eq @client.client_credential_key expect(parameters["oauth_token"]).to eq @client.token_credential_key expect(parameters["oauth_signature"]).to match(%r{^[a-zA-Z0-9\=/\+]+$}) expect(parameters["oauth_version"]).to eq "1.0" end end end end signet-0.14.0/spec/signet/oauth_1/credential_spec.rb0000644000004100000410000001241713673221365022366 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "signet/oauth_1/credential" describe Signet::OAuth1::Credential, "with a Hash for initialization" do it 'should accept "oauth_token" and "oauth_token_secret" pairs' do token = Signet::OAuth1::Credential.new( "oauth_token" => "dpf43f3p2l4k3l03", "oauth_token_secret" => "kd94hf93k423kf44" ) expect(token.key).to eq "dpf43f3p2l4k3l03" expect(token.secret).to eq "kd94hf93k423kf44" end it "should accept :oauth_token and :oauth_token_secret pairs" do token = Signet::OAuth1::Credential.new( oauth_token: "dpf43f3p2l4k3l03", oauth_token_secret: "kd94hf93k423kf44" ) expect(token.key).to eq "dpf43f3p2l4k3l03" expect(token.secret).to eq "kd94hf93k423kf44" end it 'should accept "key" and "secret" pairs' do token = Signet::OAuth1::Credential.new( "key" => "dpf43f3p2l4k3l03", "secret" => "kd94hf93k423kf44" ) expect(token.key).to eq "dpf43f3p2l4k3l03" expect(token.secret).to eq "kd94hf93k423kf44" end it "should accept :key and :secret pairs" do token = Signet::OAuth1::Credential.new( key: "dpf43f3p2l4k3l03", secret: "kd94hf93k423kf44" ) expect(token.key).to eq "dpf43f3p2l4k3l03" expect(token.secret).to eq "kd94hf93k423kf44" end it "should not complain about additional parameters" do token = Signet::OAuth1::Credential.new( "oauth_token" => "dpf43f3p2l4k3l03", "oauth_token_secret" => "kd94hf93k423kf44", "oauth_version" => "1.0" ) expect(token.key).to eq "dpf43f3p2l4k3l03" expect(token.secret).to eq "kd94hf93k423kf44" end it "should allow parameters to be specified as an implicit Hash" do class ParameterHashSet def initialize parameters @parameters = parameters.each_with_object({}) { |(k, v), h| h[k] = v; } end def to_hash @parameters end end token = Signet::OAuth1::Credential.new( ParameterHashSet.new( "oauth_token" => "dpf43f3p2l4k3l03", "oauth_token_secret" => "kd94hf93k423kf44", "oauth_version" => "1.0" ) ) expect(token.key).to eq "dpf43f3p2l4k3l03" expect(token.secret).to eq "kd94hf93k423kf44" end it "should allow parameters to be specified as an Enumerable" do token = Signet::OAuth1::Credential.new( [ %w[oauth_token dpf43f3p2l4k3l03], %w[oauth_token_secret kd94hf93k423kf44], ["oauth_version", "1.0"] ] ) expect(token.key).to eq "dpf43f3p2l4k3l03" expect(token.secret).to eq "kd94hf93k423kf44" end it "should allow parameters to be specified as an implicit Array" do class ParameterArraySet def initialize parameters @parameters = parameters end def to_ary @parameters end end token = Signet::OAuth1::Credential.new( ParameterArraySet.new( [ %w[oauth_token dpf43f3p2l4k3l03], %w[oauth_token_secret kd94hf93k423kf44], ["oauth_version", "1.0"] ] ) ) expect(token.key).to eq "dpf43f3p2l4k3l03" expect(token.secret).to eq "kd94hf93k423kf44" end it "should raise an error if key and secret are not present" do expect(lambda do Signet::OAuth1::Credential.new({}) end).to raise_error(ArgumentError) end it "should allow key and secret to be passed in as a tuple" do token = Signet::OAuth1::Credential.new( %w[dpf43f3p2l4k3l03 kd94hf93k423kf44] ) expect(token.key).to eq "dpf43f3p2l4k3l03" expect(token.secret).to eq "kd94hf93k423kf44" end it "should allow key and secret to be passed in as normal parameters" do token = Signet::OAuth1::Credential.new( "dpf43f3p2l4k3l03", "kd94hf93k423kf44" ) expect(token.key).to eq "dpf43f3p2l4k3l03" expect(token.secret).to eq "kd94hf93k423kf44" end it "should raise an error if key or secret are of the wrong type" do expect(lambda do Signet::OAuth1::Credential.new "dpf43f3p2l4k3l03", 42 end).to raise_error(TypeError) expect(lambda do Signet::OAuth1::Credential.new 42, "kd94hf93k423kf44" end).to raise_error(TypeError) end it "should raise an error if the wrong number of arguments are passed" do expect(lambda do Signet::OAuth1::Credential.new( "dpf43f3p2l4k3l03", "kd94hf93k423kf44", "something else" ) end).to raise_error(ArgumentError) end it "should convert to a Hash object" do token = Signet::OAuth1::Credential.new( "dpf43f3p2l4k3l03", "kd94hf93k423kf44" ) parameters = token.to_h expect(parameters["oauth_token"]).to eq "dpf43f3p2l4k3l03" expect(parameters["oauth_token_secret"]).to eq "kd94hf93k423kf44" end end signet-0.14.0/spec/signet/oauth_2/0000755000004100000410000000000013673221365016711 5ustar www-datawww-datasignet-0.14.0/spec/signet/oauth_2/client_spec.rb0000644000004100000410000013346413673221365021541 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "signet/oauth_2/client" require "openssl" require "jwt" require "date" conn = Faraday.default_connection def build_json_response payload [200, { "Content-Type" => "application/json; charset=utf-8" }, MultiJson.dump(payload)] end def build_form_encoded_response payload [200, { "Content-Type" => "application/json; charset=utf-8" }, Addressable::URI.form_encode(payload)] end describe Signet::OAuth2::Client, "unconfigured" do before do @client = Signet::OAuth2::Client.new end it "should allow additional paraemters to be set." do @client.additional_parameters["type"] = "web_server" expect(@client.additional_parameters).to eq({ "type" => "web_server" }) end it "should raise an error if a bogus scope is provided" do expect(lambda do @client = Signet::OAuth2::Client.new scope: :bogus end).to raise_error(TypeError) end it "should raise an error if a scope array is provided with spaces" do expect(lambda do @client = Signet::OAuth2::Client.new( scope: ["legit", "bogus bogus"] ) end).to raise_error(ArgumentError) end it "should allow the scope to be set to a String" do @client.scope = "legit" expect(@client.scope).to eq ["legit"] @client.scope = "legit alsolegit" expect(@client.scope).to eq ["legit", "alsolegit"] end it "should allow the scope to be set to an Array" do @client.scope = ["legit"] expect(@client.scope).to eq ["legit"] @client.scope = ["legit", "alsolegit"] expect(@client.scope).to eq ["legit", "alsolegit"] end it "should raise an error if a bogus redirect URI is provided" do expect(lambda do @client = Signet::OAuth2::Client.new redirect_uri: :bogus end).to raise_error(TypeError) end it "should raise an error if a relative redirect URI is provided" do expect(lambda do @client = Signet::OAuth2::Client.new redirect_uri: "/relative/path" end).to raise_error(ArgumentError) end it 'should allow "postmessage" as a redirect URI (Google hack)' do @client.authorization_uri = "https://example.com/authorize" @client.client_id = "s6BhdRkqt3" @client.redirect_uri = "postmessage" expect(@client.authorization_uri.query_values["redirect_uri"]).to eq "postmessage" end it "should allow oob values as a redirect URI (for installed apps)" do @client.authorization_uri = "https://example.com/authorize" @client.client_id = "s6BhdRkqt3" @client.redirect_uri = "urn:ietf:wg:oauth:2.0:oob" expect(@client.authorization_uri.query_values["redirect_uri"]).to eq "urn:ietf:wg:oauth:2.0:oob" @client.redirect_uri = "oob" expect(@client.authorization_uri.query_values["redirect_uri"]).to eq "oob" end it "should have no authorization_uri" do expect(@client.authorization_uri).to eq nil end it "should allow the authorization_uri to be set to a String" do @client.authorization_uri = "https://example.com/authorize" @client.client_id = "s6BhdRkqt3" @client.redirect_uri = "https://example.client.com/callback" expect(@client.authorization_uri.to_s).to include( "https://example.com/authorize" ) expect(@client.authorization_uri.query_values["client_id"]).to eq "s6BhdRkqt3" expect(@client.authorization_uri.query_values["redirect_uri"]).to eq( "https://example.client.com/callback" ) end it "should allow the authorization_uri to be set to a Hash" do @client.authorization_uri = { scheme: "https", host: "example.com", path: "/authorize" } @client.client_id = "s6BhdRkqt3" @client.redirect_uri = "https://example.client.com/callback" expect(@client.authorization_uri.to_s).to include( "https://example.com/authorize" ) expect(@client.authorization_uri.query_values["client_id"]).to eq "s6BhdRkqt3" expect(@client.authorization_uri.query_values["redirect_uri"]).to eq( "https://example.client.com/callback" ) end it "should allow the authorization_uri to be set to a URI" do @client.authorization_uri = Addressable::URI.parse "https://example.com/authorize" @client.client_id = "s6BhdRkqt3" @client.redirect_uri = Addressable::URI.parse "https://example.client.com/callback" expect(@client.authorization_uri.to_s).to include( "https://example.com/authorize" ) expect(@client.authorization_uri.query_values["client_id"]).to eq "s6BhdRkqt3" expect(@client.authorization_uri.query_values["redirect_uri"]).to eq( "https://example.client.com/callback" ) end it "should require a redirect URI when getting the authorization_uri" do @client.authorization_uri = Addressable::URI.parse "https://example.com/authorize" @client.client_id = "s6BhdRkqt3" expect(lambda do @client.authorization_uri end).to raise_error(ArgumentError) end it "should require a client ID when getting the authorization_uri" do @client.authorization_uri = Addressable::URI.parse "https://example.com/authorize" @client.redirect_uri = Addressable::URI.parse "https://example.client.com/callback" expect(lambda do @client.authorization_uri end).to raise_error(ArgumentError) end it "should have no token_credential_uri" do expect(@client.token_credential_uri).to eq nil end it "should allow the token_credential_uri to be set to a String" do @client.token_credential_uri = "https://example.com/token" expect(@client.token_credential_uri.to_s).to eq "https://example.com/token" end it "should allow the token_credential_uri to be set to a Hash" do @client.token_credential_uri = { scheme: "https", host: "example.com", path: "/token" } expect(@client.token_credential_uri.to_s).to eq "https://example.com/token" end it "should allow the token_credential_uri to be set to a URI" do @client.token_credential_uri = Addressable::URI.parse "https://example.com/token" expect(@client.token_credential_uri.to_s).to eq "https://example.com/token" end end describe Signet::OAuth2::Client, "configured for assertions profile" do describe "when using RSA keys" do before do @key = OpenSSL::PKey::RSA.new 2048 @client = Signet::OAuth2::Client.new( token_credential_uri: "https://oauth2.googleapis.com/token", scope: "https://www.googleapis.com/auth/userinfo.profile", issuer: "app@example.com", audience: "https://oauth2.googleapis.com/token", signing_key: @key ) end it "should generate valid JWTs" do jwt = @client.to_jwt expect(jwt).not_to be_nil claim, header = JWT.decode jwt, @key.public_key, true, algorithm: "RS256" expect(claim["iss"]).to eq "app@example.com" expect(claim["scope"]).to eq "https://www.googleapis.com/auth/userinfo.profile" expect(claim["aud"]).to eq "https://oauth2.googleapis.com/token" end it "should generate valid JWTs for impersonation" do @client.principal = "user@example.com" jwt = @client.to_jwt expect(jwt).not_to be_nil claim, header = JWT.decode jwt, @key.public_key, true, algorithm: "RS256" expect(claim["iss"]).to eq "app@example.com" expect(claim["prn"]).to eq "user@example.com" expect(claim["scope"]).to eq "https://www.googleapis.com/auth/userinfo.profile" expect(claim["aud"]).to eq "https://oauth2.googleapis.com/token" end it "should generate valid JWTs for impersonation using deprecated person attribute" do @client.person = "user@example.com" jwt = @client.to_jwt expect(jwt).not_to be_nil claim, header = JWT.decode jwt, @key.public_key, true, algorithm: "RS256" expect(claim["iss"]).to eq "app@example.com" expect(claim["prn"]).to eq "user@example.com" expect(claim["scope"]).to eq "https://www.googleapis.com/auth/userinfo.profile" expect(claim["aud"]).to eq "https://oauth2.googleapis.com/token" end it "should generate valid JWTs for impersonation using the sub attribute" do @client.sub = "user@example.com" jwt = @client.to_jwt expect(jwt).not_to be_nil claim, header = JWT.decode jwt, @key.public_key, true, algorithm: "RS256" expect(claim["iss"]).to eq "app@example.com" expect(claim["sub"]).to eq "user@example.com" expect(claim["scope"]).to eq "https://www.googleapis.com/auth/userinfo.profile" expect(claim["aud"]).to eq "https://oauth2.googleapis.com/token" end it "should generate a JSON representation of the client" do @client.principal = "user@example.com" json = @client.to_json expect(json).not_to be_nil deserialized = MultiJson.load json expect(deserialized["token_credential_uri"]).to eq "https://oauth2.googleapis.com/token" expect(deserialized["scope"]).to eq ["https://www.googleapis.com/auth/userinfo.profile"] expect(deserialized["issuer"]).to eq "app@example.com" expect(deserialized["audience"]).to eq "https://oauth2.googleapis.com/token" expect(deserialized["signing_key"]).to eq @key.to_s end it "should send valid access token request" do stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do |env| params = Addressable::URI.form_unencode env[:body] claim, header = JWT.decode params.assoc("assertion").last, @key.public_key, true, algorithm: "RS256" expect(params.assoc("grant_type")).to eq ["grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"] build_json_response( "access_token" => "1/abcdef1234567890", "token_type" => "Bearer", "expires_in" => 3600 ) end end connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token! connection: connection expect(@client.access_token).to eq "1/abcdef1234567890" stubs.verify_stubbed_calls end end describe "when using shared secrets" do before do @key = "my secret key" @client = Signet::OAuth2::Client.new( token_credential_uri: "https://oauth2.googleapis.com/token", scope: "https://www.googleapis.com/auth/userinfo.profile", issuer: "app@example.com", audience: "https://oauth2.googleapis.com/token", signing_key: @key ) end it "should generate valid JWTs" do jwt = @client.to_jwt expect(jwt).not_to be_nil claim, header = JWT.decode jwt, @key, true, algorithm: "HS256" expect(claim["iss"]).to eq "app@example.com" expect(claim["scope"]).to eq "https://www.googleapis.com/auth/userinfo.profile" expect(claim["aud"]).to eq "https://oauth2.googleapis.com/token" end end end describe Signet::OAuth2::Client, "configured for Google userinfo API" do before do @client = Signet::OAuth2::Client.new( authorization_uri: "https://accounts.google.com/o/oauth2/auth", token_credential_uri: "https://oauth2.googleapis.com/token", scope: "https://www.googleapis.com/auth/userinfo.profile" ) end it "should not have a grant type by default" do expect(@client.grant_type).to eq nil end it "should use the authorization_code grant type if given code" do @client.code = "00000" @client.redirect_uri = "http://www.example.com/" expect(@client.grant_type).to eq "authorization_code" end it "should use the refresh_token grant type if given refresh token" do @client.refresh_token = "54321" expect(@client.grant_type).to eq "refresh_token" end it "should use the password grant type if given username and password" do @client.username = "johndoe" @client.password = "incognito" expect(@client.grant_type).to eq "password" end it "should allow the grant type to be set manually" do @client.grant_type = "authorization_code" expect(@client.grant_type).to eq "authorization_code" @client.grant_type = "refresh_token" expect(@client.grant_type).to eq "refresh_token" @client.grant_type = "password" expect(@client.grant_type).to eq "password" end it "should allow the grant type to be set to an extension" do @client.grant_type = "urn:ietf:params:oauth:grant-type:saml2-bearer" @client.extension_parameters["assertion"] = "PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU" expect(@client.grant_type).to eq Addressable::URI.parse("urn:ietf:params:oauth:grant-type:saml2-bearer") expect(@client.extension_parameters).to eq ({ "assertion" => "PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU" }) end it "should raise an error if extension parameters are bogus" do expect(lambda do @client.extension_parameters = :bogus end).to raise_error(TypeError) end it "should include extension parameters in token request" do @client.grant_type = "urn:ietf:params:oauth:grant-type:saml2-bearer" @client.extension_parameters["assertion"] = "PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU" request = @client.generate_access_token_request expect(request).to include("assertion" => "PEFzc2VydGlvbiBJc3N1ZUluc3RhbnQ9IjIwMTEtMDU") end it "should include the scope in token request" do @client.scope = ["https://www.googleapis.com/auth/userinfo.profile"] request = @client.generate_access_token_request use_configured_scope: true expect(request).to include("scope" => ["https://www.googleapis.com/auth/userinfo.profile"]) end it "should allow the token to be updated" do issued_at = Time.now @client.update_token!( :access_token => "12345", refresh_token: "54321", :expires_in => 3600, :issued_at => issued_at ) expect(@client.access_token).to eq "12345" expect(@client.refresh_token).to eq "54321" expect(@client.expires_in).to eq 3600 expect(@client.issued_at).to eq issued_at expect(@client).to_not be_expired end it "should handle expires as equivalent to expires_in" do issued_at = Time.now @client.update_token!( :access_token => "12345", refresh_token: "54321", :expires => 600, :issued_at => issued_at ) expect(@client.expires_in).to eq 600 end it "should allow the token to be updated without an expiration" do @client.update_token!( :access_token => "12345", refresh_token: "54321" ) expect(@client.access_token).to eq "12345" expect(@client.refresh_token).to eq "54321" expect(@client.expires_in).to eq nil expect(@client.issued_at).to eq nil expect(@client).to_not be_expired end it "should allow the token expiration to be cleared" do issued_at = Time.now @client.update_token!( :access_token => "12345", refresh_token: "54321", :expires_in => 3600, :issued_at => issued_at ) @client.expires_in = nil @client.issued_at = nil expect(@client).to_not be_expired end it "should allow the expires_at time to be updated" do expires_at = Time.now @client.update_token!( expires_at: expires_at.to_i, expires_in: nil ) expect(@client.expires_at).to be_within(1).of(expires_at) expect(@client).to be_expired end it "should calculate the expires_at from issued_at when issued_at is set" do expires_in = 3600 issued_at = Time.now - expires_in @client.update_token!( :issued_at => issued_at, expires_in: expires_in ) expect(@client.expires_at).to eq issued_at + expires_in expect(@client).to be_expired end it "should calculate expires_at from Time.now when issed_at is NOT set" do expires_in = 3600 expires_at = Time.now + expires_in @client.update_token! expires_in: expires_in expect(@client.expires_at).to be_within(1).of(expires_at) expect(@client).to_not be_expired end # This test is to document the way that expires_in has always been used: # If expires_in is set on the client, it always resets the issued_at time # to Time.now it "sets issued_at to Time.now when expires_in is not set through update_token!" do one_hour = 3600 issued_at = Time.now - (2 * one_hour) current_time = Time.now @client.issued_at = issued_at @client.expires_in = one_hour expect(@client.issued_at).to_not eq issued_at expect(@client.issued_at).to be_within(1).of(current_time) expect(@client).to_not be_expired end it "should allow setting expires_at manually" do expires_at = Time.now + 100 @client.expires_at = expires_at.to_i expect(@client.expires_at).to be_within(1).of(expires_at) expect(@client).to_not be_expired end it "should normalize values of expires_at to instances of time" do time_formats = [DateTime.new, "12:00", 100, Time.new] normalized_time_formats = [] time_formats.each do |time| @client.expires_at = time normalized_time_formats << @client.expires_at end normalized_time_formats.each do |time| expect(time).to be_an_instance_of(Time) end end it "should set expires_in when expires_at is set" do issued_at = Time.now expires_at = Time.now + 100 @client.expires_at = expires_at.to_i @client.issued_at = issued_at expect(@client.expires_in).to be_within(1).of (expires_at - issued_at).to_i @client.expires_at = nil expect(@client.expires_in).to be_nil end it "should set expires_in to nil when expires_at is set to nil" do @client.expires_at = nil expect(@client.expires_in).to be_nil end it "should set expires_at when expires_in is set" do expires_in = 100 @client.expires_in = expires_in expect(@client.expires_at).to eq (@client.issued_at + expires_in) @client.expires_in = nil expect(@client.expires_at).to be_nil end it "should set expires_at to nil when expires_in is set to nil" do @client.expires_in = nil expect(@client.expires_at).to be_nil end it "should indicate the token is not expired if expired_at nil" do @client.expires_at = nil expect(@client.expires_within?(60)).to be false expect(@client.expired?).to be false end it "should indicate the token is not expiring when expiry beyond window" do @client.expires_at = Time.now + 100 expect(@client.expires_within?(60)).to be false end it "should indicate the token is expiring soon when expiry within window" do @client.expires_at = Time.now + 30 expect(@client.expires_within?(60)).to be true end it "should raise an error if the authorization endpoint is not secure" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.redirect_uri = "http://www.example.com/" @client.authorization_uri = "http://accounts.google.com/o/oauth2/auth" expect(lambda do @client.authorization_uri end).to raise_error(Signet::UnsafeOperationError) end it "should raise an error if token credential URI is missing" do @client.token_credential_uri = nil expect(lambda do @client.fetch_access_token! end).to raise_error(ArgumentError) end it "should raise an error if unauthorized" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do [401, {}, "User authorization failed or something."] end end expect(lambda do connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) end).to raise_error(Signet::AuthorizationError) stubs.verify_stubbed_calls end it "should raise a remote server error if the server gives a 5xx status" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do [509, {}, "Rate limit hit or something."] end end expect(lambda do connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) end).to raise_error(Signet::RemoteServerError) stubs.verify_stubbed_calls end it "should raise an unexpected error if the token server gives an unexpected status" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do [309, {}, "Rate limit hit or something."] end end expect(lambda do connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) end).to raise_error(Signet::UnexpectedStatusError) stubs.verify_stubbed_calls end it "should correctly fetch an access token" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.code = "00000" @client.redirect_uri = "https://www.example.com/" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do build_json_response( "access_token" => "12345", "refresh_token" => "54321", "expires_in" => "3600" ) end end connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) expect(@client.access_token).to eq "12345" expect(@client.refresh_token).to eq "54321" expect(@client.expires_in).to eq 3600 stubs.verify_stubbed_calls end it "should correctly fetch an access token with a password" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.username = "johndoe" @client.password = "incognito" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do build_json_response( "access_token" => "12345", "refresh_token" => "54321", "expires_in" => "3600" ) end end connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) expect(@client.access_token).to eq "12345" expect(@client.refresh_token).to eq "54321" expect(@client.expires_in).to eq 3600 stubs.verify_stubbed_calls end it "should correctly refresh an access token" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.refresh_token = "54321" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do build_json_response( "access_token" => "12345", "refresh_token" => "54321", "expires_in" => "3600" ) end end connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) expect(@client.access_token).to eq "12345" expect(@client.refresh_token).to eq "54321" expect(@client.expires_in).to eq 3600 stubs.verify_stubbed_calls end it "should detect unintential grant type of none" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.redirect_uri = "https://www.example.com/" expect(lambda do @client.fetch_access_token! end).to raise_error(ArgumentError) end it "should correctly fetch protected resources" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.access_token = "12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.get "/oauth2/v1/userinfo?alt=json" do [200, {}, <<~JSON] { "id": "116452824309856782163", "name": "Bob Aman", "given_name": "Bob", "family_name": "Aman", "link": "https://plus.google.com/116452824309856782163" } JSON end end connection = Faraday.new url: "https://www.googleapis.com" do |builder| builder.adapter :test, stubs end response = @client.fetch_protected_resource( connection: connection, uri: "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" ) expect(response.status).to eq 200 expect(response.body).to eq <<~JSON { "id": "116452824309856782163", "name": "Bob Aman", "given_name": "Bob", "family_name": "Aman", "link": "https://plus.google.com/116452824309856782163" } JSON stubs.verify_stubbed_calls end it "should correctly send the realm in the Authorization header" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.access_token = "12345" connection = Faraday.new url: "https://www.googleapis.com" do |builder| builder.adapter :test end request = @client.generate_authenticated_request( connection: connection, realm: "Example", request: conn.build_request(:get) do |req| req.url "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" end ) expect(request.headers["Authorization"]).to eq 'Bearer 12345, realm="Example"' end it "should correctly send the realm in the Authorization header" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.access_token = "12345" connection = Faraday.new url: "https://www.googleapis.com" do |builder| builder.adapter :test end request = @client.generate_authenticated_request( connection: connection, realm: "Example", request: [ "GET", "https://www.googleapis.com/oauth2/v1/userinfo?alt=json", {}, [""] ] ) expect(request.headers["Authorization"]).to eq 'Bearer 12345, realm="Example"' end it "should not raise an error if a request is " \ "provided without a connection" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.access_token = "12345" request = @client.generate_authenticated_request( realm: "Example", request: conn.build_request(:get) do |req| req.url "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" end ) end it "should raise an error if not enough information " \ "is supplied to create a request" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.access_token = "12345" expect(lambda do @client.generate_authenticated_request( realm: "Example", method: "POST" ) end).to raise_error(ArgumentError) end it "should raise an error if the client does not have an access token" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" expect(lambda do @client.fetch_protected_resource end).to raise_error(ArgumentError) end it "should not raise an error if the API server gives an error status" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.access_token = "12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.get "/oauth2/v1/userinfo?alt=json" do [509, {}, "Rate limit hit or something."] end end connection = Faraday.new url: "https://www.googleapis.com" do |builder| builder.adapter :test, stubs end response = @client.fetch_protected_resource( connection: connection, uri: "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" ) expect(response.status).to eq 509 expect(response.body).to eq "Rate limit hit or something." stubs.verify_stubbed_calls end it "should only raise an error if the API server " \ "gives an authorization failed status" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" @client.access_token = "12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.get "/oauth2/v1/userinfo?alt=json" do [401, {}, "User authorization failed or something."] end end expect(lambda do connection = Faraday.new( url: "https://www.googleapis.com" ) do |builder| builder.adapter :test, stubs end @client.fetch_protected_resource( connection: connection, uri: "https://www.googleapis.com/oauth2/v1/userinfo?alt=json" ) end).to raise_error(Signet::AuthorizationError) stubs.verify_stubbed_calls end it "should correctly handle an ID token" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do build_json_response( "access_token" => "12345", "refresh_token" => "54321", "expires_in" => "3600", "id_token" => ( "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl9oYXNoIjoidGdoRD" \ "lKN244VjBOMnZjdzZlTWlqZyIsImF1ZCI6ImNsaWVudC0xMjM0NSIsImlkIjoiM" \ "TIzNDUiLCJpYXQiOjEzMjA2NzA5NzgsImV4cCI6MTMyMDY3NDg3OCwiY2lkIjoi" \ "Y2xpZW50LTEyMzQ1IiwiaXNzIjoiZXhhbXBsZS5jb20ifQ.tsF3srlBaAh6pV3U" \ "wfRrHSA3-jwnvOw6MMsQ6sO4kjc" ) ) end end connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) expect(@client.access_token).to eq "12345" expect(@client.refresh_token).to eq "54321" expect(@client.decoded_id_token(nil, verify_expiration: false)).to eq ({ "token_hash" => "tghD9J7n8V0N2vcw6eMijg", "id" => "12345", "aud" => "client-12345", "iat" => 1_320_670_978, "exp" => 1_320_674_878, "cid" => "client-12345", "iss" => "example.com" }) expect(@client.expires_in).to eq 3600 stubs.verify_stubbed_calls end it "should correctly handle an ID token with `aud` array" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do build_json_response( "access_token" => "12345", "refresh_token" => "54321", "expires_in" => "3600", "id_token" => ( "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl9oYXNoIjoidGdoRD" \ "lKN244VjBOMnZjdzZlTWlqZyIsImF1ZCI6WyJjbGllbnQtMTIzNDUiXSwiaWQiO" \ "iIxMjM0NSIsImlhdCI6MTMyMDY3MDk3OCwiZXhwIjoxMzIwNjc0ODc4LCJjaWQi" \ "OiJjbGllbnQtMTIzNDUiLCJpc3MiOiJleGFtcGxlLmNvbSJ9.rSaY-M9YlB4pcU" \ "uNf21FeEtOM9pBGr_a7xe9fZrpWWU" ) ) end end connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) expect(@client.access_token).to eq "12345" expect(@client.refresh_token).to eq "54321" expect(@client.decoded_id_token(nil, verify_expiration: false)).to eq ({ "token_hash" => "tghD9J7n8V0N2vcw6eMijg", "id" => "12345", "aud" => ["client-12345"], "iat" => 1_320_670_978, "exp" => 1_320_674_878, "cid" => "client-12345", "iss" => "example.com" }) expect(@client.expires_in).to eq 3600 stubs.verify_stubbed_calls end it "should raise an error decoding an ID token if " \ "audience does not match client ID" do @client.client_id = "client-54321" @client.client_secret = "secret-12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do build_json_response( "access_token" => "12345", "refresh_token" => "54321", "expires_in" => "3600", "id_token" => ( "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl9oYXNoIjoidGdoRD" \ "lKN244VjBOMnZjdzZlTWlqZyIsImF1ZCI6ImNsaWVudC0xMjM0NSIsImlkIjoiM" \ "TIzNDUiLCJpYXQiOjEzMjA2NzA5NzgsImV4cCI6MTMyMDY3NDg3OCwiY2lkIjoi" \ "Y2xpZW50LTEyMzQ1IiwiaXNzIjoiZXhhbXBsZS5jb20ifQ.tsF3srlBaAh6pV3U" \ "wfRrHSA3-jwnvOw6MMsQ6sO4kjc" ) ) end end connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) expect(@client.access_token).to eq "12345" expect(@client.refresh_token).to eq "54321" expect(@client.expires_in).to eq 3600 expect(lambda do @client.decoded_id_token nil, verify_expiration: false end).to raise_error(Signet::UnsafeOperationError) stubs.verify_stubbed_calls end it "should raise an error decoding an ID token if " \ "audience is missing" do @client.client_id = "client-12345" @client.client_secret = "secret-12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do build_json_response( "access_token" => "12345", "refresh_token" => "54321", "expires_in" => "3600", "id_token" => ( "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl9oYXNoIjoidGdoRD" \ "lKN244VjBOMnZjdzZlTWlqZyIsImlkIjoiMTIzNDUiLCJpYXQiOjEzMjA2NzA5N" \ "zgsImV4cCI6MTMyMDY3NDg3OCwiY2lkIjoiY2xpZW50LTEyMzQ1IiwiaXNzIjoi" \ "ZXhhbXBsZS5jb20ifQ.7qj85CKbQyVdDe5y2ScdJAZNkEeKMPW9LIonLxG1vu8" ) ) end end connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) expect(@client.access_token).to eq "12345" expect(@client.refresh_token).to eq "54321" expect(@client.expires_in).to eq 3600 expect(lambda do @client.decoded_id_token nil, verify_expiration: false end).to raise_error(Signet::UnsafeOperationError) stubs.verify_stubbed_calls end it "should raise an error if the id token cannot be verified" do pending "Need to set test data" @client.client_id = "client-12345" @client.client_secret = "secret-12345" stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do build_json_response( "access_token" => "12345", "refresh_token" => "54321", "expires_in" => "3600", "id_token" => ( "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY2NvdW50cy5nb29nbGUuY29tIiwiY" \ "XVkIjoiMTA2MDM1Nzg5MTY4OC5hcHBzLmdvb2dsZXVzZXJjb250ZW50LmNvbSI" \ "sImNpZCI6IjEwNjAzNTc4OTE2ODguYXBwcy5nb29nbGV1c2VyY29udGVudC5jb" \ "20iLCJpZCI6IjExNjQ1MjgyNDMwOTg1Njc4MjE2MyIsInRva2VuX2hhc2giOiJ" \ "0Z2hEOUo4bjhWME4ydmN3NmVNaWpnIiwiaWF0IjoxMzIwNjcwOTc4LCJleHAiO" \ "jEzMjA2NzQ4Nzh9.D8x_wirkxDElqKdJBcsIws3Ogesk38okz6MN7zqC7nEAA7" \ "wcy1PxsROY1fmBvXSer0IQesAqOW-rPOCNReSn-eY8d53ph1x2HAF-AzEi3GOl" \ "6hFycH8wj7Su6JqqyEbIVLxE7q7DkAZGaMPkxbTHs1EhSd5_oaKQ6O4xO3ZnnT4" ) ) end end connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token!( connection: connection ) expect(@client.access_token).to eq "12345" expect(@client.refresh_token).to eq "54321" expect(@client.expires_in).to eq 3600 expect(lambda do pubkey = OpenSSL::PKey::RSA.new <<~PUBKEY -----BEGIN PUBLIC KEY----- MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCaY7425h964bjaoLeUm SlZ8sK7VtVk9zHbGmZh2ygGYwfuUf2bmMye2Ofv99yDE/rd4loVIAcu7RVvDRgHq 3/CZTnIrSvHsiJQsHBNa3d+F1ihPfzURzf1M5k7CFReBj2SBXhDXd57oRfBQj12w CVhhwP6kGTAWuoppbIIIBfNF2lE/Nvm7lVVYQqL9xOrP/AQ4xRbpQlB8Ll9sO9Or SvbWhCDa/LMOWxHdmrcJi6XoSg1vnOyCoKbyAoauTt/XqdkHbkDdQ6HFbJieu9il LDZZNliPhfENuKeC2MCGVXTEu8Cqhy1w6e4axavLlXoYf4laJIZ/e7au8SqDbY0B xwIDAQAB -----END PUBLIC KEY----- PUBKEY @client.decoded_id_token pubkey end).to raise_error(JWT::ExpiredSignature) stubs.verify_stubbed_calls end end describe Signet::OAuth2::Client, "authorization_uri" do before do @client = Signet::OAuth2::Client.new( client_id: "s6BhdRkqt3", redirect_uri: "https://example.client.com/callback", authorization_uri: "https://example.com/authorize" ) end it "should set access_type to offline by default" do expect(@client.authorization_uri.query_values["access_type"]).to eq "offline" end it "should set response_type to code by default" do expect(@client.authorization_uri.query_values["response_type"]).to eq "code" end it "should raise an error when setting both prompt and approval_prompt" do expect(lambda do @client.authorization_uri approval_prompt: "force", prompt: "consent" end).to raise_error(ArgumentError) end end describe Signet::OAuth2::Client, "configured with custom parameters" do before do @client = Signet::OAuth2::Client.new( client_id: "s6BhdRkqt3", redirect_uri: "https://example.client.com/callback", authorization_uri: "https://example.com/authorize", token_credential_uri: "https://example.com/token", additional_parameters: { "type" => "web_server" } ) end # Normalizing to symbols - good test case example here for changes to normalized input. # Also tests Addressable's output. # Note: The only changes made here are to testing the **INTERNAL** representation of options. it "should allow custom parameters to be set on init" do expect(@client.additional_parameters).to eq({ type: "web_server" }) end it "should allow custom parameters to be updated" do @client.update! additional_parameters: { type: "new_type" } expect(@client.additional_parameters).to eq ({ type: "new_type" }) end it "should use custom parameters when generating authorization_uri" do expect(@client.authorization_uri.query_values).to eq ({ "access_type" => "offline", "client_id" => "s6BhdRkqt3", "redirect_uri" => "https://example.client.com/callback", "response_type" => "code", "type" => "web_server" }) end it "should merge new authorization_uri custom parameters" do expect(@client.authorization_uri(additional_parameters: { "type" => "new_type", "new_param" => "new_val" }).query_values).to eql({ "access_type" => "offline", "client_id" => "s6BhdRkqt3", "new_param" => "new_val", "response_type" => "code", "redirect_uri" => "https://example.client.com/callback", "type" => "new_type" }) end it "should merge new generate_access_token_request custom parameters" do @client.update! code: "12345" params = @client.generate_access_token_request additional_parameters: { "type" => "new_type", "new_param" => "new_val" } expect(params).to include("type" => "new_type") expect(params).to include("new_param" => "new_val") end end describe Signet::OAuth2::Client, "configured with custom parameters" do before do @client = Signet::OAuth2::Client.new( "client_id" => "s6BhdRkqt3", "redirect_uri" => "https://example.client.com/callback", "authorization_uri" => "https://example.com/authorize", "token_credential_uri" => "https://example.com/token", "additional_parameters" => { "type" => "web_server" } ) end # Normalizing to symbols - good test case example here for changes to normalized input. # Also tests Addressable's output. # Note: The only changes made here are to testing the **INTERNAL** representation of options. it "should allow custom parameters to be set on init" do expect(@client.additional_parameters).to eq ({ type: "web_server" }) end it "should allow custom parameters to be updated" do @client.update! additional_parameters: { "type" => "new_type" } expect(@client.additional_parameters).to eql ({ type: "new_type" }) end it "should use custom parameters when generating authorization_uri" do expect(@client.authorization_uri.query_values).to eq ({ "access_type" => "offline", "client_id" => "s6BhdRkqt3", "redirect_uri" => "https://example.client.com/callback", "response_type" => "code", "type" => "web_server" }) end it "should have the correct authorization_uri" do expect(@client.authorization_uri.host).to eq "example.com" expect(@client.authorization_uri.path).to eq "/authorize" end it "should merge new authorization_uri custom parameters" do expect(@client.authorization_uri(additional_parameters: { "type" => "new_type", "new_param" => "new_val" }).query_values).to eq ({ "access_type" => "offline", "client_id" => "s6BhdRkqt3", "new_param" => "new_val", "response_type" => "code", "redirect_uri" => "https://example.client.com/callback", "type" => "new_type" }) end it "should not have access_type parameter in authorization_uri when we set it to nil in client" do @client.update! access_type: nil expect(@client.authorization_uri.query_values).to eq ({ "client_id" => "s6BhdRkqt3", "response_type" => "code", "redirect_uri" => "https://example.client.com/callback" }) end it "should use new access_type parameter as default for authorization_uri" do @client.update! access_type: :online expect(@client.authorization_uri.query_values).to eq ({ "access_type" => "online", "client_id" => "s6BhdRkqt3", "response_type" => "code", "redirect_uri" => "https://example.client.com/callback" }) end it "should merge new generate_access_token_request custom parameters" do @client.update! code: "12345" params = @client.generate_access_token_request additional_parameters: { "type" => "new_type", "new_param" => "new_val" } expect(params).to include("type" => "new_type") expect(params).to include("new_param" => "new_val") end end describe Signet::OAuth2::Client, "configured with custom parameters a la JSON.load(credentials_file)" do before do @client = Signet::OAuth2::Client.new( "client_id" => "s6BhdRkqt3", "redirect_uri" => "https://example.client.com/callback", "authorization_uri" => { "scheme" => "https", "user" => nil, "password" => nil, "host" => "accounts.google.com", "port" => nil, "path" => "/o/oauth2/auth", "query" => nil, "fragment" => nil }, "token_credential_uri" => "https://example.com/token", "additional_parameters" => { "type" => "web_server" } ) end it "should allow custom parameters to be set on init" do expect(@client.additional_parameters).to eq ({ type: "web_server" }) end it "should allow custom parameters to be updated" do @client.update! additional_parameters: { "type" => "new_type" } expect(@client.additional_parameters).to eql ({ type: "new_type" }) end it "should have correct authorization_uri hash options" do expect(@client.authorization_uri.host).to eq "accounts.google.com" expect(@client.authorization_uri.path).to eq "/o/oauth2/auth" end it "should use custom parameters when generating authorization_uri" do expect(@client.authorization_uri.query_values).to eq ({ "access_type" => "offline", "client_id" => "s6BhdRkqt3", "redirect_uri" => "https://example.client.com/callback", "response_type" => "code", "type" => "web_server" }) end # , "path" => "/o/oauth2/oauth", "host" => "accounts.google.com" it "should merge new authorization_uri custom parameters" do expect(@client.authorization_uri(additional_parameters: { "type" => "new_type", "new_param" => "new_val" }).query_values).to eq ({ "access_type" => "offline", "client_id" => "s6BhdRkqt3", "new_param" => "new_val", "response_type" => "code", "redirect_uri" => "https://example.client.com/callback", "type" => "new_type" }) end it "should merge new generate_access_token_request custom parameters" do @client.update! code: "12345" params = @client.generate_access_token_request additional_parameters: { "type" => "new_type", "new_param" => "new_val" } expect(params).to include("type" => "new_type") expect(params).to include("new_param" => "new_val") end end describe Signet::OAuth2::Client, "configured for id tokens" do before do @key = OpenSSL::PKey::RSA.new 2048 @client = Signet::OAuth2::Client.new( token_credential_uri: "https://oauth2.googleapis.com/token", target_audience: "https://api.example.com", issuer: "app@example.com", audience: "https://hello.googleapis.com", signing_key: @key ) end it "should set target_audience" do expect(@client.target_audience).to eq "https://api.example.com" end it "should send a valid id token request" do stubs = Faraday::Adapter::Test::Stubs.new do |stub| stub.post "/token" do |env| params = Addressable::URI.form_unencode env[:body] claim, header = JWT.decode params.assoc("assertion").last, @key.public_key, true, algorithm: "RS256" expect(params.assoc("grant_type")).to eq ["grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer"] expect(claim["target_audience"]).to eq "https://api.example.com" expect(claim["iss"]).to eq "app@example.com" expect(claim["aud"]).to eq "https://hello.googleapis.com" build_json_response( "id_token" => "12345id", "refresh_token" => "54321refresh", "expires_in" => "3600" ) end end connection = Faraday.new url: "https://www.google.com" do |builder| builder.adapter :test, stubs end @client.fetch_access_token! connection: connection expect(@client.id_token).to eq "12345id" end end signet-0.14.0/spec/signet/oauth_1_spec.rb0000644000004100000410000011501713673221365020254 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "signet/oauth_1" require "signet/oauth_1/client" require "signet/oauth_1/credential" describe Signet::OAuth1 do it "should correctly normalize parameters" do parameters = [ %w[a 1], ["c", "hi there"], %w[f 25], %w[f 50], %w[f a], %w[z p], %w[z t] ] expect(Signet::OAuth1.normalize_parameters(parameters)).to eq( "a=1&c=hi%20there&f=25&f=50&f=a&z=p&z=t" ) end it "should correctly normalize parameters" do parameters = [ ["b5", "=%3D"], %w[a3 a], ["c@", ""], ["a2", "r b"], %w[oauth_consumer_key 9djdj82h48djs9d2], %w[oauth_token kkk9d7dh3k39sjv7], ["oauth_signature_method", "HMAC-SHA1"], %w[oauth_timestamp 137131201], %w[oauth_nonce 7d8f3e4a], ["c2", ""], ["a3", "2 q"] ] expect(Signet::OAuth1.normalize_parameters(parameters)).to eq( "a2=r%20b&a3=2%20q&a3=a&b5=%3D%253D&c%40=&c2=&oauth_consumer_key=9dj" \ "dj82h48djs9d2&oauth_nonce=7d8f3e4a&oauth_signature_method=HMAC-SHA1" \ "&oauth_timestamp=137131201&oauth_token=kkk9d7dh3k39sjv7" ) end it 'should exclude the "oauth_signature" parameter when normalizing' do parameters = [ %w[a 1], %w[b 2], %w[c 3], %w[oauth_signature dpf43f3p2l4k3l03] ] expect(Signet::OAuth1.normalize_parameters(parameters)).to eq( "a=1&b=2&c=3" ) end it "should raise an error if normalizing parameters with bogus values" do expect(lambda do Signet::OAuth1.normalize_parameters 42 end).to raise_error(TypeError) end it "should raise an error if generating a base string with bogus values" do expect(lambda do Signet::OAuth1.generate_base_string( "GET", "http://photos.example.net/photos", 42 ) end).to raise_error(TypeError) end it "should correctly generate a base string" do method = "GET" uri = "http://photos.example.net/photos" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "file" => "vacation.jpg", "size" => "original" } expect(Signet::OAuth1.generate_base_string(method, uri, parameters)).to eq( "GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26" \ "oauth_consumer_key%3Ddpf43f3p2l4k3l03%26" \ "oauth_nonce%3Dkllo9940pd9333jh%26" \ "oauth_signature_method%3DHMAC-SHA1%26" \ "oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26" \ "oauth_version%3D1.0%26size%3Doriginal" ) end it "should correctly generate a base string with an already encoded URI" do method = "GET" uri = "http://photos.example.net/https%3A%2F%2Fwww.example.com" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "file" => "vacation.jpg", "size" => "original" } expect(Signet::OAuth1.generate_base_string(method, uri, parameters)).to eq( "GET&http%3A%2F%2Fphotos.example.net%2F" \ "https%253A%252F%252Fwww.example.com&file%3Dvacation.jpg%26" \ "oauth_consumer_key%3Ddpf43f3p2l4k3l03%26" \ "oauth_nonce%3Dkllo9940pd9333jh%26" \ "oauth_signature_method%3DHMAC-SHA1%26" \ "oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26" \ "oauth_version%3D1.0%26size%3Doriginal" ) end it "should correctly generate a base string with an already encoded URI" do method = "GET" uri = "http://example.com/r%20v/X?id=123" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0" } expect(Signet::OAuth1.generate_base_string(method, uri, parameters)).to eq( "GET&http%3A%2F%2Fexample.com%2Fr%2520v%2FX&" \ "id%3D123%26oauth_consumer_key%3Ddpf43f3p2l4k3l03%26" \ "oauth_nonce%3Dkllo9940pd9333jh%26" \ "oauth_signature_method%3DHMAC-SHA1%26" \ "oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26" \ "oauth_version%3D1.0" ) end it "should correctly generate a base string when port 8080 is specified" do method = "GET" uri = "http://www.example.net:8080/?q=1" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0" } expect(Signet::OAuth1.generate_base_string(method, uri, parameters)).to eq( "GET&http%3A%2F%2Fwww.example.net%3A8080%2F&" \ "oauth_consumer_key%3Ddpf43f3p2l4k3l03%26" \ "oauth_nonce%3Dkllo9940pd9333jh%26" \ "oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1191242096%26" \ "oauth_token%3Dnnch734d00sl2jdk%26oauth_version%3D1.0%26q%3D1" ) end it "should correctly generate a base string when port 80 is specified" do method = "GET" uri = "http://photos.example.net:80/photos" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "file" => "vacation.jpg", "size" => "original" } expect(Signet::OAuth1.generate_base_string(method, uri, parameters)).to eq( "GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26" \ "oauth_consumer_key%3Ddpf43f3p2l4k3l03%26" \ "oauth_nonce%3Dkllo9940pd9333jh%26" \ "oauth_signature_method%3DHMAC-SHA1%26" \ "oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26" \ "oauth_version%3D1.0%26size%3Doriginal" ) end it "should correctly generate a base string when port 443 is specified" do method = "GET" uri = "https://photos.example.net:443/photos" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "file" => "vacation.jpg", "size" => "original" } expect(Signet::OAuth1.generate_base_string(method, uri, parameters)).to eq( "GET&https%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26" \ "oauth_consumer_key%3Ddpf43f3p2l4k3l03%26" \ "oauth_nonce%3Dkllo9940pd9333jh%26" \ "oauth_signature_method%3DHMAC-SHA1%26" \ "oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26" \ "oauth_version%3D1.0%26size%3Doriginal" ) end it "should correctly generate a base signature with uppercase scheme" do method = "GET" uri = "HTTP://photos.example.net/photos?file=vacation.jpg" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "size" => "original" } expect(Signet::OAuth1.generate_base_string(method, uri, parameters)).to eq( "GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26" \ "oauth_consumer_key%3Ddpf43f3p2l4k3l03%26" \ "oauth_nonce%3Dkllo9940pd9333jh%26" \ "oauth_signature_method%3DHMAC-SHA1%26" \ "oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26" \ "oauth_version%3D1.0%26size%3Doriginal" ) end it "should correctly generate a base signature with mixedcase authority" do method = "GET" uri = "http://photos.eXaMpLe.NET/photos?file=vacation.jpg" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "size" => "original" } expect(Signet::OAuth1.generate_base_string(method, uri, parameters)).to eq( "GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26" \ "oauth_consumer_key%3Ddpf43f3p2l4k3l03%26" \ "oauth_nonce%3Dkllo9940pd9333jh%26" \ "oauth_signature_method%3DHMAC-SHA1%26" \ "oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26" \ "oauth_version%3D1.0%26size%3Doriginal" ) end it "should correctly generate a base signature with a method symbol" do method = :get uri = "http://photos.example.net/photos?file=vacation.jpg" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "size" => "original" } expect(Signet::OAuth1.generate_base_string(method, uri, parameters)).to eq( "GET&http%3A%2F%2Fphotos.example.net%2Fphotos&file%3Dvacation.jpg%26" \ "oauth_consumer_key%3Ddpf43f3p2l4k3l03%26" \ "oauth_nonce%3Dkllo9940pd9333jh%26" \ "oauth_signature_method%3DHMAC-SHA1%26" \ "oauth_timestamp%3D1191242096%26oauth_token%3Dnnch734d00sl2jdk%26" \ "oauth_version%3D1.0%26size%3Doriginal" ) end it "should correctly generate an authorization header" do parameters = [ %w[oauth_consumer_key 0685bd9184jfhq22], %w[oauth_token ad180jjd733klru7], ["oauth_signature_method", "HMAC-SHA1"], ["oauth_signature", "wOJIO9A2W5mFwDgiDvZbTSMK/PY="], %w[oauth_timestamp 137131200], %w[oauth_nonce 4572616e48616d6d65724c61686176], ["oauth_version", "1.0"] ] expect(Signet::OAuth1.generate_authorization_header( parameters, "http://sp.example.com/" )).to eq( 'OAuth realm="http://sp.example.com/", ' \ 'oauth_consumer_key="0685bd9184jfhq22", ' \ 'oauth_token="ad180jjd733klru7", ' \ 'oauth_signature_method="HMAC-SHA1", ' \ 'oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", ' \ 'oauth_timestamp="137131200", ' \ 'oauth_nonce="4572616e48616d6d65724c61686176", ' \ 'oauth_version="1.0"' ) end it "should raise an error if generating an authorization header " \ "with bogus values" do expect(lambda do Signet::OAuth1.generate_authorization_header 42 end).to raise_error(TypeError) end it "should raise an error if generating an authorization header " \ 'with the "realm" parameter specified the wrong way' do parameters = [ ["realm", "http://sp.example.com/"], %w[oauth_consumer_key 0685bd9184jfhq22], %w[oauth_token ad180jjd733klru7], ["oauth_signature_method", "HMAC-SHA1"], ["oauth_signature", "wOJIO9A2W5mFwDgiDvZbTSMK/PY="], %w[oauth_timestamp 137131200], %w[oauth_nonce 4572616e48616d6d65724c61686176], ["oauth_version", "1.0"] ] expect(lambda do Signet::OAuth1.generate_authorization_header parameters end).to raise_error(ArgumentError) end it "should correctly parse an authorization header" do parameters = Signet::OAuth1.parse_authorization_header( 'OAuth realm="http://sp.example.com/", ' \ 'oauth_consumer_key="0685bd9184jfhq22", ' \ 'oauth_token="ad180jjd733klru7", ' \ 'oauth_signature_method="HMAC-SHA1", ' \ 'oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", ' \ 'oauth_timestamp="137131200", ' \ 'oauth_nonce="4572616e48616d6d65724c61686176", ' \ 'oauth_version="1.0"' ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["realm"]).to eq "http://sp.example.com/" expect(parameters["oauth_consumer_key"]).to eq "0685bd9184jfhq22" expect(parameters["oauth_token"]).to eq "ad180jjd733klru7" expect(parameters["oauth_signature_method"]).to eq "HMAC-SHA1" expect(parameters["oauth_signature"]).to eq "wOJIO9A2W5mFwDgiDvZbTSMK/PY=" expect(parameters["oauth_timestamp"]).to eq "137131200" expect(parameters["oauth_nonce"]).to eq "4572616e48616d6d65724c61686176" expect(parameters["oauth_version"]).to eq "1.0" end it "should not unescape a realm in an authorization header" do parameters = Signet::OAuth1.parse_authorization_header( 'OAuth realm="http%3A%2F%2Fsp.example.com%2F", ' \ 'domain="http%3A%2F%2Fsp.example.com%2F", ' \ 'oauth_consumer_key="0685bd9184jfhq22", ' \ 'oauth_token="ad180jjd733klru7", ' \ 'oauth_signature_method="HMAC-SHA1", ' \ 'oauth_signature="wOJIO9A2W5mFwDgiDvZbTSMK%2FPY%3D", ' \ 'oauth_timestamp="137131200", ' \ 'oauth_nonce="4572616e48616d6d65724c61686176", ' \ 'oauth_version="1.0"' ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["realm"]).to eq "http%3A%2F%2Fsp.example.com%2F" expect(parameters["domain"]).to eq "http://sp.example.com/" expect(parameters["oauth_consumer_key"]).to eq "0685bd9184jfhq22" expect(parameters["oauth_token"]).to eq "ad180jjd733klru7" expect(parameters["oauth_signature_method"]).to eq "HMAC-SHA1" expect(parameters["oauth_signature"]).to eq "wOJIO9A2W5mFwDgiDvZbTSMK/PY=" expect(parameters["oauth_timestamp"]).to eq "137131200" expect(parameters["oauth_nonce"]).to eq "4572616e48616d6d65724c61686176" expect(parameters["oauth_version"]).to eq "1.0" end it "should raise an error if parsing an authorization header " \ "with bogus values" do expect(lambda do Signet::OAuth1.parse_authorization_header 42 end).to raise_error(TypeError) end it "should raise an error if parsing a non-OAuth authorization header" do expect(lambda do Signet::OAuth1.parse_authorization_header( "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" ) end).to raise_error(Signet::ParseError) end it "should correctly parse a form encoded credential" do credential = Signet::OAuth1.parse_form_encoded_credentials( "oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03" ) expect(credential.key).to eq "hh5s93j4hdidpola" expect(credential.secret).to eq "hdhd0244k9j7ao03" end it "should correctly parse a form encoded credential" do credential = Signet::OAuth1.parse_form_encoded_credentials( "oauth_token=hdk48Djdsa&oauth_token_secret=xyz4992k83j47x0b&" \ "oauth_callback_confirmed=true" ) expect(credential.key).to eq "hdk48Djdsa" expect(credential.secret).to eq "xyz4992k83j47x0b" end it "should raise an error if parsing a form encoded credential " \ "with bogus values" do expect(lambda do Signet::OAuth1.parse_form_encoded_credentials 42 end).to raise_error(TypeError) end it "should correctly generate a signature for a set of parameters" do method = :get uri = "http://photos.example.net/photos" client_credential_secret = "kd94hf93k423kf44" token_credential_secret = "pfkkdhi9sl3r4s00" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-SHA1", "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "file" => "vacation.jpg", "size" => "original" } expect(Signet::OAuth1.sign_parameters( method, uri, parameters, client_credential_secret, token_credential_secret )).to eq "tR3+Ty81lMeYAr/Fid0kMTYa/WM=" end it "should raise an error when trying to sign with with unknown method" do method = :get uri = "http://photos.example.net/photos" client_credential_secret = "kd94hf93k423kf44" token_credential_secret = "pfkkdhi9sl3r4s00" parameters = { "oauth_consumer_key" => "dpf43f3p2l4k3l03", "oauth_token" => "nnch734d00sl2jdk", "oauth_signature_method" => "HMAC-BOGUS", # Unknown signature method "oauth_timestamp" => "1191242096", "oauth_nonce" => "kllo9940pd9333jh", "oauth_version" => "1.0", "file" => "vacation.jpg", "size" => "original" } expect(lambda do Signet::OAuth1.sign_parameters( method, uri, parameters, client_credential_secret, token_credential_secret ) end).to raise_error(NotImplementedError) end it "should correctly generate authorization URIs" do authorization_uri = "http://photos.example.net/authorize" temporary_credential_key = "hh5s93j4hdidpola" callback = "http://printer.example.com/request_token_ready" parsed_uri = Addressable::URI.parse( Signet::OAuth1.generate_authorization_uri( authorization_uri, temporary_credential_key: temporary_credential_key, callback: callback ) ) expect(parsed_uri.query_values).to have_key("oauth_token") expect(parsed_uri.query_values["oauth_token"]).to eq temporary_credential_key expect(parsed_uri.query_values).to have_key("oauth_callback") expect(parsed_uri.query_values["oauth_callback"]).to eq callback end end describe Signet::OAuth1, "when generating temporary credentials parameters" do before do @client_credential_key = "dpf43f3p2l4k3l03" @callback = "http://printer.example.com/request_token_ready" @signature_method = "HMAC-SHA1" @scope = "http://photos.example.com/full_access" @additional_parameters = [["scope", @scope]] @unsigned_parameters = Signet::OAuth1.unsigned_temporary_credential_parameters( client_credential_key: @client_credential_key, callback: @callback, signature_method: @signature_method, additional_parameters: @additional_parameters ).each_with_object({}) { |(k, v), h| h[k] = v; } end it "should raise an error if the client credential key is missing" do expect(lambda do Signet::OAuth1.unsigned_temporary_credential_parameters( client_credential_key: nil, callback: @callback, signature_method: @signature_method, additional_parameters: @additional_parameters ) end).to raise_error(ArgumentError) end it "should have the correct client credential key" do expect(@unsigned_parameters).to have_key("oauth_consumer_key") expect(@unsigned_parameters["oauth_consumer_key"]).to eq @client_credential_key end it "should have the correct signature method" do expect(@unsigned_parameters).to have_key("oauth_signature_method") expect(@unsigned_parameters["oauth_signature_method"]).to eq @signature_method end it "should have a valid timestamp" do # Verify that we have a timestamp, it's in the correct format and within # a reasonable range of the current time. expect(@unsigned_parameters).to have_key("oauth_timestamp") expect(@unsigned_parameters["oauth_timestamp"]).to match(/^[0-9]+$/) expect(@unsigned_parameters["oauth_timestamp"].to_i).to be <= Time.now.to_i expect(@unsigned_parameters["oauth_timestamp"].to_i).to be >= Time.now.to_i - 1 end it "should have a valid nonce" do # Verify that we have a nonce and that it has sufficient length for # uniqueness. expect(@unsigned_parameters).to have_key("oauth_nonce") expect(@unsigned_parameters["oauth_nonce"]).to match(/^[0-9a-zA-Z]{16,100}$/) end it "should have the correct callback" do expect(@unsigned_parameters).to have_key("oauth_callback") expect(@unsigned_parameters["oauth_callback"]).to eq @callback end it "should have the correct scope parameter" do expect(@unsigned_parameters).to have_key("scope") expect(@unsigned_parameters["scope"]).to eq @scope end it "should have the correct OAuth version" do expect(@unsigned_parameters).to have_key("oauth_version") expect(@unsigned_parameters["oauth_version"]).to eq "1.0" end end describe Signet::OAuth1, "when generating token credential parameters" do before do @client_credential_key = "dpf43f3p2l4k3l03" @temporary_credential_key = "hh5s93j4hdidpola" @verifier = "473f82d3" @signature_method = "HMAC-SHA1" @unsigned_parameters = Signet::OAuth1.unsigned_token_credential_parameters( client_credential_key: @client_credential_key, temporary_credential_key: @temporary_credential_key, signature_method: @signature_method, verifier: @verifier ).each_with_object({}) { |(k, v), h| h[k] = v; } end it "should raise an error if the client credential key is missing" do expect(lambda do Signet::OAuth1.unsigned_token_credential_parameters( client_credential_key: nil, temporary_credential_key: @temporary_credential_key, signature_method: @signature_method, verifier: @verifier ) end).to raise_error(ArgumentError) end it "should raise an error if the temporary credential key is missing" do expect(lambda do Signet::OAuth1.unsigned_token_credential_parameters( client_credential_key: @client_credential_key, temporary_credential_key: nil, signature_method: @signature_method, verifier: @verifier ) end).to raise_error(ArgumentError) end it "should raise an error if the verifier is missing" do expect(lambda do Signet::OAuth1.unsigned_token_credential_parameters( client_credential_key: @client_credential_key, temporary_credential_key: @temporary_credential_key, signature_method: @signature_method, verifier: nil ) end).to raise_error(ArgumentError) end it "should have the correct client credential key" do expect(@unsigned_parameters).to have_key("oauth_consumer_key") expect(@unsigned_parameters["oauth_consumer_key"]).to eq @client_credential_key end it "should have the correct temporary credentials key" do expect(@unsigned_parameters).to have_key("oauth_token") expect(@unsigned_parameters["oauth_token"]).to eq @temporary_credential_key end it "should have the correct signature method" do expect(@unsigned_parameters).to have_key("oauth_signature_method") expect(@unsigned_parameters["oauth_signature_method"]).to eq @signature_method end it "should have a valid timestamp" do # Verify that we have a timestamp, it's in the correct format and within # a reasonable range of the current time. expect(@unsigned_parameters).to have_key("oauth_timestamp") expect(@unsigned_parameters["oauth_timestamp"]).to match(/^[0-9]+$/) expect(@unsigned_parameters["oauth_timestamp"].to_i).to be <= Time.now.to_i expect(@unsigned_parameters["oauth_timestamp"].to_i).to be >= Time.now.to_i - 1 end it "should have a valid nonce" do # Verify that we have a nonce and that it has sufficient length for # uniqueness. expect(@unsigned_parameters).to have_key("oauth_nonce") expect(@unsigned_parameters["oauth_nonce"]).to match(/^[0-9a-zA-Z]{16,100}$/) end it "should have the verifier" do expect(@unsigned_parameters).to have_key("oauth_verifier") expect(@unsigned_parameters["oauth_verifier"]).to eq @verifier end it "should have the correct OAuth version" do expect(@unsigned_parameters).to have_key("oauth_version") expect(@unsigned_parameters["oauth_version"]).to eq "1.0" end end describe Signet::OAuth1, "when generating protected resource parameters" do before do @client_credential_key = "dpf43f3p2l4k3l03" @token_credential_key = "nnch734d00sl2jdk" @signature_method = "HMAC-SHA1" @unsigned_parameters = Signet::OAuth1.unsigned_resource_parameters( client_credential_key: @client_credential_key, token_credential_key: @token_credential_key, signature_method: @signature_method ).each_with_object({}) { |(k, v), h| h[k] = v; } end it "should raise an error if the client credential key is missing" do expect(lambda do Signet::OAuth1.unsigned_resource_parameters( client_credential_key: nil, token_credential_key: @token_credential_key, signature_method: @signature_method ) end).to raise_error(ArgumentError) end it "should raise an error if the token credential key is missing" do expect(lambda do Signet::OAuth1.unsigned_resource_parameters( client_credential_key: @client_credential_key, token_credential_key: nil, signature_method: @signature_method ) end).to raise_error(ArgumentError) end it "should have the correct client credential key" do expect(@unsigned_parameters).to have_key("oauth_consumer_key") expect(@unsigned_parameters["oauth_consumer_key"]).to eq @client_credential_key end it "should have the correct token credentials key" do expect(@unsigned_parameters).to have_key("oauth_token") expect(@unsigned_parameters["oauth_token"]).to eq @token_credential_key end it "should have the correct signature method" do expect(@unsigned_parameters).to have_key("oauth_signature_method") expect(@unsigned_parameters["oauth_signature_method"]).to eq @signature_method end it "should have a valid timestamp" do # Verify that we have a timestamp, it's in the correct format and within # a reasonable range of the current time. expect(@unsigned_parameters).to have_key("oauth_timestamp") expect(@unsigned_parameters["oauth_timestamp"]).to match(/^[0-9]+$/) expect(@unsigned_parameters["oauth_timestamp"].to_i).to be <= Time.now.to_i expect(@unsigned_parameters["oauth_timestamp"].to_i).to be >= Time.now.to_i - 1 end it "should have a valid nonce" do # Verify that we have a nonce and that it has sufficient length for # uniqueness. expect(@unsigned_parameters).to have_key("oauth_nonce") expect(@unsigned_parameters["oauth_nonce"]).to match(/^[0-9a-zA-Z]{16,100}$/) end it "should have the correct OAuth version" do expect(@unsigned_parameters).to have_key("oauth_version") expect(@unsigned_parameters["oauth_version"]).to eq "1.0" end end describe Signet::OAuth1, "when generating token credential parameters " \ "with Signet::OAuth1::Credential objects" do before do @client_credential = Signet::OAuth1::Credential.new( "dpf43f3p2l4k3l03", "kd94hf93k423kf44" ) @temporary_credential = Signet::OAuth1::Credential.new( "hh5s93j4hdidpola", "hdhd0244k9j7ao03" ) @verifier = "473f82d3" @signature_method = "HMAC-SHA1" @unsigned_parameters = Signet::OAuth1.unsigned_token_credential_parameters( client_credential: @client_credential, temporary_credential: @temporary_credential, signature_method: @signature_method, verifier: @verifier ).each_with_object({}) { |(k, v), h| h[k] = v; } end it "should have the correct client credential key" do expect(@unsigned_parameters).to have_key("oauth_consumer_key") expect(@unsigned_parameters["oauth_consumer_key"]).to eq @client_credential.key end it "should have the correct temporary credentials key" do expect(@unsigned_parameters).to have_key("oauth_token") expect(@unsigned_parameters["oauth_token"]).to eq @temporary_credential.key end it "should have the correct signature method" do expect(@unsigned_parameters).to have_key("oauth_signature_method") expect(@unsigned_parameters["oauth_signature_method"]).to eq @signature_method end it "should have a valid timestamp" do # Verify that we have a timestamp, it's in the correct format and within # a reasonable range of the current time. expect(@unsigned_parameters).to have_key("oauth_timestamp") expect(@unsigned_parameters["oauth_timestamp"]).to match(/^[0-9]+$/) expect(@unsigned_parameters["oauth_timestamp"].to_i).to be <= Time.now.to_i expect(@unsigned_parameters["oauth_timestamp"].to_i).to be >= Time.now.to_i - 1 end it "should have a valid nonce" do # Verify that we have a nonce and that it has sufficient length for # uniqueness. expect(@unsigned_parameters).to have_key("oauth_nonce") expect(@unsigned_parameters["oauth_nonce"]).to match(/^[0-9a-zA-Z]{16,100}$/) end it "should have the correct OAuth version" do expect(@unsigned_parameters).to have_key("oauth_version") expect(@unsigned_parameters["oauth_version"]).to eq "1.0" end end describe Signet::OAuth1, "when generating token credential parameters " \ "with a Signet::OAuth1::Client object" do before do @client = Signet::OAuth1::Client.new @client.client_credential = Signet::OAuth1::Credential.new( "dpf43f3p2l4k3l03", "kd94hf93k423kf44" ) @client.temporary_credential = Signet::OAuth1::Credential.new( "hh5s93j4hdidpola", "hdhd0244k9j7ao03" ) @verifier = "473f82d3" @signature_method = "HMAC-SHA1" @unsigned_parameters = Signet::OAuth1.unsigned_token_credential_parameters( client: @client, signature_method: @signature_method, verifier: @verifier ).each_with_object({}) { |(k, v), h| h[k] = v; } end it "should have the correct client credential key" do expect(@unsigned_parameters).to have_key("oauth_consumer_key") expect(@unsigned_parameters["oauth_consumer_key"]).to eq @client.client_credential_key end it "should have the correct temporary credentials key" do expect(@unsigned_parameters).to have_key("oauth_token") expect(@unsigned_parameters["oauth_token"]).to eq @client.temporary_credential_key end it "should have the correct signature method" do expect(@unsigned_parameters).to have_key("oauth_signature_method") expect(@unsigned_parameters["oauth_signature_method"]).to eq @signature_method end it "should have a valid timestamp" do # Verify that we have a timestamp, it's in the correct format and within # a reasonable range of the current time. expect(@unsigned_parameters).to have_key("oauth_timestamp") expect(@unsigned_parameters["oauth_timestamp"]).to match(/^[0-9]+$/) expect(@unsigned_parameters["oauth_timestamp"].to_i).to be <= Time.now.to_i expect(@unsigned_parameters["oauth_timestamp"].to_i).to be >= Time.now.to_i - 1 end it "should have a valid nonce" do # Verify that we have a nonce and that it has sufficient length for # uniqueness. expect(@unsigned_parameters).to have_key("oauth_nonce") expect(@unsigned_parameters["oauth_nonce"]).to match(/^[0-9a-zA-Z]{16,100}$/) end it "should have the correct OAuth version" do expect(@unsigned_parameters).to have_key("oauth_version") expect(@unsigned_parameters["oauth_version"]).to eq "1.0" end end describe Signet::OAuth1, "when generating token credential parameters " \ "with Signet::OAuth1::Credential objects" do before do @client_credential = Signet::OAuth1::Credential.new( "dpf43f3p2l4k3l03", "kd94hf93k423kf44" ) @temporary_credential = Signet::OAuth1::Credential.new( "hh5s93j4hdidpola", "hdhd0244k9j7ao03" ) @verifier = "473f82d3" @signature_method = "HMAC-SHA1" @unsigned_parameters = Signet::OAuth1.unsigned_token_credential_parameters( client_credential: @client_credential, temporary_credential: @temporary_credential, signature_method: @signature_method, verifier: @verifier ).each_with_object({}) { |(k, v), h| h[k] = v; } end it "should have the correct client credential key" do expect(@unsigned_parameters).to have_key("oauth_consumer_key") expect(@unsigned_parameters["oauth_consumer_key"]).to eq @client_credential.key end it "should have the correct temporary credentials key" do expect(@unsigned_parameters).to have_key("oauth_token") expect(@unsigned_parameters["oauth_token"]).to eq @temporary_credential.key end it "should have the correct signature method" do expect(@unsigned_parameters).to have_key("oauth_signature_method") expect(@unsigned_parameters["oauth_signature_method"]).to eq @signature_method end it "should have a valid timestamp" do # Verify that we have a timestamp, it's in the correct format and within # a reasonable range of the current time. expect(@unsigned_parameters).to have_key("oauth_timestamp") expect(@unsigned_parameters["oauth_timestamp"]).to match(/^[0-9]+$/) expect(@unsigned_parameters["oauth_timestamp"].to_i).to be <= Time.now.to_i expect(@unsigned_parameters["oauth_timestamp"].to_i).to be >= Time.now.to_i - 1 end it "should have a valid nonce" do # Verify that we have a nonce and that it has sufficient length for # uniqueness. expect(@unsigned_parameters).to have_key("oauth_nonce") expect(@unsigned_parameters["oauth_nonce"]).to match(/^[0-9a-zA-Z]{16,100}$/) end it "should have the correct OAuth version" do expect(@unsigned_parameters).to have_key("oauth_version") expect(@unsigned_parameters["oauth_version"]).to eq "1.0" end end describe Signet::OAuth1, "extracting credential keys from options" do it "should raise an error for bogus credentials" do expect(lambda do Signet::OAuth1.extract_credential_key_option( :client, client_credential_key: true ) end).to raise_error(TypeError) end it "should raise an error for bogus credentials" do expect(lambda do Signet::OAuth1.extract_credential_key_option( :client, client_credential: 42 ) end).to raise_error(TypeError) end it "should raise an error for bogus credentials" do expect(lambda do Signet::OAuth1.extract_credential_key_option( :client, client: 42 ) end).to raise_error(TypeError) end it "should return nil for missing credential key" do expect(Signet::OAuth1.extract_credential_key_option(:client, {})).to eq nil end it "should find the correct credential key" do expect(Signet::OAuth1.extract_credential_key_option( :client, client_credential_key: "dpf43f3p2l4k3l03" )).to eq "dpf43f3p2l4k3l03" end it "should find the correct credential key" do expect(Signet::OAuth1.extract_credential_key_option( :client, client_credential: Signet::OAuth1::Credential.new( "dpf43f3p2l4k3l03", "kd94hf93k423kf44" ) )).to eq "dpf43f3p2l4k3l03" end it "should find the correct credential key" do client = Signet::OAuth1::Client.new client.client_credential = Signet::OAuth1::Credential.new( "dpf43f3p2l4k3l03", "kd94hf93k423kf44" ) expect(Signet::OAuth1.extract_credential_key_option( :client, client: client )).to eq "dpf43f3p2l4k3l03" end it "should find the correct credential key" do client = Signet::OAuth1::Client.new client.temporary_credential = Signet::OAuth1::Credential.new( "hh5s93j4hdidpola", "hdhd0244k9j7ao03" ) expect(Signet::OAuth1.extract_credential_key_option( :temporary, client: client )).to eq "hh5s93j4hdidpola" end end describe Signet::OAuth1, "extracting credential secrets from options" do it "should raise an error for bogus credentials" do expect(lambda do Signet::OAuth1.extract_credential_secret_option( :client, client_credential_secret: true ) end).to raise_error(TypeError) end it "should raise an error for bogus credentials" do expect(lambda do Signet::OAuth1.extract_credential_secret_option( :client, client_credential: 42 ) end).to raise_error(TypeError) end it "should raise an error for bogus credentials" do expect(lambda do Signet::OAuth1.extract_credential_secret_option( :client, client: 42 ) end).to raise_error(TypeError) end it "should raise an error for missing credential secret" do expect(Signet::OAuth1.extract_credential_secret_option(:client, {})).to eq nil end it "should find the correct credential secret" do expect(Signet::OAuth1.extract_credential_secret_option( :client, client_credential_secret: "kd94hf93k423kf44" )).to eq "kd94hf93k423kf44" end it "should find the correct credential secret" do expect(Signet::OAuth1.extract_credential_secret_option( :client, client_credential: Signet::OAuth1::Credential.new( "dpf43f3p2l4k3l03", "kd94hf93k423kf44" ) )).to eq "kd94hf93k423kf44" end it "should find the correct credential secret" do client = Signet::OAuth1::Client.new client.client_credential = Signet::OAuth1::Credential.new( "dpf43f3p2l4k3l03", "kd94hf93k423kf44" ) expect(Signet::OAuth1.extract_credential_secret_option( :client, client: client )).to eq "kd94hf93k423kf44" end it "should find the correct credential secret" do client = Signet::OAuth1::Client.new client.temporary_credential = Signet::OAuth1::Credential.new( "hh5s93j4hdidpola", "hdhd0244k9j7ao03" ) expect(Signet::OAuth1.extract_credential_secret_option( :temporary, client: client )).to eq "hdhd0244k9j7ao03" end end signet-0.14.0/spec/signet/oauth_2_spec.rb0000644000004100000410000001764313673221365020263 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "spec_helper" require "signet/errors" require "signet/oauth_2" describe Signet::OAuth2 do # This behavior will almost certainly change in subsequent updates. describe "when parsing an Authorization header" do it "should correctly handle HTTP Basic auth-scheme" do parameters = Signet::OAuth2.parse_authorization_header( "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW" ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["client_id"]).to eq "s6BhdRkqt3" expect(parameters["client_secret"]).to eq "gX1fBat3bV" end it "should correctly handle OAuth auth-scheme" do parameters = Signet::OAuth2.parse_authorization_header( "OAuth vF9dft4qmT" ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["access_token"]).to eq "vF9dft4qmT" end it "should correctly handle OAuth auth-scheme with realm" do parameters = Signet::OAuth2.parse_authorization_header( 'OAuth vF9dft4qmT, realm="http://sp.example.com/"' ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["access_token"]).to eq "vF9dft4qmT" expect(parameters["realm"]).to eq "http://sp.example.com/" end it "should correctly handle OAuth auth-scheme with multiple auth-params" do parameters = Signet::OAuth2.parse_authorization_header( 'OAuth vF9dft4qmT, first="one", second="two"' ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["access_token"]).to eq "vF9dft4qmT" expect(parameters["first"]).to eq "one" expect(parameters["second"]).to eq "two" end it "should liberally handle auth-params with single-quoted strings" do parameters = Signet::OAuth2.parse_authorization_header( "OAuth vF9dft4qmT, first='one', second='two'" ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["access_token"]).to eq "vF9dft4qmT" expect(parameters["first"]).to eq "one" expect(parameters["second"]).to eq "two" end it "should liberally handle auth-params with unquoted strings" do parameters = Signet::OAuth2.parse_authorization_header( "OAuth vF9dft4qmT, first=one, second=two" ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["access_token"]).to eq "vF9dft4qmT" expect(parameters["first"]).to eq "one" expect(parameters["second"]).to eq "two" end it "should not allow unquoted strings that do not match tchar" do expect(lambda do parameters = Signet::OAuth2.parse_authorization_header( "OAuth vF9dft4qmT, first=one:1" ) end).to raise_error(Signet::ParseError) end it "should not parse non-OAuth auth-schemes" do expect(lambda do Signet::OAuth2.parse_authorization_header( 'AuthSub token="GD32CMCL25aZ-v____8B"' ) end).to raise_error(Signet::ParseError) end end # This behavior will almost certainly change in subsequent updates. describe "when parsing a WWW-Authenticate header" do it "should correctly handle OAuth challenge with auth-params" do parameters = Signet::OAuth2.parse_www_authenticate_header( 'OAuth realm="http://sp.example.com/", error="expired_token", ' \ 'error_description="The access token has expired."' ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["realm"]).to eq "http://sp.example.com/" expect(parameters["error"]).to eq "expired_token" expect(parameters["error_description"]).to eq "The access token has expired." end it "should liberally handle auth-params with single-quoted strings" do parameters = Signet::OAuth2.parse_www_authenticate_header( "OAuth realm='http://sp.example.com/', error='expired_token', " \ "error_description='The access token has expired.'" ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["realm"]).to eq "http://sp.example.com/" expect(parameters["error"]).to eq "expired_token" expect(parameters["error_description"]).to eq "The access token has expired." end it "should liberally handle auth-params with token strings" do parameters = Signet::OAuth2.parse_www_authenticate_header( 'OAuth realm="http://sp.example.com/", error=expired_token, ' \ 'error_description="The access token has expired."' ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["realm"]).to eq "http://sp.example.com/" expect(parameters["error"]).to eq "expired_token" expect(parameters["error_description"]).to eq "The access token has expired." end it "should liberally handle out-of-order auth-params" do parameters = Signet::OAuth2.parse_www_authenticate_header( "OAuth error_description='The access token has expired.', " \ "error='expired_token', realm='http://sp.example.com/'" ).each_with_object({}) { |(k, v), h| h[k] = v; } expect(parameters["realm"]).to eq "http://sp.example.com/" expect(parameters["error"]).to eq "expired_token" expect(parameters["error_description"]).to eq "The access token has expired." end it "should not allow unquoted strings that do not match tchar" do expect(lambda do Signet::OAuth2.parse_www_authenticate_header( "OAuth realm=http://sp.example.com/, error=expired_token, " \ 'error_description="The access token has expired."' ) end).to raise_error(Signet::ParseError) end it "should not parse non-OAuth challenges" do expect(lambda do Signet::OAuth2.parse_www_authenticate_header( 'AuthSub realm="https://www.google.com/accounts/AuthSubRequest"' ) end).to raise_error(Signet::ParseError) end end describe "when generating a Basic Authorization header" do it "should correctly handle client ID and password pairs" do # Example from OAuth 2 spec expect(Signet::OAuth2.generate_basic_authorization_header( "s6BhdRkqt3", "gX1fBat3bV" )).to eq "Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW" end it "should correctly encode using the alogrithm given in RFC 2617" do # Example from RFC 2617 expect(Signet::OAuth2.generate_basic_authorization_header( "Aladdin", "open sesame" )).to eq "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" end end describe "when parsing a token response body" do it "should correctly handle just an access token" do expect(Signet::OAuth2.parse_credentials( '{"access_token": "12345"}', "application/json; charset=utf-8" )).to eq ({ "access_token" => "12345" }) end it "should handle form encoded responses" do expect(Signet::OAuth2.parse_credentials( "access_token=12345&expires=1000", "application/x-www-form-urlencoded; charset=utf-8" )).to eq("access_token" => "12345", "expires" => "1000") end it "should raise an error for an invalid body" do expect(lambda do Signet::OAuth2.parse_credentials( "This is not JSON.", "application/json" ) end).to raise_error(MultiJson::DecodeError) end it "should raise an error for a bogus body" do expect(lambda do Signet::OAuth2.parse_credentials :bogus, "application/json" end).to raise_error(TypeError) end end end signet-0.14.0/spec/spec_helper_spec.rb0000644000004100000410000000066613673221365017717 0ustar www-datawww-dataRSpec.describe "spec_helper.rb" do let(:spec_dir) { __dir__ } let(:root_dir) { File.expand_path File.join(spec_dir, "..") } let(:lib_dir) { File.expand_path File.join(root_dir, "lib") } describe "spec_dir" do it "is already in $LOAD_PATH" do expect($LOAD_PATH).to include spec_dir end end describe "lib_dir" do it "is already in $LOAD_PATH" do expect($LOAD_PATH).to include lib_dir end end end signet-0.14.0/spec/spec_helper.rb0000644000004100000410000000026713673221365016702 0ustar www-datawww-data$LOAD_PATH.uniq! require "rubygems" require "signet" require "rspec" require "simplecov" require "faraday" SimpleCov.start if ENV["COVERAGE"] Faraday::Adapter.load_middleware :test signet-0.14.0/CHANGELOG.md0000644000004100000410000001143713673221365014744 0ustar www-datawww-data### 0.14.0 / 2020-03-31 * Support for fetching ID tokens from google oauth2 endpoint. ### 0.13.2 / 2020-03-25 Rerelease of 0.13.1. ### 0.13.1 / 2020-03-24 * Update github url ### 0.13.0 / 2020-02-24 * Support Faraday 1.x ### 0.12.0 / 2019-10-08 * This version now requires Ruby 2.4. * Support array values of the "aud" field. * Normalize the version constant to match related gems. ### 0.11.0 / 2018-10-08 * Add constant time comparison for oauth signatures. ### 0.10.0 / 2018-09-21 * Add UnexpectedStatusError class for http status errors that are not handled. ### 0.9.2 / 2018-09-12 * Update issued_at correctly when it is set simultaneously with expires_in. ### 0.9.1 / 2018-08-29 * Warn on EOL ruby versions. * Fix DateTime normalization. ### 0.9.0 / 2018-08-20 * Add RemoteServerError class for 5xx level errors. * Allow to_json to be called with arguments * Expires_in now sets and reflects current expires_at value * Expires_within(0) now returns false when expires_at is nil. ### 0.8.1 / 2017-10-13 * Restore support for Ruby 1.9.3 ### 0.8.0 / 2017-10-12 * Ensure the "expires_at" attribute is recalculated on refresh (chutzimir) * Fix warnings on Ruby 2.4 (koic) * Allow DateTime objects to be passed into attributes (foxtacles) * Provide signature verification algorithm for compatibility with ruby-jwt 2.0 (jurriaan) * Signet::OAuth2::Client#decoded_id_token can take a keyfinder block (mvastola) ### 0.7.3 / 2016-06-20 * Fix timestamp parsing on 32-bit systems * Fix expiration check when issue/expiry times are nil ### 0.7.2 / 2015-12-21 * Don't assume Faraday form encoding middleware is present ### 0.7.1 / 2015-12-17 * Fix an issue with date parsing ### 0.7 / 2015-12-06 * No longer overwrite SSL environment variables. * Tighten up date & URL (de)serialization for OAuth2 client * Allow Hurley as a connection * Allow scope as an option in `fetch_access_token!` to request downscoped access tokens * Add expires_within(sec) method to oauth2 client to facilitate proactive refreshes ### 0.6.1 / 2015-06-08 * Fix language warnings for unused & shadowed variables ((@blowmage)[]) * Update SSL cert path for OSX ((@gambaroff)[]) * Update JWT library and fix broken tests * Fix incorrect parameter name in OAuth2 client docs ((@samuelreh)[]) * Fix symbolization of URL parameter keys ((@swifthand)[]) ### 0.6.0 / 2014-12-05 * Drop support for ruby versions < 1.9.3 * Update gem dependencies and lock down versions tighter * Allow form encoded responses when exchanging OAuth 2 authorization codes * Normalize options keys for indifferent access ### 0.5.1 / 2014-06-08 * Allow Hash objects to be used to initialize authorization URI * Added PLAINTEXT and RSA-SHA1 signature methods to OAuth 1 support * Added client object serialization * The `approval_prompt` option no longer defaults to `:force` * The `approval_prompt` and `prompt` are now mutually exclusive. ### 0.5.0 / 2013-05-31 * Switched to faraday 0.9.0 * Added `expires_at` option ### 0.4.5 * Minor documentation fixes * Allow postmessage as a valid redirect_uri in OAuth 2 ### 0.4.4 * Add support for assertion profile ### 0.4.3 * Added method to clear credentials ### 0.4.2 * Backwards compatibility for MultiJson ### 0.4.1 * Updated Launchy dependency ### 0.4.0 * Added OAuth 1 server implementation * Updated Faraday dependency ### 0.3.4 * Attempts to auto-detect CA cert location ### 0.3.3 * Request objects no longer recreated during processing * Faraday middleware now supported * Streamed requests now supported * Fixed assertion profiles; client ID/secret omission no longer an error ### 0.3.2 * Added audience security check for ID tokens ### 0.3.1 * Fixed a warning while determining grant type * Removed requirement that a connection be supplied when authorizing requests * Updated addressable dependency to avoid minor bug * Fixed some documentation stuff around markdown formatting * Added support for Google Code wiki format output when generating docs ### 0.3.0 * Replaced httpadapter gem dependency with faraday * Replaced json gem dependency with multi_json * Updated to OAuth 2.0 draft 22 * Complete test coverage ### 0.2.4 * Updated to incorporate changes to the Google OAuth endpoints ### 0.2.3 * Added support for JWT-formatted ID tokens. * Added :issued_at option to #update_token! method. ### 0.2.2 * Lowered requirements for json gem ### 0.2.1 * Updated to keep in sync with the new httpadapter changes ### 0.2.0 * Added support for OAuth 2.0 draft 10 ### 0.1.4 * Added support for a two-legged authorization flow ### 0.1.3 * Fixed issue with headers passed in as a Hash * Fixed incompatibilities with Ruby 1.8.6 ### 0.1.2 * Fixed bug with overzealous normalization ### 0.1.1 * Fixed bug with missing StringIO require * Fixed issue with dependency on unreleased features of addressable ### 0.1.0 * Initial release signet-0.14.0/signet.gemspec0000644000004100000410000000352613673221365015771 0ustar www-datawww-data$LOAD_PATH.push File.expand_path("lib", __dir__) require "signet/version" Gem::Specification.new do |gem| gem.name = "signet" gem.version = Signet::VERSION gem.required_rubygems_version = ">= 1.3.5" gem.require_paths = ["lib"] gem.authors = ["Bob Aman", "Steven Bazyl"] gem.license = "Apache-2.0" gem.description = "Signet is an OAuth 1.0 / OAuth 2.0 implementation.\n" gem.email = "sbazyl@google.com" gem.extra_rdoc_files = ["README.md"] gem.files = ["signet.gemspec", "Rakefile", "LICENSE", "CHANGELOG.md", "README.md", "Gemfile"] gem.files += Dir.glob "lib/**/*.rb" gem.files += Dir.glob "spec/**/*.{rb,opts}" gem.files += Dir.glob "vendor/**/*.rb" gem.files += Dir.glob "tasks/**/*" gem.files += Dir.glob "website/**/*" gem.homepage = "https://github.com/googleapis/signet" gem.rdoc_options = ["--main", "README.md"] gem.summary = "Signet is an OAuth 1.0 / OAuth 2.0 implementation." gem.required_ruby_version = ">= 2.4.0" gem.add_runtime_dependency "addressable", "~> 2.3" gem.add_runtime_dependency "faraday", ">= 0.17.3", "< 2.0" gem.add_runtime_dependency "jwt", ">= 1.5", "< 3.0" gem.add_runtime_dependency "multi_json", "~> 1.10" gem.add_development_dependency "google-style", "~> 0.3" gem.add_development_dependency "kramdown", "~> 1.5" gem.add_development_dependency "launchy", "~> 2.4" gem.add_development_dependency "rake", "~> 12.0" gem.add_development_dependency "rspec", "~> 3.1" gem.add_development_dependency "simplecov", "~> 0.9" gem.add_development_dependency "yard", "~> 0.9", ">= 0.9.12" if gem.respond_to? :metadata gem.metadata["changelog_uri"] = "https://github.com/googleapis/signet/blob/master/CHANGELOG.md" gem.metadata["source_code_uri"] = "https://github.com/googleapis/signet" gem.metadata["bug_tracker_uri"] = "https://github.com/googleapis/signet/issues" end end signet-0.14.0/LICENSE0000644000004100000410000002514313673221365014137 0ustar www-datawww-data Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. signet-0.14.0/Rakefile0000644000004100000410000000530213673221365014572 0ustar www-datawww-datarequire "rubygems" require "json" require "rake" require "bundler/gem_tasks" task :release_gem, :tag do |_t, args| tag = args[:tag] raise "You must provide a tag to release." if tag.nil? # Verify the tag format "vVERSION" m = tag.match /v(?\S*)/ raise "Tag #{tag} does not match the expected format." if m.nil? version = m[:version] raise "You must provide a version." if version.nil? api_token = ENV["RUBYGEMS_API_TOKEN"] require "gems" if api_token ::Gems.configure do |config| config.key = api_token end end Bundler.with_clean_env do sh "rm -rf pkg" sh "bundle update" sh "bundle exec rake build" end path_to_be_pushed = "pkg/signet-#{version}.gem" gem_was_published = nil if File.file? path_to_be_pushed begin response = ::Gems.push File.new(path_to_be_pushed) puts response raise unless response.include? "Successfully registered gem:" gem_was_published = true puts "Successfully built and pushed signet for version #{version}" rescue StandardError => e gem_was_published = false puts "Error while releasing signet version #{version}: #{e.message}" end else raise "Cannot build signet for version #{version}" end Rake::Task["kokoro:publish_docs"].invoke if gem_was_published end task :ci do header "Using Ruby - #{RUBY_VERSION}" sh "bundle exec rubocop" sh "bundle exec rspec" end namespace :kokoro do task :load_env_vars do service_account = "#{ENV['KOKORO_GFILE_DIR']}/service-account.json" ENV["GOOGLE_APPLICATION_CREDENTIALS"] = service_account filename = "#{ENV['KOKORO_GFILE_DIR']}/env_vars.json" env_vars = JSON.parse File.read(filename) env_vars.each { |k, v| ENV[k] = v } end task :presubmit do Rake::Task["ci"].invoke end task :continuous do Rake::Task["ci"].invoke end task :nightly do Rake::Task["ci"].invoke end task :release do version = "0.1.0" Bundler.with_clean_env do version = `bundle exec gem list` .split("\n").select { |line| line.include? "signet" } .first.split("(").last.split(")").first || "0.1.0" end Rake::Task["kokoro:load_env_vars"].invoke Rake::Task["release_gem"].invoke "v#{version}" end task :post do require_relative "rakelib/link_checker.rb" link_checker = LinkChecker.new link_checker.run exit link_checker.exit_status end task :publish_docs do require_relative "rakelib/devsite_builder.rb" DevsiteBuilder.new(__dir__).publish end end def header str, token = "#" line_length = str.length + 8 puts "" puts token * line_length puts "#{token * 3} #{str} #{token * 3}" puts token * line_length puts "" end signet-0.14.0/lib/0000755000004100000410000000000013673221365013673 5ustar www-datawww-datasignet-0.14.0/lib/signet.rb0000644000004100000410000000572613673221365015523 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "signet/version" module Signet #:nodoc: # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength def self.parse_auth_param_list auth_param_string # Production rules from: # http://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-12 token = /[-!#{$OUTPUT_RECORD_SEPARATOR}%&'*+.^_`|~0-9a-zA-Z]+/ d_qdtext = /[\s\x21\x23-\x5B\x5D-\x7E\x80-\xFF]/n d_quoted_pair = /\\[\s\x21-\x7E\x80-\xFF]/n d_qs = /"(?:#{d_qdtext}|#{d_quoted_pair})*"/ # Production rules that allow for more liberal parsing, i.e. single quotes s_qdtext = /[\s\x21-\x26\x28-\x5B\x5D-\x7E\x80-\xFF]/n s_quoted_pair = /\\[\s\x21-\x7E\x80-\xFF]/n s_qs = /'(?:#{s_qdtext}|#{s_quoted_pair})*'/ # Combine the above production rules to find valid auth-param pairs. auth_param = /((?:#{token})\s*=\s*(?:#{d_qs}|#{s_qs}|#{token}))/ auth_param_pairs = [] last_match = nil remainder = auth_param_string # Iterate over the string, consuming pair matches as we go. Verify that # pre-matches and post-matches contain only allowable characters. # # This would be way easier in Ruby 1.9, but we want backwards # compatibility. while (match = remainder.match auth_param) if match.pre_match && match.pre_match !~ /^[\s,]*$/ raise ParseError, "Unexpected auth param format: '#{auth_param_string}'." end auth_param_pairs << match.captures[0] # Appending pair remainder = match.post_match last_match = match end if last_match.post_match && last_match.post_match !~ /^[\s,]*$/ raise ParseError, "Unexpected auth param format: '#{auth_param_string}'." end # Now parse the auth-param pair strings & turn them into key-value pairs. (auth_param_pairs.each_with_object [] do |pair, accu| name, value = pair.split "=", 2 if value =~ /^".*"$/ value = value.gsub(/^"(.*)"$/, '\1').gsub(/\\(.)/, '\1') elsif value =~ /^'.*'$/ value = value.gsub(/^'(.*)'$/, '\1').gsub(/\\(.)/, '\1') elsif value =~ %r{[\(\)<>@,;:\\\"/\[\]?={}]} # Certain special characters are not allowed raise ParseError, "Unexpected characters in auth param " \ "list: '#{auth_param_string}'." end accu << [name, value] end) end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength end signet-0.14.0/lib/signet/0000755000004100000410000000000013673221365015164 5ustar www-datawww-datasignet-0.14.0/lib/signet/oauth_1/0000755000004100000410000000000013673221365016524 5ustar www-datawww-datasignet-0.14.0/lib/signet/oauth_1/signature_methods/0000755000004100000410000000000013673221365022250 5ustar www-datawww-datasignet-0.14.0/lib/signet/oauth_1/signature_methods/rsa_sha1.rb0000644000004100000410000000107313673221365024277 0ustar www-datawww-datarequire "digest/sha1" require "base64" require "openssl" require "signet" module Signet #:nodoc: module OAuth1 module RSASHA1 def self.generate_signature \ base_string, client_credential_secret, _token_credential_secret private_key = OpenSSL::PKey::RSA.new client_credential_secret signature = private_key.sign OpenSSL::Digest::SHA1.new, base_string # using strict_encode64 because the encode64 method adds newline characters after ever 60 chars Base64.strict_encode64(signature).strip end end end end signet-0.14.0/lib/signet/oauth_1/signature_methods/hmac_sha1.rb0000644000004100000410000000160313673221365024421 0ustar www-datawww-datarequire "openssl" require "signet" module Signet #:nodoc: module OAuth1 module HMACSHA1 def self.generate_signature \ base_string, client_credential_secret, token_credential_secret # Both the client secret and token secret must be escaped client_credential_secret = Signet::OAuth1.encode client_credential_secret token_credential_secret = Signet::OAuth1.encode token_credential_secret # The key for the signature is just the client secret and token # secret joined by the '&' character. If the token secret is omitted, # the '&' must still be present. key = [client_credential_secret, token_credential_secret].join "&" Base64.encode64(OpenSSL::HMAC.digest( OpenSSL::Digest.new("sha1"), key, base_string )).strip end end end end signet-0.14.0/lib/signet/oauth_1/signature_methods/plaintext.rb0000644000004100000410000000140513673221365024605 0ustar www-datawww-datarequire "signet" module Signet #:nodoc: module OAuth1 module PLAINTEXT def self.generate_signature \ _base_string, client_credential_secret, token_credential_secret # Both the client secret and token secret must be escaped client_credential_secret = Signet::OAuth1.encode client_credential_secret token_credential_secret = Signet::OAuth1.encode token_credential_secret # The key for the signature is just the client secret and token # secret joined by the '&' character. If the token secret is omitted, # the '&' must still be present. key = [client_credential_secret, token_credential_secret].join "&" Signet::OAuth1.encode(key).strip end end end end signet-0.14.0/lib/signet/oauth_1/client.rb0000644000004100000410000011473213673221365020337 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "faraday" # require 'faraday/utils' require "stringio" require "addressable/uri" require "signet" require "signet/errors" require "signet/oauth_1" require "signet/oauth_1/credential" module Signet module OAuth1 class Client ## # Creates an OAuth 1.0 client. # # @param [Hash] options # The configuration parameters for the client. # - :temporary_credential_uri - # The OAuth temporary credentials URI. # - :authorization_uri - # The OAuth authorization URI. # - :token_credential_uri - # The OAuth token credentials URI. # - :client_credential_key - # The OAuth client credential key. # - :client_credential_secret - # The OAuth client credential secret. # - :callback - The OAuth callback. Defaults to 'oob'. # # @example # client = Signet::OAuth1::Client.new( # :temporary_credential_uri => # 'https://www.google.com/accounts/OAuthGetRequestToken', # :authorization_uri => # 'https://www.google.com/accounts/OAuthAuthorizeToken', # :token_credential_uri => # 'https://www.google.com/accounts/OAuthGetAccessToken', # :client_credential_key => 'anonymous', # :client_credential_secret => 'anonymous' # ) def initialize options = {} update! options end ## # Updates an OAuth 1.0 client. # # @param [Hash] options # The configuration parameters for the client. # - :temporary_credential_uri - # The OAuth temporary credentials URI. # - :authorization_uri - # The OAuth authorization URI. # - :token_credential_uri - # The OAuth token credentials URI. # - :client_credential_key - # The OAuth client credential key. # - :client_credential_secret - # The OAuth client credential secret. # - :callback - The OAuth callback. Defaults to 'oob'. # # @example # client.update!( # :temporary_credential_uri => # 'https://www.google.com/accounts/OAuthGetRequestToken', # :authorization_uri => # 'https://www.google.com/accounts/OAuthAuthorizeToken', # :token_credential_uri => # 'https://www.google.com/accounts/OAuthGetAccessToken', # :client_credential_key => 'anonymous', # :client_credential_secret => 'anonymous' # ) # # @see Signet::OAuth1::Client#initialize def update! options = {} # Normalize key to String to allow indifferent access. options = options.each_with_object({}) { |(k, v), accu| accu[k.to_s] = v; } self.temporary_credential_uri = options["temporary_credential_uri"] self.authorization_uri = options["authorization_uri"] self.token_credential_uri = options["token_credential_uri"] # Technically... this would allow you to pass in a :client key... # But that would be weird. Don't do that. self.client_credential_key = Signet::OAuth1.extract_credential_key_option "client", options self.client_credential_secret = Signet::OAuth1.extract_credential_secret_option "client", options self.temporary_credential_key = Signet::OAuth1.extract_credential_key_option "temporary", options self.temporary_credential_secret = Signet::OAuth1.extract_credential_secret_option "temporary", options self.token_credential_key = Signet::OAuth1.extract_credential_key_option "token", options self.token_credential_secret = Signet::OAuth1.extract_credential_secret_option "token", options self.callback = options["callback"] self.two_legged = options["two_legged"] || false self end ## # Returns the temporary credentials URI for this client. # # @return [Addressable::URI] The temporary credentials URI. def temporary_credential_uri @temporary_credential_uri end alias request_token_uri temporary_credential_uri ## # Sets the temporary credentials URI for this client. # # @param [Addressable::URI, String, #to_str] # new_temporary_credential_uri # The temporary credentials URI. def temporary_credential_uri= new_temporary_credential_uri if !new_temporary_credential_uri.nil? new_temporary_credential_uri = Addressable::URI.parse new_temporary_credential_uri @temporary_credential_uri = new_temporary_credential_uri else @temporary_credential_uri = nil end end alias request_token_uri= temporary_credential_uri= ## # Returns the authorization URI that the user should be redirected to. # # @return [Addressable::URI] The authorization URI. # # @see Signet::OAuth1.generate_authorization_uri def authorization_uri options = {} options = options.merge( temporary_credential_key: temporary_credential_key, callback: callback ) return nil if @authorization_uri.nil? Addressable::URI.parse( ::Signet::OAuth1.generate_authorization_uri( @authorization_uri, options ) ) end ## # Sets the authorization URI for this client. # # @param [Addressable::URI, String, #to_str] new_authorization_uri # The authorization URI. def authorization_uri= new_authorization_uri if !new_authorization_uri.nil? new_authorization_uri = Addressable::URI.send( new_authorization_uri.is_a?(Hash) ? :new : :parse, new_authorization_uri ) @authorization_uri = new_authorization_uri else @authorization_uri = nil end end ## # Returns the token credential URI for this client. # # @return [Addressable::URI] The token credential URI. def token_credential_uri @token_credential_uri end alias access_token_uri token_credential_uri ## # Sets the token credential URI for this client. # # @param [Addressable::URI, Hash, String, #to_str] new_token_credential_uri # The token credential URI. def token_credential_uri= new_token_credential_uri if !new_token_credential_uri.nil? new_token_credential_uri = Addressable::URI.send( new_token_credential_uri.is_a?(Hash) ? :new : :parse, new_token_credential_uri ) @token_credential_uri = new_token_credential_uri else @token_credential_uri = nil end end alias access_token_uri= token_credential_uri= # Lots of duplicated code here, but for the sake of auto-generating # documentation, we're going to let it slide. Oh well. ## # Returns the client credential for this client. # # @return [Signet::OAuth1::Credential] The client credentials. def client_credential if client_credential_key && client_credential_secret ::Signet::OAuth1::Credential.new( client_credential_key, client_credential_secret ) elsif !client_credential_key && !client_credential_secret nil else raise ArgumentError, "The client credential key and secret must be set." end end alias consumer_token client_credential ## # Sets the client credential for this client. # # @param [Signet::OAuth1::Credential] new_client_credential # The client credentials. def client_credential= new_client_credential if !new_client_credential.nil? unless new_client_credential.is_a? ::Signet::OAuth1::Credential raise TypeError, "Expected Signet::OAuth1::Credential, " \ "got #{new_client_credential.class}." end @client_credential_key = new_client_credential.key @client_credential_secret = new_client_credential.secret else @client_credential_key = nil @client_credential_secret = nil end end alias consumer_token= client_credential= ## # Returns the client credential key for this client. # # @return [String] The client credential key. def client_credential_key @client_credential_key end alias consumer_key client_credential_key ## # Sets the client credential key for this client. # # @param [String, #to_str] new_client_credential_key # The client credential key. def client_credential_key= new_client_credential_key if !new_client_credential_key.nil? unless new_client_credential_key.respond_to? :to_str raise TypeError, "Can't convert #{new_client_credential_key.class} into String." end new_client_credential_key = new_client_credential_key.to_str @client_credential_key = new_client_credential_key else @client_credential_key = nil end end alias consumer_key= client_credential_key= ## # Returns the client credential secret for this client. # # @return [String] The client credential secret. def client_credential_secret @client_credential_secret end alias consumer_secret client_credential_secret ## # Sets the client credential secret for this client. # # @param [String, #to_str] new_client_credential_secret # The client credential secret. def client_credential_secret= new_client_credential_secret if !new_client_credential_secret.nil? unless new_client_credential_secret.respond_to? :to_str raise TypeError, "Can't convert #{new_client_credential_secret.class} " \ "into String." end new_client_credential_secret = new_client_credential_secret.to_str @client_credential_secret = new_client_credential_secret else @client_credential_secret = nil end end alias consumer_secret= client_credential_secret= ## # Returns the temporary credential for this client. # # @return [Signet::OAuth1::Credential] The temporary credentials. def temporary_credential if temporary_credential_key && temporary_credential_secret ::Signet::OAuth1::Credential.new( temporary_credential_key, temporary_credential_secret ) elsif !temporary_credential_key && !temporary_credential_secret nil else raise ArgumentError, "The temporary credential key and secret must be set." end end alias request_token temporary_credential ## # Sets the temporary credential for this client. # # @param [Signet::OAuth1::Credential] new_temporary_credential # The temporary credentials. def temporary_credential= new_temporary_credential if !new_temporary_credential.nil? unless new_temporary_credential.is_a? ::Signet::OAuth1::Credential raise TypeError, "Expected Signet::OAuth1::Credential, " \ "got #{new_temporary_credential.class}." end @temporary_credential_key = new_temporary_credential.key @temporary_credential_secret = new_temporary_credential.secret else @temporary_credential_key = nil @temporary_credential_secret = nil end end alias request_token= temporary_credential= ## # Returns the temporary credential key for this client. # # @return [String] The temporary credential key. def temporary_credential_key @temporary_credential_key end alias request_token_key temporary_credential_key ## # Sets the temporary credential key for this client. # # @param [String, #to_str] new_temporary_credential_key # The temporary credential key. def temporary_credential_key= new_temporary_credential_key if !new_temporary_credential_key.nil? unless new_temporary_credential_key.respond_to? :to_str raise TypeError, "Can't convert #{new_temporary_credential_key.class} " \ "into String." end new_temporary_credential_key = new_temporary_credential_key.to_str @temporary_credential_key = new_temporary_credential_key else @temporary_credential_key = nil end end alias request_token_key= temporary_credential_key= ## # Returns the temporary credential secret for this client. # # @return [String] The temporary credential secret. def temporary_credential_secret @temporary_credential_secret end alias request_token_secret temporary_credential_secret ## # Sets the temporary credential secret for this client. # # @param [String, #to_str] new_temporary_credential_secret # The temporary credential secret. def temporary_credential_secret= new_temporary_credential_secret if !new_temporary_credential_secret.nil? unless new_temporary_credential_secret.respond_to? :to_str raise TypeError, "Can't convert #{new_temporary_credential_secret.class} " \ "into String." end new_temporary_credential_secret = new_temporary_credential_secret.to_str @temporary_credential_secret = new_temporary_credential_secret else @temporary_credential_secret = nil end end alias request_token_secret= temporary_credential_secret= ## # Returns the token credential for this client. # # @return [Signet::OAuth1::Credential] The token credentials. def token_credential if token_credential_key && token_credential_secret ::Signet::OAuth1::Credential.new( token_credential_key, token_credential_secret ) elsif !token_credential_key && !token_credential_secret nil else raise ArgumentError, "The token credential key and secret must be set." end end alias access_token token_credential ## # Sets the token credential for this client. # # @param [Signet::OAuth1::Credential] new_token_credential # The token credentials. def token_credential= new_token_credential if !new_token_credential.nil? unless new_token_credential.is_a? ::Signet::OAuth1::Credential raise TypeError, "Expected Signet::OAuth1::Credential, " \ "got #{new_token_credential.class}." end @token_credential_key = new_token_credential.key @token_credential_secret = new_token_credential.secret else @token_credential_key = nil @token_credential_secret = nil end end alias access_token= token_credential= ## # Returns the token credential key for this client. # # @return [String] The token credential key. def token_credential_key @token_credential_key end alias access_token_key token_credential_key ## # Sets the token credential key for this client. # # @param [String, #to_str] new_token_credential_key # The token credential key. def token_credential_key= new_token_credential_key if !new_token_credential_key.nil? unless new_token_credential_key.respond_to? :to_str raise TypeError, "Can't convert #{new_token_credential_key.class} " \ "into String." end new_token_credential_key = new_token_credential_key.to_str @token_credential_key = new_token_credential_key else @token_credential_key = nil end end alias access_token_key= token_credential_key= ## # Returns the token credential secret for this client. # # @return [String] The token credential secret. def token_credential_secret @token_credential_secret end alias access_token_secret token_credential_secret ## # Sets the token credential secret for this client. # # @param [String, #to_str] new_token_credential_secret # The token credential secret. def token_credential_secret= new_token_credential_secret if !new_token_credential_secret.nil? unless new_token_credential_secret.respond_to? :to_str raise TypeError, "Can't convert #{new_token_credential_secret.class} " \ "into String." end new_token_credential_secret = new_token_credential_secret.to_str @token_credential_secret = new_token_credential_secret else @token_credential_secret = nil end end alias access_token_secret= token_credential_secret= ## # Returns the callback for this client. # # @return [String] The OAuth callback. def callback @callback || ::Signet::OAuth1::OUT_OF_BAND end ## # Sets the callback for this client. # # @param [String, #to_str] new_callback # The OAuth callback. def callback= new_callback if !new_callback.nil? unless new_callback.respond_to? :to_str raise TypeError, "Can't convert #{new_callback.class} into String." end new_callback = new_callback.to_str @callback = new_callback else @callback = nil end end ## # Returns whether the client is in two-legged mode. # # @return [TrueClass, FalseClass] # true for two-legged mode, false otherwise. def two_legged @two_legged ||= false end ## # Sets the client for two-legged mode. # # @param [TrueClass, FalseClass] new_two_legged # true for two-legged mode, false otherwise. def two_legged= new_two_legged if new_two_legged != true && new_two_legged != false raise TypeError, "Expected true or false, got #{new_two_legged.class}." else @two_legged = new_two_legged end end ## # Serialize the client object to JSON. # # @note A serialized client contains sensitive information. Persist or transmit with care. # # @return [String] A serialized JSON representation of the client. def to_json MultiJson.dump( "temporary_credential_uri" => temporary_credential_uri, "authorization_uri" => authorization_uri, "token_credential_uri" => token_credential_uri, "callback" => callback, "two_legged" => two_legged, "client_credential_key" => client_credential_key, "client_credential_secret" => client_credential_secret, "temporary_credential_key" => temporary_credential_key, "temporary_credential_secret" => temporary_credential_secret, "token_credential_key" => token_credential_key, "token_credential_secret" => token_credential_secret ) end # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength ## # Generates a request for temporary credentials. # # @param [Hash] options # The configuration parameters for the request. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # - :additional_parameters - # Non-standard additional parameters. # - :realm - # The Authorization realm. See RFC 2617. # # @return [Array] The request object. def generate_temporary_credential_request options = {} verifications = { temporary_credential_uri: "Temporary credentials URI", client_credential_key: "Client credential key", client_credential_secret: "Client credential secret" } # Make sure all required state is set verifications.each do |(key, _value)| raise ArgumentError, "#{key} was not set." unless send key end options = { signature_method: "HMAC-SHA1", additional_parameters: [], realm: nil, connection: Faraday.default_connection }.merge(options) method = :post parameters = ::Signet::OAuth1.unsigned_temporary_credential_parameters( client_credential_key: client_credential_key, callback: callback, signature_method: options[:signature_method], additional_parameters: options[:additional_parameters] ) signature = ::Signet::OAuth1.sign_parameters( method, temporary_credential_uri, parameters, client_credential_secret ) parameters << ["oauth_signature", signature] authorization_header = [ "Authorization", ::Signet::OAuth1.generate_authorization_header( parameters, options[:realm] ) ] headers = [authorization_header] if method == :post headers << ["Content-Type", "application/x-www-form-urlencoded"] headers << ["Content-Length", "0"] end options[:connection].build_request method.to_s.downcase.to_sym do |req| req.url(Addressable::URI.parse( temporary_credential_uri.to_str ).normalize.to_s) req.headers = Faraday::Utils::Headers.new headers end end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength alias generate_request_token_request generate_temporary_credential_request ## # Transmits a request for a temporary credential. This method does not # have side-effects within the client. # # @param [Hash] options # The configuration parameters for the request. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # - :additional_parameters - # Non-standard additional parameters. # - :realm - # The Authorization realm. See RFC 2617. # - :connection - # The HTTP connection to use. # Must be of type Faraday::Connection. # # @return [Signet::OAuth1::Credential] The temporary credential. # # @example # temporary_credential = client.fetch_temporary_credential( # :additional_parameters => { # :scope => 'https://mail.google.com/mail/feed/atom' # } # ) def fetch_temporary_credential options = {} options[:connection] ||= Faraday.default_connection request = generate_temporary_credential_request options request_env = request.to_env options[:connection] request_env[:request] ||= request response = options[:connection].app.call request_env return ::Signet::OAuth1.parse_form_encoded_credentials response.body if response.status.to_i == 200 message = if [400, 401, 403].include? response.status.to_i "Authorization failed." else "Unexpected status code: #{response.status}." end message += " Server message:\n#{response.body.to_s.strip}" unless response.body.to_s.strip.empty? raise ::Signet::AuthorizationError.new( message, request: request, response: response ) end alias fetch_request_token fetch_temporary_credential ## # Transmits a request for a temporary credential. This method updates # the client with the new temporary credential. # # @param [Hash] options # The configuration parameters for the request. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # - :additional_parameters - # Non-standard additional parameters. # - :realm - # The Authorization realm. See RFC 2617. # - :connection - # The HTTP connection to use. # Must be of type Faraday::Connection. # # @return [Signet::OAuth1::Credential] The temporary credential. # # @example # client.fetch_temporary_credential!(:additional_parameters => { # :scope => 'https://mail.google.com/mail/feed/atom' # }) def fetch_temporary_credential! options = {} credential = fetch_temporary_credential options self.temporary_credential = credential end alias fetch_request_token! fetch_temporary_credential! # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength ## # Generates a request for token credentials. # # @param [Hash] options # The configuration parameters for the request. # - :verifier - # The OAuth verifier provided by the server. Required. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # - :realm - # The Authorization realm. See RFC 2617. # # @return [Array] The request object. def generate_token_credential_request options = {} verifications = { token_credential_uri: "Token credentials URI", client_credential_key: "Client credential key", client_credential_secret: "Client credential secret", temporary_credential_key: "Temporary credential key", temporary_credential_secret: "Temporary credential secret" } # Make sure all required state is set verifications.each do |(key, _value)| raise ArgumentError, "#{key} was not set." unless send key end options = { signature_method: "HMAC-SHA1", realm: nil, connection: Faraday.default_connection }.merge(options) method = :post parameters = ::Signet::OAuth1.unsigned_token_credential_parameters( client_credential_key: client_credential_key, temporary_credential_key: temporary_credential_key, signature_method: options[:signature_method], verifier: options[:verifier] ) signature = ::Signet::OAuth1.sign_parameters( method, token_credential_uri, parameters, client_credential_secret, temporary_credential_secret ) parameters << ["oauth_signature", signature] authorization_header = [ "Authorization", ::Signet::OAuth1.generate_authorization_header( parameters, options[:realm] ) ] headers = [authorization_header] headers << ["Cache-Control", "no-store"] if method == :post headers << ["Content-Type", "application/x-www-form-urlencoded"] headers << ["Content-Length", "0"] end options[:connection].build_request method.to_s.downcase.to_sym do |req| req.url(Addressable::URI.parse( token_credential_uri.to_str ).normalize.to_s) req.headers = Faraday::Utils::Headers.new headers end end # rubocop:enable Metrics/MethodLength alias generate_access_token_request generate_token_credential_request ## # Transmits a request for a token credential. This method does not # have side-effects within the client. # # @param [Hash] options # The configuration parameters for the request. # - :verifier - # The OAuth verifier provided by the server. Required. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # - :realm - # The Authorization realm. See RFC 2617. # - :connection - # The HTTP connection to use. # Must be of type Faraday::Connection. # # @return [Signet::OAuth1::Credential] The token credential. # # @example # token_credential = client.fetch_token_credential( # :verifier => '12345' # ) def fetch_token_credential options = {} options[:connection] ||= Faraday.default_connection request = generate_token_credential_request options request_env = request.to_env options[:connection] request_env[:request] ||= request response = options[:connection].app.call request_env return ::Signet::OAuth1.parse_form_encoded_credentials response.body if response.status.to_i == 200 message = if [400, 401, 403].include? response.status.to_i "Authorization failed." else "Unexpected status code: #{response.status}." end message += " Server message:\n#{response.body.to_s.strip}" unless response.body.to_s.strip.empty? raise ::Signet::AuthorizationError.new( message, request: request, response: response ) end # rubocop:enable Metrics/AbcSize alias fetch_access_token fetch_token_credential ## # Transmits a request for a token credential. This method updates # the client with the new token credential. # # @param [Hash] options # The configuration parameters for the request. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # - :additional_parameters - # Non-standard additional parameters. # - :realm - # The Authorization realm. See RFC 2617. # - :connection - # The HTTP connection to use. # Must be of type Faraday::Connection. # # @return [Signet::OAuth1::Credential] The token credential. # # @example # client.fetch_token_credential!(:verifier => '12345') def fetch_token_credential! options = {} credential = fetch_token_credential options self.token_credential = credential end alias fetch_access_token! fetch_token_credential! # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/PerceivedComplexity ## # Generates an authenticated request for protected resources. # # @param [Hash] options # The configuration parameters for the request. # - :request - # A pre-constructed request to sign. # - :method - # The HTTP method for the request. Defaults to :get. # - :uri - # The URI for the request. # - :headers - # The HTTP headers for the request. # - :body - # The HTTP body for the request. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # - :realm - # The Authorization realm. See RFC 2617. # # @return [Array] The request object. def generate_authenticated_request options = {} verifications = { client_credential_key: "Client credential key", client_credential_secret: "Client credential secret" } unless two_legged verifications.update( token_credential_key: "Token credential key", token_credential_secret: "Token credential secret" ) end # Make sure all required state is set verifications.each do |(key, _value)| raise ArgumentError, "#{key} was not set." unless send key end options = { signature_method: "HMAC-SHA1", realm: nil, connection: Faraday.default_connection }.merge(options) if options[:request].is_a? Faraday::Request request = options[:request] else if options[:request].is_a? Array method, uri, headers, body = options[:request] else method = options[:method] || :get uri = options[:uri] headers = options[:headers] || [] body = options[:body] || "" end headers = headers.to_a if headers.is_a? Hash request_components = { method: method, uri: uri, headers: headers, body: body } # Verify that we have all pieces required to return an HTTP request request_components.each do |(key, value)| raise ArgumentError, "Missing :#{key} parameter." unless value end if !body.is_a?(String) && body.respond_to?(:each) # Just in case we get a chunked body merged_body = StringIO.new body.each do |chunk| merged_body.write chunk end body = merged_body.string end raise TypeError, "Expected String, got #{body.class}." unless body.is_a? String method = method.to_s.downcase.to_sym request = options[:connection].build_request method do |req| req.url Addressable::URI.parse(uri).normalize.to_s req.headers = Faraday::Utils::Headers.new headers req.body = body end end parameters = ::Signet::OAuth1.unsigned_resource_parameters( client_credential_key: client_credential_key, token_credential_key: token_credential_key, signature_method: options[:signature_method], two_legged: two_legged ) env = request.to_env options[:connection] content_type = request["Content-Type"].to_s content_type = content_type.split(";", 2).first if content_type.index ";" if request.method == :post && content_type == "application/x-www-form-urlencoded" # Serializes the body in case a hash/array was passed. Noop if already string like encoder = Faraday::Request::UrlEncoded.new(->(_env) {}) encoder.call env request.body = env[:body] post_parameters = Addressable::URI.form_unencode env[:body] parameters = parameters.concat post_parameters end # No need to attach URI query parameters, the .sign_parameters # method takes care of that automatically. signature = ::Signet::OAuth1.sign_parameters( env[:method], env[:url], parameters, client_credential_secret, token_credential_secret ) parameters << ["oauth_signature", signature] request["Authorization"] = ::Signet::OAuth1.generate_authorization_header( parameters, options[:realm] ) request["Cache-Control"] = "no-store" request end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/PerceivedComplexity ## # Transmits a request for a protected resource. # # @param [Hash] options # The configuration parameters for the request. # - :request - # A pre-constructed request to sign. # - :method - # The HTTP method for the request. Defaults to :get. # - :uri - # The URI for the request. # - :headers - # The HTTP headers for the request. # - :body - # The HTTP body for the request. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # - :realm - # The Authorization realm. See RFC 2617. # - :connection - # The HTTP connection to use. # Must be of type Faraday::Connection. # # @example # # Using Net::HTTP # response = client.fetch_protected_resource( # :uri => 'http://www.example.com/protected/resource' # ) # # @example # # Using Typhoeus # response = client.fetch_protected_resource( # :request => Typhoeus::Request.new( # 'http://www.example.com/protected/resource' # ), # :connection => connection # ) # # @return [Array] The response object. def fetch_protected_resource options = {} options[:connection] ||= Faraday.default_connection request = generate_authenticated_request options request_env = request.to_env options[:connection] request_env[:request] ||= request response = options[:connection].app.call request_env return response unless response.status.to_i == 401 # When accessing a protected resource, we only want to raise an # error for 401 responses. message = "Authorization failed." message += " Server message:\n#{response.body.to_s.strip}" unless response.body.to_s.strip.empty? raise ::Signet::AuthorizationError.new( message, request: request, response: response ) end end end end signet-0.14.0/lib/signet/oauth_1/server.rb0000644000004100000410000005161513673221365020367 0ustar www-datawww-data# Copyright (C) 2011 The Yakima Herald-Republic. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # require "faraday" require "stringio" require "addressable/uri" require "signet" require "signet/errors" require "signet/oauth_1" require "signet/oauth_1/credential" module Signet module OAuth1 class Server # @return [Proc] lookup the value from this Proc. attr_accessor :nonce_timestamp, :client_credential, :token_credential, :temporary_credential, :verifier ## # Creates an OAuth 1.0 server. # @overload initialize(options) # @param [Proc] nonce_timestamp verify a nonce/timestamp pair. # @param [Proc] client_credential find a client credential. # @param [Proc] token_credential find a token credential. # @param [Proc] temporary_credential find a temporary credential. # @param [Proc] verifier validate a verifier value. # # @example # server = Signet::OAuth1::Server.new( # :nonce_timestamp => # lambda { |n,t| OauthNonce.remember(n,t) }, # :client_credential => # lambda { |key| ClientCredential.find_by_key(key).to_hash }, # :token_credential => # lambda { |key| TokenCredential.find_by_key(key).to_hash }, # :temporary_credential => # lambda { |key| TemporaryCredential.find_by_key(key).to_hash }, # :verifier => # lambda {|verifier| Verifier.find_by_verifier(verifier).active? } # ) def initialize options = {} [:nonce_timestamp, :client_credential, :token_credential, :temporary_credential, :verifier].each do |attr| instance_variable_set "@#{attr}", options[attr] end end # rubocop:disable Naming/UncommunicativeMethodParamName # Constant time string comparison. def safe_equals? a, b check = a.bytesize ^ b.bytesize a.bytes.zip(b.bytes) { |x, y| check |= x ^ y.to_i } check.zero? end # rubocop:enable Naming/UncommunicativeMethodParamName ## # Determine if the supplied nonce/timestamp pair is valid by calling # the {#nonce_timestamp} Proc. # # @param [String, #to_str] nonce value from the request # @param [String, #to_str] timestamp value from the request # @return [Boolean] if the nonce/timestamp pair is valid. def validate_nonce_timestamp nonce, timestamp if @nonce_timestamp.respond_to? :call nonce = @nonce_timestamp.call nonce, timestamp end nonce ? true : false end ## # Find the appropriate client credential by calling # the {#client_credential} Proc. # # @param [String] key provided to the {#client_credential} Proc. # @return [Signet::OAuth1::Credential] The client credential. def find_client_credential key call_credential_lookup @client_credential, key end ## # Find the appropriate client credential by calling # the {#token_credential} Proc. # # @param [String] key provided to the {#token_credential} Proc. # @return [Signet::OAuth1::Credential] if the credential is found. def find_token_credential key call_credential_lookup @token_credential, key end ## # Find the appropriate client credential by calling # the {#temporary_credential} Proc. # # @param [String] key provided to the {#temporary_credential} Proc. # @return [Signet::OAuth1::Credential] if the credential is found. def find_temporary_credential key call_credential_lookup @temporary_credential, key end ## # Call a credential lookup, and cast the result to a proper Credential. # # @param [Proc] credential to call. # @param [String] key provided to the Proc in credential # @return [Signet::OAuth1::Credential] credential provided by # credential (if any). def call_credential_lookup credential, key cred = credential.call key if credential.respond_to? :call return nil if cred.nil? return nil unless cred.respond_to?(:to_str) || cred.respond_to?(:to_ary) || cred.respond_to?(:to_hash) if cred.instance_of? ::Signet::OAuth1::Credential cred else ::Signet::OAuth1::Credential.new cred end end ## # Determine if the verifier is valid by calling the Proc in {#verifier}. # # @param [String] verifier Key provided to the {#verifier} Proc. # @return [Boolean] if the verifier Proc returns anything other than # nil or false. def find_verifier verifier verified = @verifier.call verifier if @verifier.respond_to? :call verified ? true : false end # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/PerceivedComplexity ## # Validate and normalize the components from an HTTP request. # @overload verify_request_components(options) # @param [Faraday::Request] request A pre-constructed request to verify. # @param [String] method the HTTP method , defaults to GET # @param [Addressable::URI, String] uri the URI . # @param [Hash, Array] headers the HTTP headers. # @param [StringIO, String] body The HTTP body. # @param [HTTPAdapter] adapter The HTTP adapter(optional). # @return [Hash] normalized request components def verify_request_components options = {} if options[:request] if options[:request].is_a?(Faraday::Request) || options[:request].is_a?(Array) request = options[:request] elsif options[:adapter] request = options[:adapter].adapt_request options[:request] end method = request.method uri = request.path headers = request.headers body = request.body else method = options[:method] || :get uri = options[:uri] headers = options[:headers] || [] body = options[:body] || "" end headers = headers.to_a if headers.is_a? Hash method = method.to_s.upcase request_components = { method: method, uri: uri, headers: headers } # Verify that we have all the pieces required to validate the HTTP request request_components.each do |(key, value)| raise ArgumentError, "Missing :#{key} parameter." unless value end request_components[:body] = body request_components end # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/PerceivedComplexity ## # Validate and normalize the HTTP Authorization header. # # @param [Array] headers from HTTP request. # @return [Hash] Hash of Authorization header. def verify_auth_header_components headers auth_header = headers.find { |x| x[0] == "Authorization" } raise MalformedAuthorizationError, "Authorization header is missing" if auth_header.nil? || auth_header[1] == "" auth_hash = ::Signet::OAuth1.parse_authorization_header( auth_header[1] ).each_with_object({}) { |(key, val), acc| acc[key.downcase] = val; } auth_hash end ## # @overload request_realm(options) # @param [Hash] request A pre-constructed request to verify. # @param [String] method the HTTP method , defaults to GET # @param [Addressable::URI, String] uri the URI . # @param [Hash, Array] headers the HTTP headers. # @param [StringIO, String] body The HTTP body. # @param [HTTPAdapter] adapter The HTTP adapter(optional). # @return [String] The Authorization realm(see RFC 2617) of the request. def request_realm options = {} request_components = if options[:request] verify_request_components( request: options[:request], adapter: options[:adapter] ) else verify_request_components( method: options[:method], uri: options[:uri], headers: options[:headers], body: options[:body] ) end auth_header = request_components[:headers].find { |x| x[0] == "Authorization" } raise MalformedAuthorizationError, "Authorization header is missing" if auth_header.nil? || auth_header[1] == "" auth_hash = ::Signet::OAuth1.parse_authorization_header( auth_header[1] ).each_with_object({}) { |(key, val), acc| acc[key.downcase] = val; } auth_hash["realm"] end # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/PerceivedComplexity ## # Authenticates a temporary credential request. If no oauth_callback is # present in the request, oob will be returned. # # @overload authenticate_temporary_credential_request(options) # @param [Hash] request The configuration parameters for the request. # @param [String] method the HTTP method , defaults to GET # @param [Addressable::URI, String] uri the URI . # @param [Hash, Array] headers the HTTP headers. # @param [StringIO, String] body The HTTP body. # @param [HTTPAdapter] adapter The HTTP adapter(optional). # @return [String] The oauth_callback value, or false if not valid. def authenticate_temporary_credential_request options = {} verifications = { client_credential: lambda { |_x| ::Signet::OAuth1::Credential.new("Client credential key", "Client credential secret") } } verifications.each do |(key, _value)| raise ArgumentError, "#{key} was not set." unless send key end request_components = if options[:request] verify_request_components( request: options[:request], adapter: options[:adapter] ) else verify_request_components( method: options[:method], uri: options[:uri], headers: options[:headers] ) end # body should be blank; we don't care in any case. method = request_components[:method] uri = request_components[:uri] headers = request_components[:headers] auth_hash = verify_auth_header_components headers return false unless (client_credential = find_client_credential( auth_hash["oauth_consumer_key"] )) return false unless validate_nonce_timestamp(auth_hash["oauth_nonce"], auth_hash["oauth_timestamp"]) client_credential_secret = client_credential.secret if client_credential computed_signature = ::Signet::OAuth1.sign_parameters( method, uri, # Realm isn't used, and will throw the signature off. auth_hash.reject { |k, _v| k == "realm" }.to_a, client_credential_secret, nil ) if safe_equals? computed_signature, auth_hash["oauth_signature"] if auth_hash.fetch("oauth_callback", "oob").empty? "oob" else auth_hash.fetch "oauth_callback" end else false end end # rubocop:enable Metrics/PerceivedComplexity ## # Authenticates a token credential request. # @overload authenticate_token_credential_request(options) # @param [Hash] request The configuration parameters for the request. # @param [String] method the HTTP method , defaults to GET # @param [Addressable::URI, String] uri the URI . # @param [Hash, Array] headers the HTTP headers. # @param [StringIO, String] body The HTTP body. # @param [HTTPAdapter] adapter The HTTP adapter(optional). # @return [Hash] A hash of credentials and realm for a valid request, # or nil if not valid. def authenticate_token_credential_request options = {} verifications = { client_credential: lambda { |_x| ::Signet::OAuth1::Credential.new("Client credential key", "Client credential secret") }, temporary_credential: lambda { |_x| ::Signet::OAuth1::Credential.new("Temporary credential key", "Temporary credential secret") }, verifier: ->(_x) { "Verifier" } } verifications.each do |(key, _value)| raise ArgumentError, "#{key} was not set." unless send key end request_components = if options[:request] verify_request_components( request: options[:request], adapter: options[:adapter] ) else verify_request_components( method: options[:method], uri: options[:uri], headers: options[:headers], body: options[:body] ) end # body should be blank; we don't care in any case. method = request_components[:method] uri = request_components[:uri] headers = request_components[:headers] auth_hash = verify_auth_header_components headers return false unless ( client_credential = find_client_credential auth_hash["oauth_consumer_key"] ) return false unless ( temporary_credential = find_temporary_credential auth_hash["oauth_token"] ) return false unless validate_nonce_timestamp( auth_hash["oauth_nonce"], auth_hash["oauth_timestamp"] ) computed_signature = ::Signet::OAuth1.sign_parameters( method, uri, # Realm isn't used, and will throw the signature off. auth_hash.reject { |k, _v| k == "realm" }.to_a, client_credential.secret, temporary_credential.secret ) return nil unless safe_equals? computed_signature, auth_hash["oauth_signature"] { client_credential: client_credential, temporary_credential: temporary_credential, realm: auth_hash["realm"] } end # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/PerceivedComplexity ## # Authenticates a request for a protected resource. # @overload authenticate_resource_request(options) # @param [Hash] request The configuration parameters for the request. # @param [String] method the HTTP method , defaults to GET # @param [Addressable::URI, String] uri the URI . # @param [Hash, Array] headers the HTTP headers. # @param [StringIO, String] body The HTTP body. # @param [Boolean] two_legged skip the token_credential lookup? # @param [HTTPAdapter] adapter The HTTP adapter(optional). # # @return [Hash] A hash of the credentials and realm for a valid request, # or nil if not valid. def authenticate_resource_request options = {} verifications = { client_credential: lambda do |_x| ::Signet::OAuth1::Credential.new("Client credential key", "Client credential secret") end } unless options[:two_legged] == true verifications.update( token_credential: lambda do |_x| ::Signet::OAuth1::Credential.new("Token credential key", "Token credential secret") end ) end # Make sure all required state is set verifications.each do |(key, _value)| raise ArgumentError, "#{key} was not set." unless send key end request_components = if options[:request] verify_request_components( request: options[:request], adapter: options[:adapter] ) else verify_request_components( method: options[:method], uri: options[:uri], headers: options[:headers], body: options[:body] ) end method = request_components[:method] uri = request_components[:uri] headers = request_components[:headers] body = request_components[:body] if !body.is_a?(String) && body.respond_to?(:each) # Just in case we get a chunked body merged_body = StringIO.new body.each do |chunk| merged_body.write chunk end body = merged_body.string end raise TypeError, "Expected String, got #{body.class}." unless body.is_a? String media_type = nil headers.each do |(header, value)| media_type = value.gsub(/^([^;]+)(;.*?)?$/, '\1') if header.casecmp("Content-Type").zero? end auth_hash = verify_auth_header_components headers auth_token = auth_hash["oauth_token"] unless options[:two_legged] return nil if auth_token.nil? return nil unless (token_credential = find_token_credential auth_token) token_credential_secret = token_credential.secret if token_credential end return nil unless (client_credential = find_client_credential auth_hash["oauth_consumer_key"]) return nil unless validate_nonce_timestamp(auth_hash["oauth_nonce"], auth_hash["oauth_timestamp"]) if method == ("POST" || "PUT") && media_type == "application/x-www-form-urlencoded" request_components[:body] = body post_parameters = Addressable::URI.form_unencode body post_parameters.each { |param| param[1] = "" if param[1].nil? } # If the auth header doesn't have the same params as the body, it # can't have been signed correctly(5849#3.4.1.3) unless post_parameters.sort == auth_hash.reject { |k, _v| k.index "oauth_" }.to_a.sort raise MalformedAuthorizationError, "Request is of type application/x-www-form-urlencoded " \ "but Authentication header did not include form values" end end client_credential_secret = client_credential.secret if client_credential computed_signature = ::Signet::OAuth1.sign_parameters( method, uri, # Realm isn't used, and will throw the signature off. auth_hash.reject { |k, _v| k == "realm" }.to_a, client_credential_secret, token_credential_secret ) return nil unless safe_equals? computed_signature, auth_hash["oauth_signature"] { client_credential: client_credential, token_credential: token_credential, realm: auth_hash["realm"] } end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/PerceivedComplexity end end end signet-0.14.0/lib/signet/oauth_1/credential.rb0000644000004100000410000001035613673221365021170 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Signet #:nodoc: module OAuth1 class Credential # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/PerceivedComplexity ## # Creates a token object from a key and secret. # # @example # Signet::OAuth1::Credential.new( # :key => "dpf43f3p2l4k3l03", # :secret => "kd94hf93k423kf44" # ) # # @example # Signet::OAuth1::Credential.new([ # ["oauth_token", "dpf43f3p2l4k3l03"], # ["oauth_token_secret", "kd94hf93k423kf44"] # ]) # # @example # Signet::OAuth1::Credential.new( # "dpf43f3p2l4k3l03", "kd94hf93k423kf44" # ) def initialize *args # We want to be particularly flexible in how we initialize a token # object for maximum interoperability. However, this flexibility # means we need to be careful about returning an unexpected value for # key or secret to avoid difficult-to-debug situations. Thus lots # of type-checking. # This is cheaper than coercing to some kind of Hash with # indifferent access. Also uglier. key_from_hash = lambda do |parameters| parameters["oauth_token"] || parameters[:oauth_token] || parameters["key"] || parameters[:key] end secret_from_hash = lambda do |parameters| parameters["oauth_token_secret"] || parameters[:oauth_token_secret] || parameters["secret"] || parameters[:secret] end if args.first.respond_to? :to_hash parameters = args.first.to_hash @key = key_from_hash.call parameters @secret = secret_from_hash.call parameters unless @key && @secret raise ArgumentError, "Could not find both key and secret in #{hash.inspect}." end else # Normalize to an Array if !args.first.is_a?(String) && !args.first.respond_to?(:to_str) && args.first.is_a?(Enumerable) # We need to special-case strings since they're technically # Enumerable objects. args = args.first.to_a elsif args.first.respond_to? :to_ary args = args.first.to_ary end if args.all? { |value| value.is_a? Array } parameters = args.each_with_object({}) { |(k, v), h| h[k] = v; } @key = key_from_hash.call parameters @secret = secret_from_hash.call parameters elsif args.size == 2 @key, @secret = args else raise ArgumentError, "wrong number of arguments (#{args.size} for 2)" end end raise TypeError, "Expected String, got #{@key.class}." unless @key.respond_to? :to_str @key = @key.to_str raise TypeError, "Expected String, got #{@secret.class}." unless @secret.respond_to? :to_str @secret = @secret.to_str end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/PerceivedComplexity attr_accessor :key, :secret def to_hash { "oauth_token" => key, "oauth_token_secret" => secret } end alias to_h to_hash def == other if other.respond_to?(:key) && other.respond_to?(:secret) key == other.key && secret == other.secret else false end end end end end signet-0.14.0/lib/signet/oauth_2.rb0000644000004100000410000001220013673221365017045 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "base64" require "signet" require "multi_json" module Signet #:nodoc: ## # An implementation of http://tools.ietf.org/html/draft-ietf-oauth-v2-10 # # This module will be updated periodically to support newer drafts of the # specification, as they become widely deployed. module OAuth2 def self.parse_authorization_header field_value auth_scheme = field_value[/^([-._0-9a-zA-Z]+)/, 1] case auth_scheme when /^Basic$/i # HTTP Basic is allowed in OAuth 2 return parse_basic_credentials(field_value[/^Basic\s+(.*)$/i, 1]) when /^OAuth$/i # Other token types may be supported eventually return parse_bearer_credentials(field_value[/^OAuth\s+(.*)$/i, 1]) else raise ParseError, "Parsing non-OAuth Authorization headers is out of scope." end end def self.parse_www_authenticate_header field_value auth_scheme = field_value[/^([-._0-9a-zA-Z]+)/, 1] case auth_scheme when /^OAuth$/i # Other token types may be supported eventually return parse_oauth_challenge(field_value[/^OAuth\s+(.*)$/i, 1]) else raise ParseError, "Parsing non-OAuth WWW-Authenticate headers is out of scope." end end def self.parse_basic_credentials credential_string decoded = Base64.decode64 credential_string client_id, client_secret = decoded.split ":", 2 [["client_id", client_id], ["client_secret", client_secret]] end def self.parse_bearer_credentials credential_string access_token = credential_string[/^([^,\s]+)(?:\s|,|$)/i, 1] parameters = [] parameters << ["access_token", access_token] auth_param_string = credential_string[/^(?:[^,\s]+)\s*,\s*(.*)$/i, 1] if auth_param_string # This code will rarely get called, but is included for completeness parameters.concat Signet.parse_auth_param_list(auth_param_string) end parameters end def self.parse_oauth_challenge challenge_string Signet.parse_auth_param_list challenge_string end def self.parse_credentials body, content_type raise TypeError, "Expected String, got #{body.class}." unless body.is_a? String case content_type when %r{^application/json.*} return MultiJson.load body when %r{^application/x-www-form-urlencoded.*} return Hash[Addressable::URI.form_unencode(body)] else raise ArgumentError, "Invalid content type '#{content_type}'" end end ## # Generates a Basic Authorization header from a client identifier and a # client password. # # @param [String] client_id # The client identifier. # @param [String] client_password # The client password. # # @return [String] # The value for the HTTP Basic Authorization header. def self.generate_basic_authorization_header client_id, client_password if client_id =~ /:/ raise ArgumentError, "A client identifier may not contain a ':' character." end "Basic " + Base64.encode64( client_id + ":" + client_password ).delete("\n") end ## # Generates an authorization header for an access token # # @param [String] access_token # The access token. # @param [Hash] auth_params # Additional parameters to be encoded in the header # # @return [String] # The value for the HTTP Basic Authorization header. def self.generate_bearer_authorization_header \ access_token, auth_params = nil # TODO: escaping? header = "Bearer #{access_token}" if auth_params && !auth_params.empty? header += (", " + (auth_params.each_with_object [] do |(key, value), accu| accu << "#{key}=\"#{value}\"" end).join(", ") ) end header end ## # Appends the necessary OAuth parameters to # the base authorization endpoint URI. # # @param [Addressable::URI, String, #to_str] authorization_uri # The base authorization endpoint URI. # # @return [String] The authorization URI to redirect the user to. def self.generate_authorization_uri authorization_uri, parameters = {} parameters.each do |key, value| parameters.delete key if value.nil? end parsed_uri = Addressable::URI.parse(authorization_uri).dup query_values = parsed_uri.query_values || {} query_values = query_values.merge parameters parsed_uri.query_values = query_values parsed_uri.normalize.to_s end end end signet-0.14.0/lib/signet/version.rb0000644000004100000410000000121513673221365017175 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. module Signet VERSION = "0.14.0".freeze end signet-0.14.0/lib/signet/oauth_2/0000755000004100000410000000000013673221365016525 5ustar www-datawww-datasignet-0.14.0/lib/signet/oauth_2/client.rb0000644000004100000410000012443413673221365020340 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "faraday" require "stringio" require "addressable/uri" require "signet" require "signet/errors" require "signet/oauth_2" require "jwt" require "date" module Signet module OAuth2 class Client OOB_MODES = ["urn:ietf:wg:oauth:2.0:oob:auto", "urn:ietf:wg:oauth:2.0:oob", "oob"].freeze ## # Creates an OAuth 2.0 client. # # @param [Hash] options # The configuration parameters for the client. # - :authorization_uri - # The authorization server's HTTP endpoint capable of # authenticating the end-user and obtaining authorization. # - :token_credential_uri - # The authorization server's HTTP endpoint capable of issuing # tokens and refreshing expired tokens. # - :client_id - # A unique identifier issued to the client to identify itself to the # authorization server. # - :client_secret - # A shared symmetric secret issued by the authorization server, # which is used to authenticate the client. # - :scope - # The scope of the access request, expressed either as an Array # or as a space-delimited String. # - :target_audience - # The final target audience for ID tokens fetched by this client, # as a String. # - :state - # An arbitrary string designed to allow the client to maintain state. # - :code - # The authorization code received from the authorization server. # - :redirect_uri - # The redirection URI used in the initial request. # - :username - # The resource owner's username. # - :password - # The resource owner's password. # - :issuer - # Issuer ID when using assertion profile # - :person - # Target user for assertions # - :expiry - # Number of seconds assertions are valid for # - :signing_key - # Signing key when using assertion profile # - :refresh_token - # The refresh token associated with the access token # to be refreshed. # - :access_token - # The current access token for this client. # - :id_token - # The current ID token for this client. # - :extension_parameters - # When using an extension grant type, this the set of parameters used # by that extension. # # @example # client = Signet::OAuth2::Client.new( # :authorization_uri => # 'https://example.server.com/authorization', # :token_credential_uri => # 'https://example.server.com/token', # :client_id => 'anonymous', # :client_secret => 'anonymous', # :scope => 'example', # :redirect_uri => 'https://example.client.com/oauth' # ) # # @see Signet::OAuth2::Client#update! def initialize options = {} @authorization_uri = nil @token_credential_uri = nil @client_id = nil @client_secret = nil @code = nil @expires_at = nil @issued_at = nil @issuer = nil @password = nil @principal = nil @redirect_uri = nil @scope = nil @target_audience = nil @state = nil @username = nil @access_type = nil update! options end # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/PerceivedComplexity ## # Updates an OAuth 2.0 client. # # @param [Hash] options # The configuration parameters for the client. # - :authorization_uri - # The authorization server's HTTP endpoint capable of # authenticating the end-user and obtaining authorization. # - :token_credential_uri - # The authorization server's HTTP endpoint capable of issuing # tokens and refreshing expired tokens. # - :client_id - # A unique identifier issued to the client to identify itself to the # authorization server. # - :client_secret - # A shared symmetric secret issued by the authorization server, # which is used to authenticate the client. # - :scope - # The scope of the access request, expressed either as an Array # or as a space-delimited String. # - :target_audience - # The final target audience for ID tokens fetched by this client, # as a String. # - :state - # An arbitrary string designed to allow the client to maintain state. # - :code - # The authorization code received from the authorization server. # - :redirect_uri - # The redirection URI used in the initial request. # - :username - # The resource owner's username. # - :password - # The resource owner's password. # - :issuer - # Issuer ID when using assertion profile # - :audience - # Target audience for assertions # - :person - # Target user for assertions # - :expiry - # Number of seconds assertions are valid for # - :signing_key - # Signing key when using assertion profile # - :refresh_token - # The refresh token associated with the access token # to be refreshed. # - :access_token - # The current access token for this client. # - :access_type - # The current access type parameter for #authorization_uri. # - :id_token - # The current ID token for this client. # - :extension_parameters - # When using an extension grant type, this is the set of parameters used # by that extension. # # @example # client.update!( # :code => 'i1WsRn1uB1', # :access_token => 'FJQbwq9', # :expires_in => 3600 # ) # # @see Signet::OAuth2::Client#initialize # @see Signet::OAuth2::Client#update_token! def update! options = {} # Normalize all keys to symbols to allow indifferent access. options = deep_hash_normalize options self.authorization_uri = options[:authorization_uri] if options.key? :authorization_uri self.token_credential_uri = options[:token_credential_uri] if options.key? :token_credential_uri self.client_id = options[:client_id] if options.key? :client_id self.client_secret = options[:client_secret] if options.key? :client_secret self.scope = options[:scope] if options.key? :scope self.target_audience = options[:target_audience] if options.key? :target_audience self.state = options[:state] if options.key? :state self.code = options[:code] if options.key? :code self.redirect_uri = options[:redirect_uri] if options.key? :redirect_uri self.username = options[:username] if options.key? :username self.password = options[:password] if options.key? :password self.issuer = options[:issuer] if options.key? :issuer self.person = options[:person] if options.key? :person self.sub = options[:sub] if options.key? :sub self.expiry = options[:expiry] || 60 self.audience = options[:audience] if options.key? :audience self.signing_key = options[:signing_key] if options.key? :signing_key self.extension_parameters = options[:extension_parameters] || {} self.additional_parameters = options[:additional_parameters] || {} self.access_type = options.fetch(:access_type) { :offline } update_token! options self end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/PerceivedComplexity ## # Updates an OAuth 2.0 client. # # @param [Hash] options # The configuration parameters related to the token. # - :refresh_token - # The refresh token associated with the access token # to be refreshed. # - :access_token - # The current access token for this client. # - :id_token - # The current ID token for this client. # - :expires_in - # The time in seconds until access token expiration. # - :expires_at - # The time as an integer number of seconds since the Epoch # - :issued_at - # The timestamp that the token was issued at. # # @example # client.update!( # :refresh_token => 'n4E9O119d', # :access_token => 'FJQbwq9', # :expires_in => 3600 # ) # # @see Signet::OAuth2::Client#initialize # @see Signet::OAuth2::Client#update! def update_token! options = {} # Normalize all keys to symbols to allow indifferent access internally options = deep_hash_normalize options self.expires_in = options[:expires] if options.key? :expires self.expires_in = options[:expires_in] if options.key? :expires_in self.expires_at = options[:expires_at] if options.key? :expires_at # By default, the token is issued at `Time.now` when `expires_in` is # set, but this can be used to supply a more precise time. self.issued_at = options[:issued_at] if options.key? :issued_at # Special case where we want expires_at to be relative to issued_at if options.key?(:issued_at) && options.key?(:expires_in) set_relative_expires_at options[:issued_at], options[:expires_in] end self.access_token = options[:access_token] if options.key? :access_token self.refresh_token = options[:refresh_token] if options.key? :refresh_token self.id_token = options[:id_token] if options.key? :id_token self end # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/PerceivedComplexity ## # Returns the authorization URI that the user should be redirected to. # # @return [Addressable::URI] The authorization URI. # # @see Signet::OAuth2.generate_authorization_uri def authorization_uri options = {} # Normalize external input options = deep_hash_normalize options return nil if @authorization_uri.nil? options[:response_type] = :code unless options[:response_type] options[:access_type] = access_type if !options[:access_type] && access_type options[:client_id] ||= client_id options[:redirect_uri] ||= redirect_uri if options[:prompt] && options[:approval_prompt] raise ArgumentError, "prompt and approval_prompt are mutually exclusive parameters" end raise ArgumentError, "Missing required client identifier." unless options[:client_id] raise ArgumentError, "Missing required redirect URI." unless options[:redirect_uri] options[:scope] = scope.join " " if !options[:scope] && scope options[:state] = state unless options[:state] options.merge!(additional_parameters.merge(options[:additional_parameters] || {})) options.delete :additional_parameters options = Hash[options.map do |key, option| [key.to_s, option] end] uri = Addressable::URI.parse( ::Signet::OAuth2.generate_authorization_uri( @authorization_uri, options ) ) if uri.normalized_scheme != "https" raise Signet::UnsafeOperationError, "Authorization endpoint must be protected by TLS." end uri end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/PerceivedComplexity ## # Sets the authorization URI for this client. # # @param [Addressable::URI, Hash, String, #to_str] new_authorization_uri # The authorization URI. def authorization_uri= new_authorization_uri @authorization_uri = coerce_uri new_authorization_uri end ## # Returns the token credential URI for this client. # # @return [Addressable::URI] The token credential URI. def token_credential_uri @token_credential_uri end ## # Sets the token credential URI for this client. # # @param [Addressable::URI, Hash, String, #to_str] new_token_credential_uri # The token credential URI. def token_credential_uri= new_token_credential_uri @token_credential_uri = coerce_uri new_token_credential_uri end # Addressable expects URIs formatted as hashes to come in with symbols as keys. # Returns nil implicitly for the nil case. def coerce_uri incoming_uri if incoming_uri.is_a? Hash Addressable::URI.new deep_hash_normalize(incoming_uri) elsif incoming_uri Addressable::URI.parse incoming_uri end end ## # Returns the current access type parameter for #authorization_uri. # # @return [String, Symbol] The current access type. def access_type @access_type end ## # Sets the current access type parameter for #authorization_uri. # # @param [String, Symbol] new_access_type # The current access type. def access_type= new_access_type @access_type = new_access_type end ## # Returns the client identifier for this client. # # @return [String] The client identifier. def client_id @client_id end ## # Sets the client identifier for this client. # # @param [String] new_client_id # The client identifier. def client_id= new_client_id @client_id = new_client_id end ## # Returns the client secret for this client. # # @return [String] The client secret. def client_secret @client_secret end ## # Sets the client secret for this client. # # @param [String] new_client_secret # The client secret. def client_secret= new_client_secret @client_secret = new_client_secret end ## # Returns the scope for this client. Scope is a list of access ranges # defined by the authorization server. # # @return [Array] The scope of access the client is requesting. def scope @scope end ## # Sets the scope for this client. # # @param [Array, String] new_scope # The scope of access the client is requesting. This may be # expressed as either an Array of String objects or as a # space-delimited String. def scope= new_scope case new_scope when Array new_scope.each do |scope| if scope.include? " " raise ArgumentError, "Individual scopes cannot contain the space character." end end @scope = new_scope when String @scope = new_scope.split " " when nil @scope = nil else raise TypeError, "Expected Array or String, got #{new_scope.class}" end end ## # Returns the final target audience for ID tokens fetched by this client. # # @return [String] The target audience. def target_audience @target_audience end ## # Sets the final target audience for ID tokens fetched by this client. # # @param [String] new_target_audience The new target audience. def target_audience= new_target_audience @target_audience = new_target_audience end ## # Returns the client's current state value. # # @return [String] The state value. def state @state end ## # Sets the client's current state value. # # @param [String] new_state # The state value. def state= new_state @state = new_state end ## # Returns the authorization code issued to this client. # Used only by the authorization code access grant type. # # @return [String] The authorization code. def code @code end ## # Sets the authorization code issued to this client. # Used only by the authorization code access grant type. # # @param [String] new_code # The authorization code. def code= new_code @code = new_code end ## # Returns the redirect URI for this client. # # @return [String] The redirect URI. def redirect_uri @redirect_uri end ## # Sets the redirect URI for this client. # # @param [String] new_redirect_uri # The redirect URI. def redirect_uri= new_redirect_uri new_redirect_uri = Addressable::URI.parse new_redirect_uri # TODO: - Better solution to allow google postmessage flow. For now, make an exception to the spec. unless new_redirect_uri.nil? || new_redirect_uri.absolute? || uri_is_postmessage?(new_redirect_uri) || uri_is_oob?(new_redirect_uri) raise ArgumentError, "Redirect URI must be an absolute URI." end @redirect_uri = new_redirect_uri end ## # Returns the username associated with this client. # Used only by the resource owner password credential access grant type. # # @return [String] The username. def username @username end ## # Sets the username associated with this client. # Used only by the resource owner password credential access grant type. # # @param [String] new_username # The username. def username= new_username @username = new_username end ## # Returns the password associated with this client. # Used only by the resource owner password credential access grant type. # # @return [String] The password. def password @password end ## # Sets the password associated with this client. # Used only by the resource owner password credential access grant type. # # @param [String] new_password # The password. def password= new_password @password = new_password end ## # Returns the issuer ID associated with this client. # Used only by the assertion grant type. # # @return [String] Issuer id. def issuer @issuer end ## # Sets the issuer ID associated with this client. # Used only by the assertion grant type. # # @param [String] new_issuer # Issuer ID (typical in email adddress form). def issuer= new_issuer @issuer = new_issuer end ## # Returns the target audience ID when issuing assertions. # Used only by the assertion grant type. # # @return [String] Target audience ID. def audience @audience end ## # Sets the target audience ID when issuing assertions. # Used only by the assertion grant type. # # @param [String] new_audience # Target audience ID def audience= new_audience @audience = new_audience end ## # Returns the target resource owner for impersonation. # Used only by the assertion grant type. # # @return [String] Target user for impersonation. def principal @principal end ## # Sets the target resource owner for impersonation. # Used only by the assertion grant type. # # @param [String] new_person # Target user for impersonation def principal= new_person @principal = new_person end alias person principal alias person= principal= ## # The target "sub" when issuing assertions. # Used in some Admin SDK APIs. # attr_accessor :sub ## # Returns the number of seconds assertions are valid for # Used only by the assertion grant type. # # @return [Integer] Assertion expiry, in seconds def expiry @expiry end ## # Sets the number of seconds assertions are valid for # Used only by the assertion grant type. # # @param [Integer, String] new_expiry # Assertion expiry, in seconds def expiry= new_expiry @expiry = new_expiry ? new_expiry.to_i : nil end ## # Returns the signing key associated with this client. # Used only by the assertion grant type. # # @return [String,OpenSSL::PKey] Signing key def signing_key @signing_key end ## # Sets the signing key when issuing assertions. # Used only by the assertion grant type. # # @param [String, OpenSSL::Pkey] new_key # Signing key. Either private key for RSA or string for HMAC algorithm def signing_key= new_key @signing_key = new_key end ## # Algorithm used for signing JWTs # @return [String] Signing algorithm def signing_algorithm signing_key.is_a?(String) ? "HS256" : "RS256" end ## # Returns the set of extension parameters used by the client. # Used only by extension access grant types. # # @return [Hash] The extension parameters. def extension_parameters @extension_parameters ||= {} end ## # Sets extension parameters used by the client. # Used only by extension access grant types. # # @param [Hash] new_extension_parameters # The parameters. def extension_parameters= new_extension_parameters if new_extension_parameters.respond_to? :to_hash @extension_parameters = new_extension_parameters.to_hash else raise TypeError, "Expected Hash, got #{new_extension_parameters.class}." end end ## # Returns the set of additional (non standard) parameters to be used by the client. # # @return [Hash] The pass through parameters. def additional_parameters @additional_parameters ||= {} end ## # Sets additional (non standard) parameters to be used by the client. # # @param [Hash] new_additional_parameters # The parameters. def additional_parameters= new_additional_parameters if new_additional_parameters.respond_to? :to_hash @additional_parameters = new_additional_parameters.to_hash else raise TypeError, "Expected Hash, got #{new_additional_parameters.class}." end end ## # Returns the refresh token associated with this client. # # @return [String] The refresh token. def refresh_token @refresh_token ||= nil end ## # Sets the refresh token associated with this client. # # @param [String] new_refresh_token # The refresh token. def refresh_token= new_refresh_token @refresh_token = new_refresh_token end ## # Returns the access token associated with this client. # # @return [String] The access token. def access_token @access_token ||= nil end ## # Sets the access token associated with this client. # # @param [String] new_access_token # The access token. def access_token= new_access_token @access_token = new_access_token end ## # Returns the ID token associated with this client. # # @return [String] The ID token. def id_token @id_token ||= nil end ## # Sets the ID token associated with this client. # # @param [String] new_id_token # The ID token. def id_token= new_id_token @id_token = new_id_token end ## # Returns the decoded ID token associated with this client. # # @param [OpenSSL::PKey::RSA, Object] public_key # The public key to use to verify the ID token. Skips verification if # omitted. # # @return [String] The decoded ID token. def decoded_id_token public_key = nil, options = {}, &keyfinder options[:algorithm] ||= signing_algorithm verify = !public_key.nil? || block_given? payload, _header = JWT.decode(id_token, public_key, verify, options, &keyfinder) raise Signet::UnsafeOperationError, "No ID token audience declared." unless payload.key? "aud" unless Array(payload["aud"]).include?(client_id) raise Signet::UnsafeOperationError, "ID token audience did not match Client ID." end payload end ## # Returns the lifetime of the access token in seconds. # Returns nil if the token does not expire. # # @return [Integer, nil] The access token lifetime. def expires_in if @expires_at.nil? || @issued_at.nil? nil else (@expires_at - @issued_at).to_i end end ## # Sets the lifetime of the access token in seconds. Resets the issued_at # timestamp. Nil values will be treated as though the token does # not expire. # # @param [String, Integer, nil] new_expires_in # The access token lifetime. def expires_in= new_expires_in if !new_expires_in.nil? @issued_at = Time.now @expires_at = @issued_at + new_expires_in.to_i else @expires_at = nil @issued_at = nil end end ## # Returns the timestamp the access token was issued at. # # @return [Time, nil] The access token issuance time. def issued_at @issued_at end ## # Sets the timestamp the access token was issued at. # # @param [String,Integer,Time] new_issued_at # The access token issuance time. def issued_at= new_issued_at @issued_at = normalize_timestamp new_issued_at end ## # Returns the timestamp the access token will expire at. # Returns nil if the token does not expire. # # @return [Time, nil] The access token lifetime. def expires_at @expires_at end ## # Limits the lifetime of the access token as number of seconds since # the Epoch. Nil values will be treated as though the token does # not expire. # @param [String,Integer,Time, nil] new_expires_at # The access token expiration time. def expires_at= new_expires_at @expires_at = normalize_timestamp new_expires_at end ## # Returns true if the access token has expired. # Returns false if the token has not expired or has an nil @expires_at. # # @return [TrueClass, FalseClass] # The expiration state of the access token. def expired? !expires_at.nil? && Time.now >= expires_at end ## # Returns true if the access token has expired or expires within # the next n seconds. Returns false for tokens with a nil @expires_at. # # @param [Integer] sec # Max number of seconds from now where a token is still considered # expired. # @return [TrueClass, FalseClass] # The expiration state of the access token. def expires_within? sec !expires_at.nil? && Time.now >= (expires_at - sec) end ## # Removes all credentials from the client. def clear_credentials! @access_token = nil @refresh_token = nil @id_token = nil @username = nil @password = nil @code = nil @issued_at = nil @expires_at = nil end ## # Returns the inferred grant type, based on the current state of the # client object. Returns `"none"` if the client has insufficient # information to make an in-band authorization request. # # @return [String] # The inferred grant type. def grant_type @grant_type ||= nil return @grant_type if @grant_type if code && redirect_uri "authorization_code" elsif refresh_token "refresh_token" elsif username && password "password" elsif issuer && signing_key "urn:ietf:params:oauth:grant-type:jwt-bearer" end end def grant_type= new_grant_type case new_grant_type when "authorization_code", "refresh_token", "password", "client_credentials" @grant_type = new_grant_type else @grant_type = Addressable::URI.parse new_grant_type end end def to_jwt options = {} options = deep_hash_normalize options now = Time.new skew = options[:skew] || 60 assertion = { "iss" => issuer, "aud" => audience, "exp" => (now + expiry).to_i, "iat" => (now - skew).to_i } assertion["scope"] = scope.join " " unless scope.nil? assertion["target_audience"] = target_audience unless target_audience.nil? assertion["prn"] = person unless person.nil? assertion["sub"] = sub unless sub.nil? JWT.encode assertion, signing_key, signing_algorithm end # rubocop:disable Style/MethodDefParentheses # rubocop:disable Metrics/AbcSize ## # Serialize the client object to JSON. # # @note A serialized client contains sensitive information. Persist or transmit with care. # # @return [String] A serialized JSON representation of the client. def to_json(*) MultiJson.dump( "authorization_uri" => authorization_uri ? authorization_uri.to_s : nil, "token_credential_uri" => token_credential_uri ? token_credential_uri.to_s : nil, "client_id" => client_id, "client_secret" => client_secret, "scope" => scope, "target_audience" => target_audience, "state" => state, "code" => code, "redirect_uri" => redirect_uri ? redirect_uri.to_s : nil, "username" => username, "password" => password, "issuer" => issuer, "audience" => audience, "person" => person, "expiry" => expiry, "expires_at" => expires_at ? expires_at.to_i : nil, "signing_key" => signing_key, "refresh_token" => refresh_token, "access_token" => access_token, "id_token" => id_token, "extension_parameters" => extension_parameters ) end # rubocop:enable Style/MethodDefParentheses # rubocop:disable Metrics/CyclomaticComplexity # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/PerceivedComplexity ## # Generates a request for token credentials. # # @param [Hash] options # The configuration parameters for the request. # - :code - # The authorization code. # # @private # @return [Array] The request object. def generate_access_token_request options = {} options = deep_hash_normalize options parameters = { "grant_type" => grant_type } case grant_type when "authorization_code" parameters["code"] = code parameters["redirect_uri"] = redirect_uri when "password" parameters["username"] = username parameters["password"] = password when "refresh_token" parameters["refresh_token"] = refresh_token when "urn:ietf:params:oauth:grant-type:jwt-bearer" parameters["assertion"] = to_jwt options else if redirect_uri # Grant type was intended to be `authorization_code` because of # the presence of the redirect URI. raise ArgumentError, "Missing authorization code." end parameters.merge! extension_parameters end parameters["client_id"] = client_id unless client_id.nil? parameters["client_secret"] = client_secret unless client_secret.nil? if options[:scope] parameters["scope"] = options[:scope] elsif options[:use_configured_scope] && !scope.nil? parameters["scope"] = scope end additional = additional_parameters.merge(options[:additional_parameters] || {}) additional.each { |k, v| parameters[k.to_s] = v } parameters end # rubocop:enable Metrics/CyclomaticComplexity # rubocop:enable Metrics/PerceivedComplexity def fetch_access_token options = {} raise ArgumentError, "Missing token endpoint URI." if token_credential_uri.nil? options = deep_hash_normalize options client = options[:connection] ||= Faraday.default_connection url = Addressable::URI.parse(token_credential_uri).normalize.to_s parameters = generate_access_token_request options if client.is_a? Faraday::Connection response = client.post url, Addressable::URI.form_encode(parameters), "Content-Type" => "application/x-www-form-urlencoded" status = response.status.to_i body = response.body content_type = response.headers["Content-type"] else # Hurley response = client.post url, parameters status = response.status_code.to_i body = response.body content_type = response.header[:content_type] end return ::Signet::OAuth2.parse_credentials body, content_type if status == 200 message = " Server message:\n#{response.body.to_s.strip}" unless body.to_s.strip.empty? if [400, 401, 403].include? status message = "Authorization failed." + message raise ::Signet::AuthorizationError.new( message, response: response ) elsif status.to_s[0] == "5" message = "Remote server error." + message raise ::Signet::RemoteServerError, message else message = "Unexpected status code: #{response.status}." + message raise ::Signet::UnexpectedStatusError, message end end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength def fetch_access_token! options = {} token_hash = fetch_access_token options if token_hash # No-op for grant types other than `authorization_code`. # An authorization code is a one-time use token and is immediately # revoked after usage. self.code = nil self.issued_at = Time.now update_token! token_hash end token_hash end ## # Refresh the access token, if possible def refresh! options = {} fetch_access_token! options end # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength # rubocop:disable Metrics/PerceivedComplexity ## # Generates an authenticated request for protected resources. # # @param [Hash] options # The configuration parameters for the request. # - :request - # A pre-constructed request. An OAuth 2 Authorization header # will be added to it, as well as an explicit Cache-Control # `no-store` directive. # - :method - # The HTTP method for the request. Defaults to 'GET'. # - :uri - # The URI for the request. # - :headers - # The HTTP headers for the request. # - :body - # The HTTP body for the request. # - :realm - # The Authorization realm. See RFC 2617. # @return [Faraday::Request] The request object. def generate_authenticated_request options = {} options = deep_hash_normalize options raise ArgumentError, "Missing access token." if access_token.nil? options = { realm: nil }.merge(options) if options[:request].is_a? Faraday::Request request = options[:request] else if options[:request].is_a? Array method, uri, headers, body = options[:request] else method = options[:method] || :get uri = options[:uri] headers = options[:headers] || [] body = options[:body] || "" end headers = headers.to_a if headers.is_a? Hash request_components = { method: method, uri: uri, headers: headers, body: body } # Verify that we have all pieces required to return an HTTP request request_components.each do |(key, value)| raise ArgumentError, "Missing :#{key} parameter." unless value end method = method.to_s.downcase.to_sym request = options[:connection].build_request method.to_s.downcase.to_sym do |req| req.url Addressable::URI.parse(uri).normalize.to_s req.headers = Faraday::Utils::Headers.new headers req.body = body end end request["Authorization"] = ::Signet::OAuth2.generate_bearer_authorization_header( access_token, options[:realm] ? [["realm", options[:realm]]] : nil ) request["Cache-Control"] = "no-store" request end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength # rubocop:enable Metrics/PerceivedComplexity ## # Transmits a request for a protected resource. # # @param [Hash] options # The configuration parameters for the request. # - :request - # A pre-constructed request. An OAuth 2 Authorization header # will be added to it, as well as an explicit Cache-Control # `no-store` directive. # - :method - # The HTTP method for the request. Defaults to 'GET'. # - :uri - # The URI for the request. # - :headers - # The HTTP headers for the request. # - :body - # The HTTP body for the request. # - :realm - # The Authorization realm. See RFC 2617. # - :connection - # The HTTP connection to use. # Must be of type Faraday::Connection. # # @example # # Using Net::HTTP # response = client.fetch_protected_resource( # :uri => 'http://www.example.com/protected/resource' # ) # # @return [Array] The response object. def fetch_protected_resource options = {} options = deep_hash_normalize options options[:connection] ||= Faraday.default_connection request = generate_authenticated_request options request_env = request.to_env options[:connection] request_env[:request] ||= request response = options[:connection].app.call request_env return response unless response.status.to_i == 401 # When accessing a protected resource, we only want to raise an # error for 401 responses. message = "Authorization failed." message += " Server message:\n#{response.body.to_s.strip}" unless response.body.to_s.strip.empty? raise ::Signet::AuthorizationError.new( message, request: request, response: response ) end private ## # Check if URI is Google's postmessage flow (not a valid redirect_uri by spec, but allowed) # @private def uri_is_postmessage? uri uri.to_s.casecmp("postmessage").zero? end ## # Check if the URI is a out-of-band # @private def uri_is_oob? uri OOB_MODES.include? uri.to_s end # Convert all keys in this hash (nested) to symbols for uniform retrieval def recursive_hash_normalize_keys val if val.is_a? Hash deep_hash_normalize val else val end end def deep_hash_normalize old_hash sym_hash = {} old_hash&.each { |k, v| sym_hash[k.to_sym] = recursive_hash_normalize_keys v } sym_hash end def normalize_timestamp time case time when NilClass nil when Time time when DateTime time.to_time when String Time.parse time when Integer Time.at time else raise "Invalid time value #{time}" end end def set_relative_expires_at issued_at, expires_in self.issued_at = issued_at # Using local expires_in because if self.expires_in is used, it returns # the time left before the token expires self.expires_at = self.issued_at + expires_in.to_i end end end end signet-0.14.0/lib/signet/errors.rb0000644000004100000410000000561213673221365017031 0ustar www-datawww-data# Copyright (C) 2010 Google Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. require "addressable/uri" module Signet ## # An error indicating that the client has aborted an operation that # would have been unsafe to perform. class UnsafeOperationError < StandardError end ## # An error indicating the client failed to parse a value. class ParseError < StandardError end ## # An error indicating that the server considers the Authorization header to # be malformed(missing/unsupported/invalid parameters), and the request # should be considered invalid. class MalformedAuthorizationError < StandardError end ## # An error indicating that the server failed at processing the request # due to a internal error class RemoteServerError < StandardError end ## # An error indicating that the server sent an unexpected http status class UnexpectedStatusError < StandardError end ## # An error indicating the remote server refused to authorize the client. class AuthorizationError < StandardError ## # Creates a new authentication error. # # @param [String] message # A message describing the error. # @param [Hash] options # The configuration parameters for the request. # - :request - # A Faraday::Request object. Optional. # - :response - # A Faraday::Response object. Optional. # - :code - # An error code. # - :description - # Human-readable text intended to be used to assist in resolving the # error condition. # - :uri - # A URI identifying a human-readable web page with additional # information about the error, indended for the resource owner. def initialize message, options = {} super message @options = options @request = options[:request] @response = options[:response] @code = options[:code] @description = options[:description] @uri = Addressable::URI.parse options[:uri] end ## # The HTTP request that triggered this authentication error. # # @return [Array] A tuple of method, uri, headers, and body. attr_reader :request ## # The HTTP response that triggered this authentication error. # # @return [Array] A tuple of status, headers, and body. attr_reader :response end end signet-0.14.0/lib/signet/oauth_1.rb0000644000004100000410000004225213673221365017056 0ustar www-datawww-datarequire "addressable/uri" require "signet" require "securerandom" module Signet #:nodoc: module OAuth1 OUT_OF_BAND = "oob".freeze ## # Converts a value to a percent-encoded String according to # the rules given in RFC 5849. All non-unreserved characters are # percent-encoded. # # @param [Symbol, #to_str] value The value to be encoded. # # @return [String] The percent-encoded value. def self.encode value value = value.to_s if value.is_a? Symbol Addressable::URI.encode_component( value, Addressable::URI::CharacterClasses::UNRESERVED ) end ## # Converts a percent-encoded String to an unencoded value. # # @param [#to_str] value # The percent-encoded String to be unencoded. # # @return [String] The unencoded value. def self.unencode value Addressable::URI.unencode_component value end ## # Returns a timestamp suitable for use as an 'oauth_timestamp' # value. # # @return [String] The current timestamp. def self.generate_timestamp Time.now.to_i.to_s end ## # Returns a nonce suitable for use as an 'oauth_nonce' # value. # # @return [String] A random nonce. def self.generate_nonce SecureRandom.random_bytes(16).unpack("H*").join "" end # rubocop:disable Metrics/MethodLength ## # Processes an options Hash to find a credential key value. # Allows for greater flexibility in configuration. # # @param [Symbol] credential_type # One of :client, :temporary, # :token, :consumer, :request, # or :access. # # @return [String] The credential key value. def self.extract_credential_key_option credential_type, options # Normalize key to String to allow indifferent access. options = options.each_with_object({}) { |(k, v), accu| accu[k.to_s] = v; } credential_key = "#{credential_type}_credential_key" credential = "#{credential_type}_credential" if options[credential_key] credential_key = options[credential_key] elsif options[credential] require "signet/oauth_1/credential" unless options[credential].respond_to? :key raise TypeError, "Expected Signet::OAuth1::Credential, " \ "got #{options[credential].class}." end credential_key = options[credential].key elsif options["client"] require "signet/oauth_1/client" unless options["client"].is_a? ::Signet::OAuth1::Client raise TypeError, "Expected Signet::OAuth1::Client, got #{options['client'].class}." end credential_key = options["client"].send credential_key else credential_key = nil end if !credential_key.nil? && !credential_key.is_a?(String) raise TypeError, "Expected String, got #{credential_key.class}." end credential_key end ## # Processes an options Hash to find a credential secret value. # Allows for greater flexibility in configuration. # # @param [Symbol] credential_type # One of :client, :temporary, # :token, :consumer, :request, # or :access. # # @return [String] The credential secret value. def self.extract_credential_secret_option credential_type, options # Normalize key to String to allow indifferent access. options = options.each_with_object({}) { |(k, v), accu| accu[k.to_s] = v; } credential_secret = "#{credential_type}_credential_secret" credential = "#{credential_type}_credential" if options[credential_secret] credential_secret = options[credential_secret] elsif options[credential] require "signet/oauth_1/credential" unless options[credential].respond_to? :secret raise TypeError, "Expected Signet::OAuth1::Credential, " \ "got #{options[credential].class}." end credential_secret = options[credential].secret elsif options["client"] require "signet/oauth_1/client" unless options["client"].is_a? ::Signet::OAuth1::Client raise TypeError, "Expected Signet::OAuth1::Client, got #{options['client'].class}." end credential_secret = options["client"].send credential_secret else credential_secret = nil end if !credential_secret.nil? && !credential_secret.is_a?(String) raise TypeError, "Expected String, got #{credential_secret.class}." end credential_secret end # rubocop:enable Metrics/MethodLength ## # Normalizes a set of OAuth parameters according to the algorithm given # in RFC 5849. Sorts key/value pairs lexically by byte order, first by # key, then by value, joins key/value pairs with the '=' character, then # joins the entire parameter list with '&' characters. # # @param [Enumerable] parameters The OAuth parameter list. # # @return [String] The normalized parameter list. def self.normalize_parameters parameters raise TypeError, "Expected Enumerable, got #{parameters.class}." unless parameters.is_a? Enumerable parameter_list = parameters.map do |k, v| next if k == "oauth_signature" # This is probably the wrong place to try to exclude the realm "#{encode k}=#{encode v}" end parameter_list.compact.sort.join "&" end ## # Generates a signature base string according to the algorithm given in # RFC 5849. Joins the method, URI, and normalized parameter string with # '&' characters. # # @param [String] method The HTTP method. # @param [Addressable::URI, String, #to_str] uri The URI. # @param [Enumerable] parameters The OAuth parameter list. # # @return [String] The signature base string. def self.generate_base_string method, uri, parameters raise TypeError, "Expected Enumerable, got #{parameters.class}." unless parameters.is_a? Enumerable method = method.to_s.upcase parsed_uri = Addressable::URI.parse uri uri = Addressable::URI.new( scheme: parsed_uri.normalized_scheme, authority: parsed_uri.normalized_authority, path: parsed_uri.path, query: parsed_uri.query, fragment: parsed_uri.fragment ) uri_parameters = uri.query_values.to_a uri = uri.omit(:query, :fragment).to_s merged_parameters = uri_parameters.concat(parameters.map { |k, v| [k, v] }) parameter_string = normalize_parameters merged_parameters [ encode(method), encode(uri), encode(parameter_string) ].join("&") end ## # Generates an Authorization header from a parameter list # according to the rules given in RFC 5849. # # @param [Enumerable] parameters The OAuth parameter list. # @param [String] realm # The Authorization realm. See RFC 2617. # # @return [String] The Authorization header. def self.generate_authorization_header parameters, realm = nil if !parameters.is_a?(Enumerable) || parameters.is_a?(String) raise TypeError, "Expected Enumerable, got #{parameters.class}." end parameter_list = parameters.map do |k, v| if k == "realm" raise ArgumentError, 'The "realm" parameter must be specified as a separate argument.' end "#{encode k}=\"#{encode v}\"" end if realm realm = realm.gsub '"', '\"' parameter_list.unshift "realm=\"#{realm}\"" end "OAuth " + parameter_list.join(", ") end ## # Parses an Authorization header into its component # parameters. Parameter keys and values are decoded according to the # rules given in RFC 5849. def self.parse_authorization_header field_value raise TypeError, "Expected String, got #{field_value.class}." unless field_value.is_a? String auth_scheme = field_value[/^([-._0-9a-zA-Z]+)/, 1] case auth_scheme when /^OAuth$/i # Other token types may be supported eventually pairs = Signet.parse_auth_param_list(field_value[/^OAuth\s+(.*)$/i, 1]) return (pairs.each_with_object [] do |(k, v), accu| if k != "realm" k = unencode k v = unencode v end accu << [k, v] end) else raise ParseError, "Parsing non-OAuth Authorization headers is out of scope." end end ## # Parses an application/x-www-form-urlencoded HTTP response # body into an OAuth key/secret pair. # # @param [String] body The response body. # # @return [Signet::OAuth1::Credential] The OAuth credentials. def self.parse_form_encoded_credentials body raise TypeError, "Expected String, got #{body.class}." unless body.is_a? String Signet::OAuth1::Credential.new( Addressable::URI.form_unencode(body) ) end ## # Generates an OAuth signature using the signature method indicated in the # parameter list. Unsupported signature methods will result in a # NotImplementedError exception being raised. # # @param [String] method The HTTP method. # @param [Addressable::URI, String, #to_str] uri The URI. # @param [Enumerable] parameters The OAuth parameter list. # @param [String] client_credential_secret The client credential secret. # @param [String] token_credential_secret # The token credential secret. Omitted when unavailable. # # @return [String] The signature. def self.sign_parameters method, uri, parameters, client_credential_secret, token_credential_secret = nil # Technically, the token_credential_secret parameter here may actually # be a temporary credential secret when obtaining a token credential # for the first time base_string = generate_base_string method, uri, parameters parameters = parameters.each_with_object({}) { |(k, v), h| h[k.to_s] = v; } signature_method = parameters["oauth_signature_method"] case signature_method when "HMAC-SHA1" require "signet/oauth_1/signature_methods/hmac_sha1" return Signet::OAuth1::HMACSHA1.generate_signature( base_string, client_credential_secret, token_credential_secret ) when "RSA-SHA1" require "signet/oauth_1/signature_methods/rsa_sha1" return Signet::OAuth1::RSASHA1.generate_signature( base_string, client_credential_secret, token_credential_secret ) when "PLAINTEXT" require "signet/oauth_1/signature_methods/plaintext" return Signet::OAuth1::PLAINTEXT.generate_signature( base_string, client_credential_secret, token_credential_secret ) else raise NotImplementedError, "Unsupported signature method: #{signature_method}" end end ## # Generates an OAuth parameter list to be used when obtaining a set of # temporary credentials. # # @param [Hash] options # The configuration parameters for the request. # - :client_credential_key - # The client credential key. # - :callback - # The OAuth callback. Defaults to {Signet::OAuth1::OUT_OF_BAND}. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # - :additional_parameters - # Non-standard additional parameters. # # @return [Array] # The parameter list as an Array of key/value pairs. def self.unsigned_temporary_credential_parameters options = {} options = { callback: ::Signet::OAuth1::OUT_OF_BAND, signature_method: "HMAC-SHA1", additional_parameters: [] }.merge(options) client_credential_key = extract_credential_key_option :client, options raise ArgumentError, "Missing :client_credential_key parameter." if client_credential_key.nil? parameters = [ ["oauth_consumer_key", client_credential_key], ["oauth_signature_method", options[:signature_method]], ["oauth_timestamp", generate_timestamp], ["oauth_nonce", generate_nonce], ["oauth_version", "1.0"], ["oauth_callback", options[:callback]] ] # Works for any Enumerable options[:additional_parameters].each do |key, value| parameters << [key, value] end parameters end ## # Appends the optional 'oauth_token' and 'oauth_callback' parameters to # the base authorization URI. # # @param [Addressable::URI, String, #to_str] authorization_uri # The base authorization URI. # # @return [String] The authorization URI to redirect the user to. def self.generate_authorization_uri authorization_uri, options = {} options = { callback: nil, additional_parameters: {} }.merge(options) temporary_credential_key = extract_credential_key_option :temporary, options parsed_uri = Addressable::URI.parse(authorization_uri).dup query_values = parsed_uri.query_values || {} if options[:additional_parameters] query_values = query_values.merge( options[:additional_parameters].each_with_object({}) { |(k, v), h| h[k] = v; } ) end query_values["oauth_token"] = temporary_credential_key if temporary_credential_key query_values["oauth_callback"] = options[:callback] if options[:callback] parsed_uri.query_values = query_values parsed_uri.normalize.to_s end ## # Generates an OAuth parameter list to be used when obtaining a set of # token credentials. # # @param [Hash] options # The configuration parameters for the request. # - :client_credential_key - # The client credential key. # - :temporary_credential_key - # The temporary credential key. # - :verifier - # The OAuth verifier. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # # @return [Array] # The parameter list as an Array of key/value pairs. def self.unsigned_token_credential_parameters options = {} options = { signature_method: "HMAC-SHA1", verifier: nil }.merge(options) client_credential_key = extract_credential_key_option :client, options temporary_credential_key = extract_credential_key_option :temporary, options raise ArgumentError, "Missing :client_credential_key parameter." if client_credential_key.nil? raise ArgumentError, "Missing :temporary_credential_key parameter." if temporary_credential_key.nil? raise ArgumentError, "Missing :verifier parameter." if options[:verifier].nil? parameters = [ ["oauth_consumer_key", client_credential_key], ["oauth_token", temporary_credential_key], ["oauth_signature_method", options[:signature_method]], ["oauth_timestamp", generate_timestamp], ["oauth_nonce", generate_nonce], ["oauth_verifier", options[:verifier]], ["oauth_version", "1.0"] ] # No additional parameters allowed here parameters end ## # Generates an OAuth parameter list to be used when requesting a # protected resource. # # @param [Hash] options # The configuration parameters for the request. # - :client_credential_key - # The client credential key. # - :token_credential_key - # The token credential key. # - :signature_method - # The signature method. Defaults to 'HMAC-SHA1'. # - :two_legged - # A switch for two-legged OAuth. Defaults to false. # # @return [Array] # The parameter list as an Array of key/value pairs. def self.unsigned_resource_parameters options = {} options = { signature_method: "HMAC-SHA1", two_legged: false }.merge(options) client_credential_key = extract_credential_key_option :client, options raise ArgumentError, "Missing :client_credential_key parameter." if client_credential_key.nil? unless options[:two_legged] token_credential_key = extract_credential_key_option :token, options raise ArgumentError, "Missing :token_credential_key parameter." if token_credential_key.nil? end parameters = [ ["oauth_consumer_key", client_credential_key], ["oauth_signature_method", options[:signature_method]], ["oauth_timestamp", generate_timestamp], ["oauth_nonce", generate_nonce], ["oauth_version", "1.0"] ] parameters << ["oauth_token", token_credential_key] unless options[:two_legged] # No additional parameters allowed here parameters end end end signet-0.14.0/Gemfile0000644000004100000410000000021213673221365014413 0ustar www-datawww-datasource "https://rubygems.org" gemspec gem "bundler", ">= 1.15" gem "gems", "~> 1.2" gem "hurley" gem "jruby-openssl", platforms: :jruby signet-0.14.0/website/0000755000004100000410000000000013673221365014567 5ustar www-datawww-datasignet-0.14.0/website/index.html0000644000004100000410000000343013673221365016564 0ustar www-datawww-data Signet

Signet

Signet is an OAuth 1.0 / OAuth 2.0 implementation.

You know what to do:

sudo gem install signet