googleauth 0.5.1 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.github/CODEOWNERS +7 -0
- data/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +5 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +36 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +21 -0
- data/.github/ISSUE_TEMPLATE/support_request.md +7 -0
- data/.kokoro/build.bat +16 -0
- data/.kokoro/build.sh +4 -0
- data/.kokoro/continuous/common.cfg +24 -0
- data/.kokoro/continuous/linux.cfg +25 -0
- data/.kokoro/continuous/osx.cfg +8 -0
- data/.kokoro/continuous/post.cfg +30 -0
- data/.kokoro/continuous/windows.cfg +29 -0
- data/.kokoro/osx.sh +4 -0
- data/.kokoro/presubmit/common.cfg +24 -0
- data/.kokoro/presubmit/linux.cfg +24 -0
- data/.kokoro/presubmit/osx.cfg +8 -0
- data/.kokoro/presubmit/windows.cfg +29 -0
- data/.kokoro/release.cfg +94 -0
- data/.kokoro/trampoline.bat +10 -0
- data/.kokoro/trampoline.sh +4 -0
- data/.repo-metadata.json +5 -0
- data/.rubocop.yml +19 -1
- data/CHANGELOG.md +112 -19
- data/CODE_OF_CONDUCT.md +43 -0
- data/Gemfile +19 -13
- data/{COPYING → LICENSE} +0 -0
- data/README.md +58 -18
- data/Rakefile +126 -9
- data/googleauth.gemspec +28 -25
- data/integration/helper.rb +31 -0
- data/integration/id_tokens/key_source_test.rb +74 -0
- data/lib/googleauth.rb +7 -96
- data/lib/googleauth/application_default.rb +81 -0
- data/lib/googleauth/client_id.rb +21 -19
- data/lib/googleauth/compute_engine.rb +70 -43
- data/lib/googleauth/credentials.rb +442 -0
- data/lib/googleauth/credentials_loader.rb +117 -43
- data/lib/googleauth/default_credentials.rb +93 -0
- data/lib/googleauth/iam.rb +11 -11
- data/lib/googleauth/id_tokens.rb +233 -0
- data/lib/googleauth/id_tokens/errors.rb +71 -0
- data/lib/googleauth/id_tokens/key_sources.rb +394 -0
- data/lib/googleauth/id_tokens/verifier.rb +144 -0
- data/lib/googleauth/json_key_reader.rb +50 -0
- data/lib/googleauth/scope_util.rb +12 -12
- data/lib/googleauth/service_account.rb +74 -63
- data/lib/googleauth/signet.rb +55 -13
- data/lib/googleauth/stores/file_token_store.rb +8 -8
- data/lib/googleauth/stores/redis_token_store.rb +22 -22
- data/lib/googleauth/token_store.rb +6 -6
- data/lib/googleauth/user_authorizer.rb +80 -68
- data/lib/googleauth/user_refresh.rb +44 -35
- data/lib/googleauth/version.rb +1 -1
- data/lib/googleauth/web_user_authorizer.rb +77 -68
- data/rakelib/devsite_builder.rb +45 -0
- data/rakelib/link_checker.rb +64 -0
- data/rakelib/repo_metadata.rb +59 -0
- data/spec/googleauth/apply_auth_examples.rb +74 -50
- data/spec/googleauth/client_id_spec.rb +75 -55
- data/spec/googleauth/compute_engine_spec.rb +98 -46
- data/spec/googleauth/credentials_spec.rb +478 -0
- data/spec/googleauth/get_application_default_spec.rb +149 -111
- data/spec/googleauth/iam_spec.rb +25 -25
- data/spec/googleauth/scope_util_spec.rb +26 -24
- data/spec/googleauth/service_account_spec.rb +269 -144
- data/spec/googleauth/signet_spec.rb +101 -30
- data/spec/googleauth/stores/file_token_store_spec.rb +12 -13
- data/spec/googleauth/stores/redis_token_store_spec.rb +11 -11
- data/spec/googleauth/stores/store_examples.rb +16 -16
- data/spec/googleauth/user_authorizer_spec.rb +153 -124
- data/spec/googleauth/user_refresh_spec.rb +186 -121
- data/spec/googleauth/web_user_authorizer_spec.rb +82 -69
- data/spec/spec_helper.rb +21 -19
- data/test/helper.rb +33 -0
- data/test/id_tokens/key_sources_test.rb +240 -0
- data/test/id_tokens/verifier_test.rb +269 -0
- metadata +87 -34
- data/.rubocop_todo.yml +0 -32
- data/.travis.yml +0 -37
@@ -27,45 +27,116 @@
|
|
27
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
28
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
29
|
|
30
|
-
spec_dir = File.expand_path
|
31
|
-
$LOAD_PATH.unshift
|
30
|
+
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
|
31
|
+
$LOAD_PATH.unshift spec_dir
|
32
32
|
$LOAD_PATH.uniq!
|
33
33
|
|
34
|
-
require
|
35
|
-
require
|
36
|
-
require
|
37
|
-
require
|
38
|
-
require
|
34
|
+
require "apply_auth_examples"
|
35
|
+
require "googleauth/signet"
|
36
|
+
require "jwt"
|
37
|
+
require "openssl"
|
38
|
+
require "spec_helper"
|
39
39
|
|
40
40
|
describe Signet::OAuth2::Client do
|
41
|
-
before
|
42
|
-
@key = OpenSSL::PKey::RSA.new
|
41
|
+
before :example do
|
42
|
+
@key = OpenSSL::PKey::RSA.new 2048
|
43
43
|
@client = Signet::OAuth2::Client.new(
|
44
|
-
token_credential_uri:
|
45
|
-
scope:
|
46
|
-
issuer:
|
47
|
-
audience:
|
48
|
-
signing_key:
|
44
|
+
token_credential_uri: "https://oauth2.googleapis.com/token",
|
45
|
+
scope: "https://www.googleapis.com/auth/userinfo.profile",
|
46
|
+
issuer: "app@example.com",
|
47
|
+
audience: "https://oauth2.googleapis.com/token",
|
48
|
+
signing_key: @key
|
49
|
+
)
|
50
|
+
@id_client = Signet::OAuth2::Client.new(
|
51
|
+
token_credential_uri: "https://oauth2.googleapis.com/token",
|
52
|
+
target_audience: "https://pubsub.googleapis.com/",
|
53
|
+
issuer: "app@example.com",
|
54
|
+
audience: "https://oauth2.googleapis.com/token",
|
55
|
+
signing_key: @key
|
56
|
+
)
|
49
57
|
end
|
50
58
|
|
51
|
-
def make_auth_stubs
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
59
|
+
def make_auth_stubs opts
|
60
|
+
body_fields = { "token_type" => "Bearer", "expires_in" => 3600 }
|
61
|
+
body_fields["access_token"] = opts[:access_token] if opts[:access_token]
|
62
|
+
body_fields["id_token"] = opts[:id_token] if opts[:id_token]
|
63
|
+
body = MultiJson.dump body_fields
|
56
64
|
blk = proc do |request|
|
57
|
-
params = Addressable::URI.form_unencode
|
58
|
-
|
59
|
-
|
65
|
+
params = Addressable::URI.form_unencode request.body
|
66
|
+
claim, _header = JWT.decode(params.assoc("assertion").last,
|
67
|
+
@key.public_key, true,
|
68
|
+
algorithm: "RS256")
|
69
|
+
!opts[:id_token] || claim["target_audience"] == "https://pubsub.googleapis.com/"
|
60
70
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
71
|
+
with_params = { body: hash_including(
|
72
|
+
"grant_type" => "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
73
|
+
) }
|
74
|
+
with_params[:headers] = { "User-Agent" => opts[:user_agent] } if opts[:user_agent]
|
75
|
+
stub_request(:post, "https://oauth2.googleapis.com/token")
|
76
|
+
.with(with_params, &blk)
|
77
|
+
.to_return(body: body,
|
78
|
+
status: 200,
|
79
|
+
headers: { "Content-Type" => "application/json" })
|
68
80
|
end
|
69
81
|
|
70
|
-
it_behaves_like
|
82
|
+
it_behaves_like "apply/apply! are OK"
|
83
|
+
|
84
|
+
describe "#configure_connection" do
|
85
|
+
it "honors default_connection" do
|
86
|
+
token = "1/abcdef1234567890"
|
87
|
+
stub = make_auth_stubs access_token: token, user_agent: "RubyRocks/1.0"
|
88
|
+
conn = Faraday.new headers: { "User-Agent" => "RubyRocks/1.0" }
|
89
|
+
@client.configure_connection default_connection: conn
|
90
|
+
md = { foo: "bar" }
|
91
|
+
@client.apply! md
|
92
|
+
want = { foo: "bar", authorization: "Bearer #{token}" }
|
93
|
+
expect(md).to eq(want)
|
94
|
+
expect(stub).to have_been_requested
|
95
|
+
end
|
96
|
+
|
97
|
+
it "honors connection_builder" do
|
98
|
+
token = "1/abcdef1234567890"
|
99
|
+
stub = make_auth_stubs access_token: token, user_agent: "RubyRocks/2.0"
|
100
|
+
connection_builder = proc do
|
101
|
+
Faraday.new headers: { "User-Agent" => "RubyRocks/2.0" }
|
102
|
+
end
|
103
|
+
@client.configure_connection connection_builder: connection_builder
|
104
|
+
md = { foo: "bar" }
|
105
|
+
@client.apply! md
|
106
|
+
want = { foo: "bar", authorization: "Bearer #{token}" }
|
107
|
+
expect(md).to eq(want)
|
108
|
+
expect(stub).to have_been_requested
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
describe "#fetch_access_token!" do
|
113
|
+
it "retries when orig_fetch_access_token! raises Signet::RemoteServerError" do
|
114
|
+
mocked_responses = [:raise, :raise, "success"]
|
115
|
+
allow(@client).to receive(:orig_fetch_access_token!).exactly(3).times do
|
116
|
+
response = mocked_responses.shift
|
117
|
+
response == :raise ? raise(Signet::RemoteServerError) : response
|
118
|
+
end
|
119
|
+
expect(@client.fetch_access_token!).to eq("success")
|
120
|
+
end
|
121
|
+
|
122
|
+
it "raises when the max retry count is exceeded" do
|
123
|
+
mocked_responses = [:raise, :raise, :raise, :raise, :raise, :raise, "success"]
|
124
|
+
allow(@client).to receive(:orig_fetch_access_token!).exactly(6).times do
|
125
|
+
response = mocked_responses.shift
|
126
|
+
response == :raise ? raise(Signet::RemoteServerError) : response
|
127
|
+
end
|
128
|
+
expect { @client.fetch_access_token! }.to raise_error Signet::AuthorizationError
|
129
|
+
end
|
130
|
+
|
131
|
+
it "does not retry and raises right away if it encounters a Signet::AuthorizationError" do
|
132
|
+
allow(@client).to receive(:orig_fetch_access_token!).at_most(:once)
|
133
|
+
.and_raise(Signet::AuthorizationError.new("Some Message"))
|
134
|
+
expect { @client.fetch_access_token! }.to raise_error Signet::AuthorizationError
|
135
|
+
end
|
136
|
+
|
137
|
+
it "does not retry and raises right away if it encounters a Signet::ParseError" do
|
138
|
+
allow(@client).to receive(:orig_fetch_access_token!).at_most(:once).and_raise(Signet::ParseError)
|
139
|
+
expect { @client.fetch_access_token! }.to raise_error Signet::ParseError
|
140
|
+
end
|
141
|
+
end
|
71
142
|
end
|
@@ -27,32 +27,31 @@
|
|
27
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
28
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
29
|
|
30
|
-
spec_dir = File.expand_path
|
31
|
-
$LOAD_PATH.unshift
|
30
|
+
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
|
31
|
+
$LOAD_PATH.unshift spec_dir
|
32
32
|
$LOAD_PATH.uniq!
|
33
33
|
|
34
|
-
require
|
35
|
-
require
|
36
|
-
require
|
37
|
-
require
|
38
|
-
require
|
39
|
-
require
|
34
|
+
require "googleauth"
|
35
|
+
require "googleauth/stores/file_token_store"
|
36
|
+
require "spec_helper"
|
37
|
+
require "fakefs/safe"
|
38
|
+
require "fakefs/spec_helpers"
|
39
|
+
require "googleauth/stores/store_examples"
|
40
40
|
|
41
41
|
module FakeFS
|
42
42
|
class File
|
43
43
|
# FakeFS doesn't implement. And since we don't need to actually lock,
|
44
44
|
# just stub out...
|
45
|
-
def flock
|
46
|
-
end
|
45
|
+
def flock *; end
|
47
46
|
end
|
48
47
|
end
|
49
48
|
|
50
49
|
describe Google::Auth::Stores::FileTokenStore do
|
51
50
|
include FakeFS::SpecHelpers
|
52
51
|
|
53
|
-
let
|
54
|
-
Google::Auth::Stores::FileTokenStore.new
|
52
|
+
let :store do
|
53
|
+
Google::Auth::Stores::FileTokenStore.new file: "/tokens.yaml"
|
55
54
|
end
|
56
55
|
|
57
|
-
it_behaves_like
|
56
|
+
it_behaves_like "token store"
|
58
57
|
end
|
@@ -27,24 +27,24 @@
|
|
27
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
28
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
29
|
|
30
|
-
spec_dir = File.expand_path
|
31
|
-
$LOAD_PATH.unshift
|
30
|
+
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
|
31
|
+
$LOAD_PATH.unshift spec_dir
|
32
32
|
$LOAD_PATH.uniq!
|
33
33
|
|
34
|
-
require
|
35
|
-
require
|
36
|
-
require
|
37
|
-
require
|
38
|
-
require
|
34
|
+
require "googleauth"
|
35
|
+
require "googleauth/stores/redis_token_store"
|
36
|
+
require "spec_helper"
|
37
|
+
require "fakeredis/rspec"
|
38
|
+
require "googleauth/stores/store_examples"
|
39
39
|
|
40
40
|
describe Google::Auth::Stores::RedisTokenStore do
|
41
|
-
let
|
41
|
+
let :redis do
|
42
42
|
Redis.new
|
43
43
|
end
|
44
44
|
|
45
|
-
let
|
46
|
-
Google::Auth::Stores::RedisTokenStore.new
|
45
|
+
let :store do
|
46
|
+
Google::Auth::Stores::RedisTokenStore.new redis: redis
|
47
47
|
end
|
48
48
|
|
49
|
-
it_behaves_like
|
49
|
+
it_behaves_like "token store"
|
50
50
|
end
|
@@ -27,32 +27,32 @@
|
|
27
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
28
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
29
|
|
30
|
-
spec_dir = File.expand_path
|
31
|
-
$LOAD_PATH.unshift
|
30
|
+
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
|
31
|
+
$LOAD_PATH.unshift spec_dir
|
32
32
|
$LOAD_PATH.uniq!
|
33
33
|
|
34
|
-
require
|
34
|
+
require "spec_helper"
|
35
35
|
|
36
|
-
shared_examples
|
37
|
-
before
|
38
|
-
store.store
|
36
|
+
shared_examples "token store" do
|
37
|
+
before :each do
|
38
|
+
store.store "default", "test"
|
39
39
|
end
|
40
40
|
|
41
|
-
it
|
42
|
-
expect(store.load(
|
41
|
+
it "should return a stored value" do
|
42
|
+
expect(store.load("default")).to eq "test"
|
43
43
|
end
|
44
44
|
|
45
|
-
it
|
46
|
-
expect(store.load(
|
45
|
+
it "should return nil for missing tokens" do
|
46
|
+
expect(store.load("notavalidkey")).to be_nil
|
47
47
|
end
|
48
48
|
|
49
|
-
it
|
50
|
-
store.delete
|
51
|
-
expect(store.load(
|
49
|
+
it "should return nil for deleted tokens" do
|
50
|
+
store.delete "default"
|
51
|
+
expect(store.load("default")).to be_nil
|
52
52
|
end
|
53
53
|
|
54
|
-
it
|
55
|
-
store.store
|
56
|
-
expect(store.load(
|
54
|
+
it "should save overwrite values on store" do
|
55
|
+
store.store "default", "test2"
|
56
|
+
expect(store.load("default")).to eq "test2"
|
57
57
|
end
|
58
58
|
end
|
@@ -27,285 +27,314 @@
|
|
27
27
|
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
28
28
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
29
29
|
|
30
|
-
spec_dir = File.expand_path
|
31
|
-
$LOAD_PATH.unshift
|
30
|
+
spec_dir = File.expand_path File.join(File.dirname(__FILE__))
|
31
|
+
$LOAD_PATH.unshift spec_dir
|
32
32
|
$LOAD_PATH.uniq!
|
33
33
|
|
34
|
-
require
|
35
|
-
require
|
36
|
-
require
|
37
|
-
require
|
38
|
-
require
|
34
|
+
require "googleauth"
|
35
|
+
require "googleauth/user_authorizer"
|
36
|
+
require "uri"
|
37
|
+
require "multi_json"
|
38
|
+
require "spec_helper"
|
39
39
|
|
40
40
|
describe Google::Auth::UserAuthorizer do
|
41
41
|
include TestHelpers
|
42
42
|
|
43
|
-
let(:client_id) { Google::Auth::ClientId.new
|
44
|
-
let(:scope) { %w
|
43
|
+
let(:client_id) { Google::Auth::ClientId.new "testclient", "notasecret" }
|
44
|
+
let(:scope) { %w[email profile] }
|
45
45
|
let(:token_store) { DummyTokenStore.new }
|
46
|
-
let(:callback_uri) {
|
47
|
-
let
|
46
|
+
let(:callback_uri) { "https://www.example.com/oauth/callback" }
|
47
|
+
let :authorizer do
|
48
48
|
Google::Auth::UserAuthorizer.new(client_id,
|
49
49
|
scope,
|
50
50
|
token_store,
|
51
51
|
callback_uri)
|
52
52
|
end
|
53
53
|
|
54
|
-
shared_examples
|
55
|
-
it
|
54
|
+
shared_examples "valid authorization url" do
|
55
|
+
it "should have a valid base URI" do
|
56
56
|
expect(uri).to match %r{https://accounts.google.com/o/oauth2/auth}
|
57
57
|
end
|
58
58
|
|
59
|
-
it
|
59
|
+
it "should request offline access" do
|
60
60
|
expect(URI(uri).query).to match(/access_type=offline/)
|
61
61
|
end
|
62
62
|
|
63
|
-
it
|
63
|
+
it "should request response type code" do
|
64
64
|
expect(URI(uri).query).to match(/response_type=code/)
|
65
65
|
end
|
66
66
|
|
67
|
-
it
|
67
|
+
it "should force approval" do
|
68
68
|
expect(URI(uri).query).to match(/approval_prompt=force/)
|
69
69
|
end
|
70
70
|
|
71
|
-
it
|
71
|
+
it "should include granted scopes" do
|
72
72
|
expect(URI(uri).query).to match(/include_granted_scopes=true/)
|
73
73
|
end
|
74
74
|
|
75
|
-
it
|
75
|
+
it "should include the correct client id" do
|
76
76
|
expect(URI(uri).query).to match(/client_id=testclient/)
|
77
77
|
end
|
78
78
|
|
79
|
-
it
|
79
|
+
it "should not include a client secret" do
|
80
80
|
expect(URI(uri).query).to_not match(/client_secret/)
|
81
81
|
end
|
82
82
|
|
83
|
-
it
|
83
|
+
it "should include the redirect_uri" do
|
84
84
|
expect(URI(uri).query).to match(
|
85
|
-
%r{redirect_uri=https://www.example.com/oauth/callback}
|
85
|
+
%r{redirect_uri=https://www.example.com/oauth/callback}
|
86
|
+
)
|
86
87
|
end
|
87
88
|
|
88
|
-
it
|
89
|
+
it "should include the scope" do
|
89
90
|
expect(URI(uri).query).to match(/scope=email%20profile/)
|
90
91
|
end
|
91
92
|
end
|
92
93
|
|
93
|
-
context
|
94
|
-
let(:
|
95
|
-
|
94
|
+
context "when generating authorization URLs and callback_uri is 'postmessage'" do
|
95
|
+
let(:callback_uri) { "postmessage" }
|
96
|
+
let :authorizer do
|
97
|
+
Google::Auth::UserAuthorizer.new(client_id,
|
98
|
+
scope,
|
99
|
+
token_store,
|
100
|
+
callback_uri)
|
101
|
+
end
|
102
|
+
let :uri do
|
103
|
+
authorizer.get_authorization_url login_hint: "user1", state: "mystate"
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should include the redirect_uri 'postmessage'" do
|
107
|
+
expect(URI(uri).query).to match(
|
108
|
+
%r{redirect_uri=postmessage}
|
109
|
+
)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
context "when generating authorization URLs with user ID & state" do
|
114
|
+
let :uri do
|
115
|
+
authorizer.get_authorization_url login_hint: "user1", state: "mystate"
|
96
116
|
end
|
97
117
|
|
98
|
-
it_behaves_like
|
118
|
+
it_behaves_like "valid authorization url"
|
99
119
|
|
100
|
-
it
|
120
|
+
it "includes a login hint" do
|
101
121
|
expect(URI(uri).query).to match(/login_hint=user1/)
|
102
122
|
end
|
103
123
|
|
104
|
-
it
|
124
|
+
it "includes the app state" do
|
105
125
|
expect(URI(uri).query).to match(/state=mystate/)
|
106
126
|
end
|
107
127
|
end
|
108
128
|
|
109
|
-
context
|
110
|
-
let(:uri) { authorizer.get_authorization_url
|
129
|
+
context "when generating authorization URLs with user ID and no state" do
|
130
|
+
let(:uri) { authorizer.get_authorization_url login_hint: "user1" }
|
111
131
|
|
112
|
-
it_behaves_like
|
132
|
+
it_behaves_like "valid authorization url"
|
113
133
|
|
114
|
-
it
|
134
|
+
it "includes a login hint" do
|
115
135
|
expect(URI(uri).query).to match(/login_hint=user1/)
|
116
136
|
end
|
117
137
|
|
118
|
-
it
|
138
|
+
it "does not include the state parameter" do
|
119
139
|
expect(URI(uri).query).to_not match(/state/)
|
120
140
|
end
|
121
141
|
end
|
122
142
|
|
123
|
-
context
|
143
|
+
context "when generating authorization URLs with no user ID and no state" do
|
124
144
|
let(:uri) { authorizer.get_authorization_url }
|
125
145
|
|
126
|
-
it_behaves_like
|
146
|
+
it_behaves_like "valid authorization url"
|
127
147
|
|
128
|
-
it
|
148
|
+
it "does not include the login hint parameter" do
|
129
149
|
expect(URI(uri).query).to_not match(/login_hint/)
|
130
150
|
end
|
131
151
|
|
132
|
-
it
|
152
|
+
it "does not include the state parameter" do
|
133
153
|
expect(URI(uri).query).to_not match(/state/)
|
134
154
|
end
|
135
155
|
end
|
136
156
|
|
137
|
-
context
|
138
|
-
let
|
157
|
+
context "when retrieving tokens" do
|
158
|
+
let :token_json do
|
139
159
|
MultiJson.dump(
|
140
|
-
access_token:
|
141
|
-
refresh_token:
|
142
|
-
expiration_time_millis: 1_441_234_742_000
|
160
|
+
access_token: "accesstoken",
|
161
|
+
refresh_token: "refreshtoken",
|
162
|
+
expiration_time_millis: 1_441_234_742_000
|
163
|
+
)
|
143
164
|
end
|
144
165
|
|
145
|
-
context
|
146
|
-
let
|
147
|
-
token_store.store
|
148
|
-
authorizer.get_credentials
|
166
|
+
context "with a valid user id" do
|
167
|
+
let :credentials do
|
168
|
+
token_store.store "user1", token_json
|
169
|
+
authorizer.get_credentials "user1"
|
149
170
|
end
|
150
171
|
|
151
|
-
it
|
172
|
+
it "should return an instance of UserRefreshCredentials" do
|
152
173
|
expect(credentials).to be_instance_of(
|
153
|
-
Google::Auth::UserRefreshCredentials
|
174
|
+
Google::Auth::UserRefreshCredentials
|
175
|
+
)
|
154
176
|
end
|
155
177
|
|
156
|
-
it
|
157
|
-
expect(credentials.refresh_token).to eq
|
178
|
+
it "should return credentials with a valid refresh token" do
|
179
|
+
expect(credentials.refresh_token).to eq "refreshtoken"
|
158
180
|
end
|
159
181
|
|
160
|
-
it
|
161
|
-
expect(credentials.access_token).to eq
|
182
|
+
it "should return credentials with a valid access token" do
|
183
|
+
expect(credentials.access_token).to eq "accesstoken"
|
162
184
|
end
|
163
185
|
|
164
|
-
it
|
165
|
-
expect(credentials.client_id).to eq
|
186
|
+
it "should return credentials with a valid client ID" do
|
187
|
+
expect(credentials.client_id).to eq "testclient"
|
166
188
|
end
|
167
189
|
|
168
|
-
it
|
169
|
-
expect(credentials.client_secret).to eq
|
190
|
+
it "should return credentials with a valid client secret" do
|
191
|
+
expect(credentials.client_secret).to eq "notasecret"
|
170
192
|
end
|
171
193
|
|
172
|
-
it
|
173
|
-
expect(credentials.scope).to eq %w
|
194
|
+
it "should return credentials with a valid scope" do
|
195
|
+
expect(credentials.scope).to eq %w[email profile]
|
174
196
|
end
|
175
197
|
|
176
|
-
it
|
198
|
+
it "should return credentials with a valid expiration time" do
|
177
199
|
expect(credentials.expires_at).to eq Time.at(1_441_234_742)
|
178
200
|
end
|
179
201
|
end
|
180
202
|
|
181
|
-
context
|
182
|
-
it
|
183
|
-
expect(authorizer.get_credentials(
|
203
|
+
context "with an invalid user id" do
|
204
|
+
it "should return nil" do
|
205
|
+
expect(authorizer.get_credentials("notauser")).to be_nil
|
184
206
|
end
|
185
207
|
end
|
186
208
|
end
|
187
209
|
|
188
|
-
context
|
210
|
+
context "when saving tokens" do
|
189
211
|
let(:expiry) { Time.now.to_i }
|
190
|
-
let
|
212
|
+
let :credentials do
|
191
213
|
Google::Auth::UserRefreshCredentials.new(
|
192
|
-
client_id:
|
214
|
+
client_id: client_id.id,
|
193
215
|
client_secret: client_id.secret,
|
194
|
-
scope:
|
195
|
-
refresh_token:
|
196
|
-
access_token:
|
197
|
-
expires_at:
|
216
|
+
scope: scope,
|
217
|
+
refresh_token: "refreshtoken",
|
218
|
+
access_token: "accesstoken",
|
219
|
+
expires_at: expiry
|
198
220
|
)
|
199
221
|
end
|
200
222
|
|
201
|
-
let
|
202
|
-
authorizer.store_credentials
|
203
|
-
token_store.load
|
223
|
+
let :token_json do
|
224
|
+
authorizer.store_credentials "user1", credentials
|
225
|
+
token_store.load "user1"
|
204
226
|
end
|
205
227
|
|
206
|
-
it
|
228
|
+
it "should persist in the token store" do
|
207
229
|
expect(token_json).to_not be_nil
|
208
230
|
end
|
209
231
|
|
210
|
-
it
|
211
|
-
expect(MultiJson.load(token_json)[
|
232
|
+
it "should persist the refresh token" do
|
233
|
+
expect(MultiJson.load(token_json)["refresh_token"]).to eq "refreshtoken"
|
212
234
|
end
|
213
235
|
|
214
|
-
it
|
215
|
-
expect(MultiJson.load(token_json)[
|
236
|
+
it "should persist the access token" do
|
237
|
+
expect(MultiJson.load(token_json)["access_token"]).to eq "accesstoken"
|
216
238
|
end
|
217
239
|
|
218
|
-
it
|
219
|
-
expect(MultiJson.load(token_json)[
|
240
|
+
it "should persist the client id" do
|
241
|
+
expect(MultiJson.load(token_json)["client_id"]).to eq "testclient"
|
220
242
|
end
|
221
243
|
|
222
|
-
it
|
223
|
-
expect(MultiJson.load(token_json)[
|
244
|
+
it "should persist the scope" do
|
245
|
+
expect(MultiJson.load(token_json)["scope"]).to include("email", "profile")
|
224
246
|
end
|
225
247
|
|
226
|
-
it
|
248
|
+
it "should persist the expiry as milliseconds" do
|
227
249
|
expected_expiry = expiry * 1000
|
228
|
-
expect(MultiJson.load(token_json)[
|
229
|
-
expected_expiry
|
250
|
+
expect(MultiJson.load(token_json)["expiration_time_millis"]).to eql(
|
251
|
+
expected_expiry
|
252
|
+
)
|
230
253
|
end
|
231
254
|
end
|
232
255
|
|
233
|
-
context
|
234
|
-
let
|
235
|
-
MultiJson.dump(
|
236
|
-
|
237
|
-
|
256
|
+
context "with valid authorization code" do
|
257
|
+
let :token_json do
|
258
|
+
MultiJson.dump("access_token" => "1/abc123",
|
259
|
+
"token_type" => "Bearer",
|
260
|
+
"expires_in" => 3600)
|
238
261
|
end
|
239
262
|
|
240
|
-
before
|
241
|
-
stub_request(:post,
|
263
|
+
before :example do
|
264
|
+
stub_request(:post, "https://oauth2.googleapis.com/token")
|
242
265
|
.to_return(body: token_json, status: 200, headers: {
|
243
|
-
|
266
|
+
"Content-Type" => "application/json"
|
267
|
+
})
|
244
268
|
end
|
245
269
|
|
246
|
-
it
|
270
|
+
it "should exchange a code for credentials" do
|
247
271
|
credentials = authorizer.get_credentials_from_code(
|
248
|
-
user_id:
|
249
|
-
|
272
|
+
user_id: "user1", code: "code"
|
273
|
+
)
|
274
|
+
expect(credentials.access_token).to eq "1/abc123"
|
275
|
+
expect(credentials.redirect_uri.to_s).to eq "https://www.example.com/oauth/callback"
|
250
276
|
end
|
251
277
|
|
252
|
-
it
|
253
|
-
authorizer.get_credentials_from_code
|
254
|
-
expect(token_store.load(
|
278
|
+
it "should not store credentials when get only requested" do
|
279
|
+
authorizer.get_credentials_from_code user_id: "user1", code: "code"
|
280
|
+
expect(token_store.load("user1")).to be_nil
|
255
281
|
end
|
256
282
|
|
257
|
-
it
|
283
|
+
it "should store credentials when requested" do
|
258
284
|
authorizer.get_and_store_credentials_from_code(
|
259
|
-
user_id:
|
260
|
-
|
285
|
+
user_id: "user1", code: "code"
|
286
|
+
)
|
287
|
+
expect(token_store.load("user1")).to_not be_nil
|
261
288
|
end
|
262
289
|
end
|
263
290
|
|
264
|
-
context
|
265
|
-
before
|
266
|
-
stub_request(:post,
|
291
|
+
context "with invalid authorization code" do
|
292
|
+
before :example do
|
293
|
+
stub_request(:post, "https://oauth2.googleapis.com/token")
|
267
294
|
.to_return(status: 400)
|
268
295
|
end
|
269
296
|
|
270
|
-
it
|
297
|
+
it "should raise an authorization error" do
|
271
298
|
expect do
|
272
|
-
authorizer.get_credentials_from_code
|
299
|
+
authorizer.get_credentials_from_code user_id: "user1", code: "badcode"
|
273
300
|
end.to raise_error Signet::AuthorizationError
|
274
301
|
end
|
275
302
|
|
276
|
-
it
|
303
|
+
it "should not store credentials when exchange fails" do
|
277
304
|
expect do
|
278
|
-
authorizer.get_credentials_from_code
|
305
|
+
authorizer.get_credentials_from_code user_id: "user1", code: "badcode"
|
279
306
|
end.to raise_error Signet::AuthorizationError
|
280
|
-
expect(token_store.load(
|
307
|
+
expect(token_store.load("user1")).to be_nil
|
281
308
|
end
|
282
309
|
end
|
283
310
|
|
284
|
-
context
|
285
|
-
let
|
311
|
+
context "when reovking authorization" do
|
312
|
+
let :token_json do
|
286
313
|
MultiJson.dump(
|
287
|
-
access_token:
|
288
|
-
refresh_token:
|
289
|
-
expiration_time_millis: 1_441_234_742_000
|
314
|
+
access_token: "accesstoken",
|
315
|
+
refresh_token: "refreshtoken",
|
316
|
+
expiration_time_millis: 1_441_234_742_000
|
317
|
+
)
|
290
318
|
end
|
291
319
|
|
292
|
-
before
|
293
|
-
token_store.store
|
294
|
-
stub_request(
|
295
|
-
:
|
320
|
+
before :example do
|
321
|
+
token_store.store "user1", token_json
|
322
|
+
stub_request(:post, "https://oauth2.googleapis.com/revoke")
|
323
|
+
.with(body: hash_including("token" => "refreshtoken"))
|
296
324
|
.to_return(status: 200)
|
297
325
|
end
|
298
326
|
|
299
|
-
it
|
300
|
-
authorizer.revoke_authorization
|
327
|
+
it "should revoke the grant" do
|
328
|
+
authorizer.revoke_authorization "user1"
|
301
329
|
expect(a_request(
|
302
|
-
|
330
|
+
:post, "https://oauth2.googleapis.com/revoke"
|
331
|
+
).with(body: hash_including("token" => "refreshtoken")))
|
303
332
|
.to have_been_made
|
304
333
|
end
|
305
334
|
|
306
|
-
it
|
307
|
-
authorizer.revoke_authorization
|
308
|
-
expect(token_store.load(
|
335
|
+
it "should remove the token from storage" do
|
336
|
+
authorizer.revoke_authorization "user1"
|
337
|
+
expect(token_store.load("user1")).to be_nil
|
309
338
|
end
|
310
339
|
end
|
311
340
|
|