simple_google_auth 0.0.6 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +40 -32
- data/lib/simple_google_auth.rb +15 -41
- data/lib/simple_google_auth/auth_data_presenter.rb +50 -0
- data/lib/simple_google_auth/authorization_uri_builder.rb +28 -0
- data/lib/simple_google_auth/config.rb +44 -0
- data/lib/simple_google_auth/controller.rb +15 -7
- data/lib/simple_google_auth/http_client.rb +20 -15
- data/lib/simple_google_auth/oauth.rb +19 -12
- data/lib/simple_google_auth/receiver.rb +2 -3
- data/lib/simple_google_auth/version.rb +1 -1
- data/spec/simple_google_auth/auth_data_presenter_spec.rb +41 -0
- data/spec/simple_google_auth/authorization_uri_builder_spec.rb +13 -0
- data/spec/simple_google_auth/config_spec.rb +39 -0
- data/spec/simple_google_auth/controller_spec.rb +48 -0
- data/spec/simple_google_auth/http_client_spec.rb +80 -0
- data/spec/simple_google_auth/oauth_spec.rb +65 -0
- data/spec/simple_google_auth/receiver_spec.rb +77 -0
- data/spec/simple_google_auth_spec.rb +13 -0
- data/spec/spec_helper.rb +102 -0
- metadata +38 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2c7a9d7423d02f4853b934ee51ee465b3e5211dc
|
4
|
+
data.tar.gz: 4d0e7eab5f74f770e44fd2b33036010d9eef7c89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 00ff54f0f86e9c386e2d8324549b138b8e94abeffc94c9e4959c339c244ca21a0b6005ff8b95369ed34db98ce257d353f191b981c3200cdaef66565e569d4382
|
7
|
+
data.tar.gz: 3eaf3489ed9eaebc0a42e2600314a7d870ed38c354e25f325c2cc97ea8141f00a664d237c6153df927d56da67dabf6dc312c0d7eea4386aa0788d094f750f413
|
data/README.md
CHANGED
@@ -13,42 +13,41 @@ such as OmniAuth's Google strategy.
|
|
13
13
|
|
14
14
|
## Installation
|
15
15
|
|
16
|
-
Follow these
|
16
|
+
Follow these four steps to integrate with your site.
|
17
17
|
|
18
|
-
Step 1: Make yourself a project at https://cloud.google.com/console, if you haven't already.
|
18
|
+
Step 1: Make yourself a project at https://cloud.google.com/console, if you haven't already. In that project, go to the "APIs & auth" tab, then the "Credentials" tab. Create a new client ID of application type "Web application". Set the Authorized Redirect URI to
|
19
|
+
`https://yoursite.com/google-callback`. You might want to put in `http://localhost:3000/google-callback` so you can test locally too.
|
19
20
|
|
20
|
-
Step 2:
|
21
|
-
`http://yoursite.com/google-callback`. You might want to put in `http://localhost:3000/google-callback` so you can test locally too.
|
22
|
-
|
23
|
-
Step 3: Add simple_google_auth to your Gemfile
|
21
|
+
Step 2: Add simple_google_auth to your `Gemfile` and run `bundle`
|
24
22
|
|
25
23
|
gem 'simple_google_auth'
|
26
24
|
|
27
|
-
Step
|
25
|
+
Step 3: Add the following code to the bottom of your `config/application.rb` and tweak it with your site's values:
|
28
26
|
|
29
27
|
SimpleGoogleAuth.configure do |config|
|
30
28
|
config.client_id = "the client ID as supplied by Google in step 2"
|
31
29
|
config.client_secret = "the client secret as supplied by Google in step 2"
|
32
30
|
config.redirect_uri = "http://localhost:3000/google-callback"
|
33
31
|
config.authenticate = lambda do |data|
|
34
|
-
data
|
32
|
+
data.email == "your.email@example.com" || data.email.ends_with?("@example.net")
|
35
33
|
end
|
36
34
|
end
|
37
35
|
|
38
|
-
Step
|
36
|
+
Step 4: In your `application_controller.rb`, add a before action:
|
39
37
|
|
40
|
-
|
38
|
+
before_action :redirect_if_not_google_authenticated
|
41
39
|
|
42
|
-
Done! Any request to your site will now redirect
|
43
|
-
A route that captures requests
|
40
|
+
Done! Any request to your site will now redirect to Google for authentication.
|
41
|
+
A route that captures requests to `/google-callback` on your site is automatically created and handled for you.
|
44
42
|
|
45
|
-
If you log in with `your.email@example.com`, it'll let you in to the site and take you to the page you were initially trying to go to.
|
43
|
+
If you log in with `your.email@example.com`, or any address in the `example.net` domain, it'll let you in to the site and take you to the page you were initially trying to go to.
|
46
44
|
Otherwise it'll redirect to `/` (by default) with `params[:message]` set to the authentication error.
|
47
45
|
|
48
|
-
## Setting up
|
46
|
+
## Setting up a production environment
|
49
47
|
|
50
48
|
You might want to put a different configure block in your development.rb and production.rb, each specifying
|
51
|
-
a different redirect URI. Just pop them on the end of the file.
|
49
|
+
a different redirect URI. Just pop them on the end of the file. You can also have different client IDs and
|
50
|
+
secrets, or authentication criteria.
|
52
51
|
|
53
52
|
# development.rb
|
54
53
|
SimpleGoogleAuth.configure do |config|
|
@@ -62,23 +61,26 @@ a different redirect URI. Just pop them on the end of the file.
|
|
62
61
|
|
63
62
|
## How do I tell who is logged in?
|
64
63
|
|
65
|
-
Call `#google_auth_data` from your controller or view and you'll get the
|
64
|
+
Call `#google_auth_data` from your controller or view and you'll get the authentication data that Google sends back.
|
65
|
+
|
66
|
+
Welcome, <%= google_auth_data.email %>!
|
66
67
|
|
67
|
-
|
68
|
+
SimpleGoogleAuth exposes the following data via methods: access_token, expires_in, token_type, refresh_token, id_token, iss, at_hash, email_verified, sub, azp, email, aud, iat, exp, hd. You can also use `google_auth_data` as a hash and get any additional fields not listed here.
|
68
69
|
|
69
|
-
Take a look at https://developers.google.com/accounts/docs/OAuth2Login#obtainuserinfo
|
70
|
+
Take a look at [the Google OAuth documentation](https://developers.google.com/accounts/docs/OAuth2Login#obtainuserinfo)
|
71
|
+
to see more information about what these fields mean.
|
70
72
|
|
71
73
|
## Refreshing tokens and offline mode
|
72
74
|
|
73
|
-
By default
|
74
|
-
on the credentials after they've been loaded from
|
75
|
+
By default SimpleGoogleAuth doesn't check the expiry time
|
76
|
+
on the credentials after they've been loaded from Google the first time.
|
75
77
|
This is less hassle if all you want is simple authentication for your site,
|
76
78
|
but prevents you from using the credentials for other uses (eg. GCal integration)
|
77
|
-
because the oauth tokens will expire and
|
79
|
+
because the oauth tokens will expire and Google won't accept them anymore.
|
78
80
|
|
79
81
|
If you want the tokens to be refreshed when they expire then you need to
|
80
82
|
add an extra line to your config. Doing so will ensure that your
|
81
|
-
|
83
|
+
Google auth tokens never get stale and allow you to use offline mode.
|
82
84
|
|
83
85
|
SimpleGoogleAuth.configure do |config|
|
84
86
|
config.refresh_stale_tokens = true
|
@@ -89,17 +91,17 @@ be checked to make sure it's not stale. If it is stale the tokens will be
|
|
89
91
|
refreshed before being returned.
|
90
92
|
|
91
93
|
If your users have already allowed your site access to a certain set of scopes
|
92
|
-
|
93
|
-
extra param in the request_parameters configuration hash to force
|
94
|
+
Google won't re-issue you a refresh_token automatically. You'll need to set an
|
95
|
+
extra param in the request_parameters configuration hash to force Google to
|
94
96
|
send you the refresh token every time your users authenticate.
|
95
97
|
|
96
98
|
SimpleGoogleAuth.configure do |config|
|
97
99
|
config.refresh_stale_tokens = true
|
98
|
-
config.request_parameters.merge!(
|
100
|
+
config.request_parameters.merge!(approval_prompt: "force")
|
99
101
|
end
|
100
102
|
|
101
|
-
For more details on offline mode and approval_prompt refer to the
|
102
|
-
|
103
|
+
For more details on offline mode and approval_prompt refer to the
|
104
|
+
[Google OAuth documentation](https://developers.google.com/accounts/docs/OAuth2WebServer).
|
103
105
|
|
104
106
|
## Configuring
|
105
107
|
|
@@ -107,19 +109,25 @@ There are a few configuration options that can be set using `SimpleGoogleAuth.co
|
|
107
109
|
|
108
110
|
Option | Default | Description
|
109
111
|
--- | --- | ---
|
110
|
-
client_id | (required) | Client ID as provided by Google.
|
111
|
-
client_secret | (required) | Client secret as provided by Google.
|
112
|
+
client_id* | (required) | Client ID as provided by Google.
|
113
|
+
client_secret* | (required) | Client secret as provided by Google.
|
112
114
|
redirect_uri | (required) | Where Google should redirect to after authentication.
|
113
115
|
redirect_path | `nil` | A route is created at this path. If no path is specified, the path is taken from redirect_uri.
|
114
116
|
authenticate | (required) | A lambda that's run to determine whether the user should be accepted as valid or not. Takes one argument, a hash of identification data as provided by Google. Should return true on success, or false if the login should not proceed.
|
115
117
|
failed_login_path | `"/"` | Where to redirect to upon a failed login. `params[:message]` will be set with the error that occurred.
|
116
|
-
ca_path | `"/etc/ssl/certs"` | A path or file of SSL certificates, used to check that we're really talking to the Google servers.
|
117
118
|
google_auth_url | `"https://accounts.google.com/o/oauth2/auth"` | Google's authentication URL.
|
118
119
|
google_token_url | `"https://accounts.google.com/o/oauth2/token"` | Google's token URL.
|
119
120
|
state_session_key_name | `"simple-google-auth.state"` | The name of the session variable used to store a random string used to prevent CSRF attacks during authentication.
|
120
121
|
data_session_key_name | `"simple-google-auth.data"` | The name of the session variable used to store identification data from Google.
|
121
|
-
request_parameters | {scope: "openid email"} | Parameters to use when requesting a login from Google
|
122
|
+
request_parameters | `{scope: "openid email"}` | Parameters to use when requesting a login from Google
|
123
|
+
|
124
|
+
Items marked with * may be a lambda, which will be called when that config item is required.
|
122
125
|
|
123
126
|
## Licence
|
124
127
|
|
125
|
-
MIT.
|
128
|
+
MIT. Copyright 2014-2015 Roger Nesbitt, Powershop New Zealand Limited.
|
129
|
+
|
130
|
+
## Authors and contributors
|
131
|
+
|
132
|
+
- Roger Nesbitt
|
133
|
+
- Andy Newport
|
data/lib/simple_google_auth.rb
CHANGED
@@ -1,63 +1,37 @@
|
|
1
1
|
require 'net/https'
|
2
|
+
require 'simple_google_auth/config'
|
2
3
|
|
3
4
|
module SimpleGoogleAuth
|
4
|
-
Config = Struct.new(
|
5
|
-
:client_id,
|
6
|
-
:client_secret,
|
7
|
-
:redirect_uri,
|
8
|
-
:redirect_path,
|
9
|
-
:failed_login_path,
|
10
|
-
:authenticate,
|
11
|
-
:ca_path,
|
12
|
-
:google_auth_url,
|
13
|
-
:google_token_url,
|
14
|
-
:state_session_key_name,
|
15
|
-
:data_session_key_name,
|
16
|
-
:request_parameters,
|
17
|
-
:refresh_stale_tokens
|
18
|
-
) do
|
19
|
-
def get_or_call(attribute)
|
20
|
-
value = send(attribute)
|
21
|
-
value.respond_to?(:call) ? value.call : value
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
5
|
mattr_accessor :config
|
26
6
|
self.config = Config.new
|
27
7
|
|
8
|
+
Error = Class.new(StandardError)
|
9
|
+
ProviderError = Class.new(Error)
|
10
|
+
NonJsonResponseError = Class.new(ProviderError)
|
11
|
+
|
28
12
|
def self.configure
|
29
13
|
yield config
|
30
14
|
|
31
15
|
if config.refresh_stale_tokens
|
32
|
-
config.request_parameters.merge!(
|
16
|
+
config.request_parameters.merge!(access_type: "offline")
|
33
17
|
end
|
34
18
|
end
|
35
|
-
|
36
|
-
def self.uri(state)
|
37
|
-
query = config.request_parameters.merge(
|
38
|
-
response_type: "code",
|
39
|
-
client_id: config.get_or_call(:client_id),
|
40
|
-
redirect_uri: config.redirect_uri,
|
41
|
-
state: state
|
42
|
-
)
|
43
|
-
|
44
|
-
"#{config.google_auth_url}?" + query.map {|k, v| "#{k}=#{CGI.escape v}"}.join("&")
|
45
|
-
end
|
46
19
|
end
|
47
20
|
|
21
|
+
require 'simple_google_auth/http_client'
|
22
|
+
require 'simple_google_auth/auth_data_presenter'
|
23
|
+
require 'simple_google_auth/oauth'
|
24
|
+
require 'simple_google_auth/authorization_uri_builder'
|
25
|
+
require 'simple_google_auth/engine'
|
26
|
+
require 'simple_google_auth/controller'
|
27
|
+
require 'simple_google_auth/receiver'
|
28
|
+
|
48
29
|
SimpleGoogleAuth.configure do |config|
|
49
|
-
config.ca_path = %w(/etc/ssl/certs).detect {|dir| Dir.exists?(dir)}
|
50
30
|
config.google_auth_url = "https://accounts.google.com/o/oauth2/auth"
|
51
31
|
config.google_token_url = "https://accounts.google.com/o/oauth2/token"
|
52
32
|
config.state_session_key_name = "simple-google-auth.state"
|
53
33
|
config.data_session_key_name = "simple-google-auth.data"
|
54
34
|
config.failed_login_path = "/"
|
55
35
|
config.request_parameters = {scope: "openid email"}
|
56
|
-
config.authenticate = lambda { raise "You must define an authenticate lambda that
|
36
|
+
config.authenticate = lambda {|data| raise "You must define an authenticate lambda that determines whether a user should be allowed access or not"}
|
57
37
|
end
|
58
|
-
|
59
|
-
require 'simple_google_auth/http_client'
|
60
|
-
require 'simple_google_auth/oauth'
|
61
|
-
require 'simple_google_auth/engine'
|
62
|
-
require 'simple_google_auth/controller'
|
63
|
-
require 'simple_google_auth/receiver'
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module SimpleGoogleAuth
|
2
|
+
class AuthDataPresenter
|
3
|
+
InvalidAuthDataError = Class.new(Error)
|
4
|
+
|
5
|
+
FIELDS = %w(
|
6
|
+
access_token
|
7
|
+
expires_in
|
8
|
+
token_type
|
9
|
+
refresh_token
|
10
|
+
id_token
|
11
|
+
iss
|
12
|
+
at_hash
|
13
|
+
email_verified
|
14
|
+
sub
|
15
|
+
azp
|
16
|
+
email
|
17
|
+
aud
|
18
|
+
iat
|
19
|
+
exp
|
20
|
+
hd
|
21
|
+
expires_at
|
22
|
+
)
|
23
|
+
|
24
|
+
def initialize(auth_data)
|
25
|
+
raise InvalidAuthDataError if auth_data["id_token"].nil?
|
26
|
+
|
27
|
+
token_data = unpack_json_web_token(auth_data["id_token"])
|
28
|
+
@data = auth_data.merge(token_data)
|
29
|
+
end
|
30
|
+
|
31
|
+
def [](field)
|
32
|
+
@data[field.to_s]
|
33
|
+
end
|
34
|
+
|
35
|
+
FIELDS.each do |field|
|
36
|
+
define_method(field) { @data[field.to_s] }
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def unpack_json_web_token(id_token)
|
42
|
+
# We don't worry about validating the signature because we got this JWT directly
|
43
|
+
# from Google over HTTPS (see
|
44
|
+
# https://developers.google.com/identity/protocols/OpenIDConnect#obtainuserinfo)
|
45
|
+
signature, id_data_64 = id_token.split(".")
|
46
|
+
id_data_64 << "=" until id_data_64.length % 4 == 0
|
47
|
+
JSON.parse(Base64.decode64(id_data_64))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SimpleGoogleAuth
|
2
|
+
class AuthorizationUriBuilder
|
3
|
+
def initialize(state)
|
4
|
+
@state = state
|
5
|
+
end
|
6
|
+
|
7
|
+
def uri
|
8
|
+
params = config.request_parameters.merge(
|
9
|
+
response_type: "code",
|
10
|
+
client_id: config.client_id,
|
11
|
+
redirect_uri: config.redirect_uri,
|
12
|
+
state: @state
|
13
|
+
)
|
14
|
+
|
15
|
+
"#{config.google_auth_url}?#{params_to_query(params)}"
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def config
|
21
|
+
SimpleGoogleAuth.config
|
22
|
+
end
|
23
|
+
|
24
|
+
def params_to_query(params)
|
25
|
+
params.map {|k, v| "#{CGI.escape k.to_s}=#{CGI.escape v.to_s}"}.join("&")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module SimpleGoogleAuth
|
2
|
+
config_fields = [
|
3
|
+
:client_id,
|
4
|
+
:client_secret,
|
5
|
+
:redirect_uri,
|
6
|
+
:redirect_path,
|
7
|
+
:failed_login_path,
|
8
|
+
:authenticate,
|
9
|
+
:google_auth_url,
|
10
|
+
:google_token_url,
|
11
|
+
:state_session_key_name,
|
12
|
+
:data_session_key_name,
|
13
|
+
:request_parameters,
|
14
|
+
:refresh_stale_tokens
|
15
|
+
]
|
16
|
+
|
17
|
+
class Config < Struct.new(*config_fields)
|
18
|
+
def ca_path=(value)
|
19
|
+
Rails.logger.warn "ca_path is no longer used by SimpleGoogleAuth as OpenSSL is clever enough to find its ca_path now"
|
20
|
+
end
|
21
|
+
|
22
|
+
def client_id
|
23
|
+
get_or_call super
|
24
|
+
end
|
25
|
+
|
26
|
+
def client_secret
|
27
|
+
get_or_call super
|
28
|
+
end
|
29
|
+
|
30
|
+
def authenticate=(value)
|
31
|
+
if !value.respond_to?(:call)
|
32
|
+
raise Error, "Your SimpleGoogleAuth authenticator must be an object that responds to :call, normally a lambda. See documentation for configuration details."
|
33
|
+
end
|
34
|
+
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def get_or_call(value)
|
41
|
+
value.respond_to?(:call) ? value.call : value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -1,36 +1,44 @@
|
|
1
1
|
module SimpleGoogleAuth
|
2
2
|
module Controller
|
3
3
|
protected
|
4
|
+
|
4
5
|
def redirect_if_not_google_authenticated
|
5
6
|
redirect_to google_authentication_uri if google_auth_data.nil?
|
6
7
|
end
|
7
8
|
|
8
9
|
def google_authentication_uri
|
9
10
|
state = session[SimpleGoogleAuth.config.state_session_key_name] = SecureRandom.hex + request.path
|
10
|
-
SimpleGoogleAuth.
|
11
|
+
SimpleGoogleAuth::AuthorizationUriBuilder.new(state).uri
|
11
12
|
end
|
12
13
|
|
13
14
|
def google_auth_data
|
14
|
-
return unless
|
15
|
+
return unless cached_google_auth_data
|
15
16
|
|
16
17
|
if should_refresh_google_auth_data?
|
17
18
|
refresh_google_auth_data
|
18
19
|
end
|
19
|
-
|
20
|
+
cached_google_auth_data
|
20
21
|
end
|
21
22
|
|
22
23
|
private
|
23
24
|
|
24
25
|
def refresh_google_auth_data
|
25
26
|
api = SimpleGoogleAuth::OAuth.new(SimpleGoogleAuth.config)
|
26
|
-
|
27
|
-
auth_data = api.refresh_auth_token!(google_auth_data_from_session["refresh_token"])
|
27
|
+
auth_data = api.refresh_auth_token!(cached_google_auth_data["refresh_token"])
|
28
28
|
|
29
29
|
session[SimpleGoogleAuth.config.data_session_key_name] = auth_data
|
30
|
+
@_google_auth_data_presenter = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def cached_google_auth_data
|
34
|
+
@_google_auth_data_presenter ||= google_auth_data_from_session
|
30
35
|
end
|
31
36
|
|
32
37
|
def google_auth_data_from_session
|
33
|
-
session[SimpleGoogleAuth.config.data_session_key_name]
|
38
|
+
if auth_data = session[SimpleGoogleAuth.config.data_session_key_name]
|
39
|
+
AuthDataPresenter.new(auth_data)
|
40
|
+
end
|
41
|
+
rescue AuthDataPresenter::InvalidAuthDataError
|
34
42
|
end
|
35
43
|
|
36
44
|
def should_refresh_google_auth_data?
|
@@ -38,7 +46,7 @@ module SimpleGoogleAuth
|
|
38
46
|
end
|
39
47
|
|
40
48
|
def google_auth_data_stale?
|
41
|
-
expiry_time =
|
49
|
+
expiry_time = cached_google_auth_data["expires_at"]
|
42
50
|
|
43
51
|
expiry_time.nil? || Time.parse(expiry_time).past?
|
44
52
|
end
|
@@ -1,30 +1,35 @@
|
|
1
1
|
module SimpleGoogleAuth
|
2
2
|
class HttpClient
|
3
|
-
def initialize(url
|
3
|
+
def initialize(url)
|
4
4
|
@uri = URI(url)
|
5
5
|
@http = Net::HTTP.new(@uri.host, @uri.port)
|
6
|
-
|
6
|
+
|
7
|
+
if @uri.scheme == "https"
|
8
|
+
@http.use_ssl = true
|
9
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
10
|
+
end
|
7
11
|
end
|
8
12
|
|
9
13
|
def request(params)
|
10
14
|
request = Net::HTTP::Post.new(@uri.request_uri)
|
11
15
|
request.set_form_data(params)
|
12
16
|
response = @http.request(request)
|
13
|
-
response.body
|
14
|
-
end
|
15
17
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
25
|
-
Rails.logger.warn "SimpleGoogleAuth does not have a ca_path configured; SSL with Google is not protected"
|
26
|
-
end
|
18
|
+
if response.content_type != 'application/json'
|
19
|
+
raise NonJsonResponseError, "The server responded with non-JSON content"
|
20
|
+
end
|
21
|
+
|
22
|
+
data = begin
|
23
|
+
JSON.parse(response.body)
|
24
|
+
rescue JSON::ParserError
|
25
|
+
raise NonJsonResponseError, "The server responded with JSON content that was not parseable"
|
27
26
|
end
|
27
|
+
|
28
|
+
if response.code !~ /\A2\d\d\z/
|
29
|
+
raise ProviderError, "The server responded with error #{response.code}: #{data.inspect}"
|
30
|
+
end
|
31
|
+
|
32
|
+
data
|
28
33
|
end
|
29
34
|
end
|
30
35
|
end
|
@@ -2,7 +2,7 @@ module SimpleGoogleAuth
|
|
2
2
|
class OAuth
|
3
3
|
def initialize(config)
|
4
4
|
@config = config
|
5
|
-
@client = HttpClient.new(@config.google_token_url
|
5
|
+
@client = HttpClient.new(@config.google_token_url)
|
6
6
|
end
|
7
7
|
|
8
8
|
def exchange_code_for_auth_token!(code)
|
@@ -25,27 +25,34 @@ module SimpleGoogleAuth
|
|
25
25
|
client_secret: @config.client_secret,
|
26
26
|
grant_type: "refresh_token")
|
27
27
|
|
28
|
-
|
28
|
+
response["refresh_token"] ||= refresh_token
|
29
|
+
|
30
|
+
parse_auth_response(response)
|
29
31
|
end
|
30
32
|
|
31
33
|
private
|
32
|
-
def parse_auth_response(
|
33
|
-
auth_data
|
34
|
+
def parse_auth_response(auth_data)
|
35
|
+
validate_data_present!(auth_data)
|
34
36
|
|
35
37
|
auth_data["expires_at"] = calculate_expiry(auth_data).to_s
|
36
38
|
|
37
|
-
|
38
|
-
auth_data.merge!(id_data)
|
39
|
+
auth_data
|
39
40
|
end
|
40
41
|
|
41
|
-
def
|
42
|
-
|
42
|
+
def validate_data_present!(auth_data)
|
43
|
+
%w(id_token expires_in).each do |field|
|
44
|
+
if auth_data[field].blank?
|
45
|
+
raise Error, "Expecting field '#{field}' to be set but it is blank"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
if !auth_data['expires_in'].is_a?(Numeric) || auth_data['expires_in'] <= 0
|
50
|
+
raise Error, "Field 'expires_in' must be a number greater than 0"
|
51
|
+
end
|
43
52
|
end
|
44
53
|
|
45
|
-
def
|
46
|
-
|
47
|
-
id_data_64 << "=" until id_data_64.length % 4 == 0
|
48
|
-
JSON.parse(Base64.decode64(id_data_64))
|
54
|
+
def calculate_expiry(auth_data)
|
55
|
+
Time.now + auth_data["expires_in"] - 5.seconds
|
49
56
|
end
|
50
57
|
end
|
51
58
|
end
|
@@ -1,7 +1,5 @@
|
|
1
1
|
module SimpleGoogleAuth
|
2
2
|
class Receiver
|
3
|
-
Error = Class.new(StandardError)
|
4
|
-
|
5
3
|
def call(env)
|
6
4
|
request = Rack::Request.new(env)
|
7
5
|
config = SimpleGoogleAuth.config
|
@@ -10,7 +8,8 @@ module SimpleGoogleAuth
|
|
10
8
|
api = SimpleGoogleAuth::OAuth.new(config)
|
11
9
|
auth_data = api.exchange_code_for_auth_token!(request.params["code"])
|
12
10
|
|
13
|
-
|
11
|
+
data = AuthDataPresenter.new(auth_data)
|
12
|
+
raise Error, "Authentication failed" unless config.authenticate.call(data)
|
14
13
|
|
15
14
|
request.session[config.data_session_key_name] = auth_data
|
16
15
|
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleGoogleAuth::AuthDataPresenter do
|
4
|
+
let(:id_data) do
|
5
|
+
{
|
6
|
+
"iss" => "accounts.google.com",
|
7
|
+
"sub" => "10769150350006150715113082367",
|
8
|
+
"email" => "test@test.example",
|
9
|
+
"aud" => "1234987819200.apps.googleusercontent.com",
|
10
|
+
"iat" => 1353601026,
|
11
|
+
"exp" => 1353604926
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
let(:id_token) { "12345." + Base64.encode64(id_data.to_json).gsub('=', '') }
|
16
|
+
let(:auth_data) do
|
17
|
+
{
|
18
|
+
"id_token" => id_token,
|
19
|
+
"expires_in" => 1200,
|
20
|
+
"access_token" => "abcdef",
|
21
|
+
"token_type" => "Bearer"
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
subject { SimpleGoogleAuth::AuthDataPresenter.new(auth_data) }
|
26
|
+
|
27
|
+
it "provides indifferent hash access to data in the JWT" do
|
28
|
+
expect(subject['email']).to eq 'test@test.example'
|
29
|
+
expect(subject[:email]).to eq 'test@test.example'
|
30
|
+
end
|
31
|
+
|
32
|
+
it "provides method access to data in the JWT" do
|
33
|
+
expect(subject.email).to eq 'test@test.example'
|
34
|
+
end
|
35
|
+
|
36
|
+
it "raises if id_token not provided" do
|
37
|
+
expect {
|
38
|
+
SimpleGoogleAuth::AuthDataPresenter.new({})
|
39
|
+
}.to raise_error(SimpleGoogleAuth::AuthDataPresenter::InvalidAuthDataError)
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleGoogleAuth::AuthorizationUriBuilder do
|
4
|
+
subject do
|
5
|
+
SimpleGoogleAuth::AuthorizationUriBuilder.new("somestate")
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#uri" do
|
9
|
+
it "constructs an authorization URI" do
|
10
|
+
expect(subject.uri).to eq 'https://accounts.google.com/o/oauth2/auth?scope=openid+email&response_type=code&client_id=123&redirect_uri=%2Fabc&state=somestate'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleGoogleAuth::Config do
|
4
|
+
subject { SimpleGoogleAuth::Config.new }
|
5
|
+
|
6
|
+
describe "#client_id" do
|
7
|
+
it "gets the value if it doesn't respond to call" do
|
8
|
+
subject.client_id = '12345'
|
9
|
+
expect(subject.client_id).to eq '12345'
|
10
|
+
end
|
11
|
+
|
12
|
+
it "calls to get the value if it responds to call" do
|
13
|
+
subject.client_id = lambda { '12345' }
|
14
|
+
expect(subject.client_id).to eq '12345'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#authenticate=" do
|
19
|
+
it "saves the value if it is callable" do
|
20
|
+
fn = lambda {|data| true}
|
21
|
+
subject.authenticate = fn
|
22
|
+
expect(subject.authenticate).to eql fn
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises if the value isn't callable" do
|
26
|
+
expect {
|
27
|
+
subject.authenticate = "not a lambda"
|
28
|
+
}.to raise_error(SimpleGoogleAuth::Error, /responds to :call/)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#ca_path=" do
|
33
|
+
it "logs a warning" do
|
34
|
+
Rails.logger ||= double
|
35
|
+
expect(Rails.logger).to receive(:warn)
|
36
|
+
subject.ca_path = "/etc/certs"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleGoogleAuth::Controller do
|
4
|
+
class TestController
|
5
|
+
include SimpleGoogleAuth::Controller
|
6
|
+
|
7
|
+
attr_reader :request, :session
|
8
|
+
|
9
|
+
def redirect_to(x)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
subject { TestController.new }
|
14
|
+
|
15
|
+
let(:id_data) { Base64.encode64({email:"hi@hi"}.to_json).gsub('=', '') }
|
16
|
+
let(:auth_data) { {"id_token" => "123." + id_data} }
|
17
|
+
let(:request) { double(path: "/somepath") }
|
18
|
+
let(:session) { {} }
|
19
|
+
|
20
|
+
before do
|
21
|
+
allow(subject).to receive(:request).and_return(request)
|
22
|
+
allow(subject).to receive(:session).and_return(session)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "#redirect_if_not_google_authenticated" do
|
26
|
+
it "redirects if not authenticated" do
|
27
|
+
expect(SecureRandom).to receive(:hex).and_return("abcd")
|
28
|
+
expect(subject).to receive(:redirect_to).with("https://accounts.google.com/o/oauth2/auth?scope=openid+email&response_type=code&client_id=123&redirect_uri=%2Fabc&state=abcd%2Fsomepath")
|
29
|
+
subject.send(:redirect_if_not_google_authenticated)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "does nothing if authenticated" do
|
33
|
+
session[SimpleGoogleAuth.config.data_session_key_name] = auth_data
|
34
|
+
expect(subject).to_not receive(:redirect_to)
|
35
|
+
subject.send(:redirect_if_not_google_authenticated)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#google_auth_data" do
|
40
|
+
it "returns data from the session" do
|
41
|
+
session[SimpleGoogleAuth.config.data_session_key_name] = auth_data
|
42
|
+
data = subject.send(:google_auth_data)
|
43
|
+
expect(data.email).to eq 'hi@hi'
|
44
|
+
end
|
45
|
+
|
46
|
+
it "refreshes the token data if it's expired and refresh_stale_tokens is true"
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleGoogleAuth::HttpClient do
|
4
|
+
describe "#request" do
|
5
|
+
let(:http) { instance_double(Net::HTTP) }
|
6
|
+
let(:request) { instance_double(Net::HTTP::Post) }
|
7
|
+
|
8
|
+
before do
|
9
|
+
expect(Net::HTTP).to receive(:new).with("some.host", 443).and_return(http)
|
10
|
+
expect(http).to receive(:use_ssl=).with(true)
|
11
|
+
expect(http).to receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER)
|
12
|
+
expect(http).to receive(:request).with(request).and_return(response)
|
13
|
+
|
14
|
+
expect(Net::HTTP::Post).to receive(:new).with("/somepath").and_return(request)
|
15
|
+
expect(request).to receive(:set_form_data).with('some' => 'data')
|
16
|
+
end
|
17
|
+
|
18
|
+
subject { SimpleGoogleAuth::HttpClient.new("https://some.host/somepath") }
|
19
|
+
|
20
|
+
context "when the call is successful" do
|
21
|
+
let(:response) do
|
22
|
+
instance_double(
|
23
|
+
Net::HTTPSuccess,
|
24
|
+
code: '200',
|
25
|
+
body: {"data" => "very"}.to_json,
|
26
|
+
content_type: 'application/json'
|
27
|
+
)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "returns the server's response" do
|
31
|
+
expect(subject.request('some' => 'data')).to eq("data" => "very")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context "when non-json data is returned" do
|
36
|
+
let(:response) do
|
37
|
+
instance_double(
|
38
|
+
Net::HTTPSuccess,
|
39
|
+
code: '200',
|
40
|
+
body: "some html",
|
41
|
+
content_type: 'text/html'
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
it "raises an error" do
|
46
|
+
expect { subject.request('some' => 'data') }.to raise_error(SimpleGoogleAuth::NonJsonResponseError, /non-JSON/)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "when non-json-parseable data is returned" do
|
51
|
+
let(:response) do
|
52
|
+
instance_double(
|
53
|
+
Net::HTTPSuccess,
|
54
|
+
code: '200',
|
55
|
+
body: "some html",
|
56
|
+
content_type: 'application/json'
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "raises an error" do
|
61
|
+
expect { subject.request('some' => 'data') }.to raise_error(SimpleGoogleAuth::NonJsonResponseError, /parseable/)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when non-successful json data is returned" do
|
66
|
+
let(:response) do
|
67
|
+
instance_double(
|
68
|
+
Net::HTTPSuccess,
|
69
|
+
code: '400',
|
70
|
+
body: {"data" => "very"}.to_json,
|
71
|
+
content_type: 'application/json'
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "raises an error" do
|
76
|
+
expect { subject.request('some' => 'data') }.to raise_error(SimpleGoogleAuth::ProviderError, /400.+very/)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleGoogleAuth::OAuth do
|
4
|
+
let(:config) do
|
5
|
+
instance_double(
|
6
|
+
SimpleGoogleAuth::Config,
|
7
|
+
google_token_url: "/token/url",
|
8
|
+
client_id: '12345',
|
9
|
+
client_secret: 'abcde',
|
10
|
+
redirect_uri: '/ok'
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:client) { instance_double(SimpleGoogleAuth::HttpClient) }
|
15
|
+
let(:response) { {"id_token" => "sometoken", "expires_in" => 1200, "other" => "data"} }
|
16
|
+
let(:expires_at) { Time.now + 1200 - 5 }
|
17
|
+
|
18
|
+
before do
|
19
|
+
now = Time.now
|
20
|
+
allow(Time).to receive(:now).and_return(now)
|
21
|
+
|
22
|
+
expect(SimpleGoogleAuth::HttpClient).to receive(:new).with(config.google_token_url).and_return(client)
|
23
|
+
end
|
24
|
+
|
25
|
+
subject { SimpleGoogleAuth::OAuth.new(config) }
|
26
|
+
|
27
|
+
describe "#exchange_code_for_auth_token!" do
|
28
|
+
before do
|
29
|
+
expect(client).to receive(:request).with(
|
30
|
+
code: "magic",
|
31
|
+
grant_type: "authorization_code",
|
32
|
+
client_id: "12345",
|
33
|
+
client_secret: "abcde",
|
34
|
+
redirect_uri: "/ok"
|
35
|
+
).and_return(response)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "returns a hash of auth token data" do
|
39
|
+
expect(subject.exchange_code_for_auth_token!('magic')).to eq('expires_in' => 1200, 'other' => 'data', 'id_token' => 'sometoken', 'expires_at' => expires_at.to_s)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe "#refresh_auth_token!" do
|
44
|
+
context "when a refresh token is provided" do
|
45
|
+
before do
|
46
|
+
expect(client).to receive(:request).with(
|
47
|
+
refresh_token: "magic",
|
48
|
+
grant_type: "refresh_token",
|
49
|
+
client_id: "12345",
|
50
|
+
client_secret: "abcde",
|
51
|
+
).and_return(response)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "returns a hash of auth token data" do
|
55
|
+
expect(subject.refresh_auth_token!('magic')).to eq('expires_in' => 1200, 'other' => 'data', 'id_token' => 'sometoken', 'expires_at' => expires_at.to_s, 'refresh_token' => 'magic')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context "when no refresh token is provided" do
|
60
|
+
it "does nothing and returns nil" do
|
61
|
+
expect(subject.refresh_auth_token!(nil)).to be nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleGoogleAuth::Receiver do
|
4
|
+
let(:authenticator) { double(call: true) }
|
5
|
+
let(:authentication_result) { true }
|
6
|
+
let(:session) { double }
|
7
|
+
let(:state) { "abcd" * 8 + "/place" }
|
8
|
+
let(:code) { "sekrit" }
|
9
|
+
let(:params) { {"state" => state, "code" => code} }
|
10
|
+
let(:request) { instance_double(Rack::Request, session: session, params: params) }
|
11
|
+
let(:api) { instance_double(SimpleGoogleAuth::OAuth) }
|
12
|
+
let(:auth_data) { double }
|
13
|
+
let(:env) { double }
|
14
|
+
let(:auth_data_presenter) { instance_double(SimpleGoogleAuth::AuthDataPresenter) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
expect(Rack::Request).to receive(:new).with(env).and_return(request)
|
18
|
+
expect(session).to receive(:[]).at_least(:once).with('simple-google-auth.state').and_return(state)
|
19
|
+
|
20
|
+
SimpleGoogleAuth.config.authenticate = authenticator
|
21
|
+
SimpleGoogleAuth.config.failed_login_path = '/error'
|
22
|
+
end
|
23
|
+
|
24
|
+
subject { SimpleGoogleAuth::Receiver.new.call(env) }
|
25
|
+
|
26
|
+
context "when a valid code is provided to the receiver" do
|
27
|
+
before do
|
28
|
+
expect(SimpleGoogleAuth::OAuth).to receive(:new).with(SimpleGoogleAuth.config).and_return(api)
|
29
|
+
expect(api).to receive(:exchange_code_for_auth_token!).with(code).and_return(auth_data)
|
30
|
+
|
31
|
+
expect(SimpleGoogleAuth::AuthDataPresenter).to receive(:new).with(auth_data).and_return(auth_data_presenter)
|
32
|
+
expect(authenticator).to receive(:call).with(auth_data_presenter).and_return(authentication_result)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "and the authenticator accepts the login" do
|
36
|
+
before do
|
37
|
+
expect(session).to receive(:[]=).with('simple-google-auth.data', auth_data)
|
38
|
+
end
|
39
|
+
|
40
|
+
it "redirects to the URL specified in the session" do
|
41
|
+
expect(subject).to eq [302, {"Location" => "/place"}, [" "]]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "and the authenticator rejects the login" do
|
46
|
+
let(:authentication_result) { false }
|
47
|
+
|
48
|
+
it "redirects to the failed login path with a message" do
|
49
|
+
expect(subject).to eq [302, {"Location" => "/error?message=Authentication+failed"}, [" "]]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "when the state doesn't match" do
|
55
|
+
let(:params) { {"state" => "doesnotmatch", "code" => code} }
|
56
|
+
|
57
|
+
it "redirects to the failed login path with a message" do
|
58
|
+
expect(subject).to eq [302, {"Location" => "/error?message=Invalid+state+returned+from+Google"}, [" "]]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "when the google authentication fails" do
|
63
|
+
let(:params) { {"state" => state, "error" => "bad stuff"} }
|
64
|
+
|
65
|
+
it "redirects to the failed login path with a message" do
|
66
|
+
expect(subject).to eq [302, {"Location" => "/error?message=Authentication+failed%3A+bad+stuff"}, [" "]]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "when no code is returned (unexpected)" do
|
71
|
+
let(:params) { {"state" => state} }
|
72
|
+
|
73
|
+
it "redirects to the failed login path with a message" do
|
74
|
+
expect(subject).to eq [302, {"Location" => "/error?message=No+authentication+code+returned"}, [" "]]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SimpleGoogleAuth do
|
4
|
+
describe "::configure" do
|
5
|
+
it "yields the config object" do
|
6
|
+
SimpleGoogleAuth.configure do |config|
|
7
|
+
expect(config).to be_a(SimpleGoogleAuth::Config)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
it "sets access_type to offline if refresh_stale_tokens set"
|
12
|
+
end
|
13
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
ENV['RAILS_ENV'] ||= 'test'
|
2
|
+
|
3
|
+
require 'rails/all'
|
4
|
+
#require 'rspec/rails'
|
5
|
+
require 'simple_google_auth'
|
6
|
+
|
7
|
+
SimpleGoogleAuth.configure do |c|
|
8
|
+
c.client_id = '123'
|
9
|
+
c.redirect_uri = '/abc'
|
10
|
+
end
|
11
|
+
|
12
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
13
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
14
|
+
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
15
|
+
# this file to always be loaded, without a need to explicitly require it in any
|
16
|
+
# files.
|
17
|
+
#
|
18
|
+
# Given that it is always loaded, you are encouraged to keep this file as
|
19
|
+
# light-weight as possible. Requiring heavyweight dependencies from this file
|
20
|
+
# will add to the boot time of your test suite on EVERY test run, even for an
|
21
|
+
# individual file that may not need all of that loaded. Instead, consider making
|
22
|
+
# a separate helper file that requires the additional dependencies and performs
|
23
|
+
# the additional setup, and require it from the spec files that actually need
|
24
|
+
# it.
|
25
|
+
#
|
26
|
+
# The `.rspec` file also contains a few flags that are not defaults but that
|
27
|
+
# users commonly want.
|
28
|
+
#
|
29
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
30
|
+
RSpec.configure do |config|
|
31
|
+
# rspec-expectations config goes here. You can use an alternate
|
32
|
+
# assertion/expectation library such as wrong or the stdlib/minitest
|
33
|
+
# assertions if you prefer.
|
34
|
+
config.expect_with :rspec do |expectations|
|
35
|
+
# This option will default to `true` in RSpec 4. It makes the `description`
|
36
|
+
# and `failure_message` of custom matchers include text for helper methods
|
37
|
+
# defined using `chain`, e.g.:
|
38
|
+
# be_bigger_than(2).and_smaller_than(4).description
|
39
|
+
# # => "be bigger than 2 and smaller than 4"
|
40
|
+
# ...rather than:
|
41
|
+
# # => "be bigger than 2"
|
42
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
43
|
+
end
|
44
|
+
|
45
|
+
# rspec-mocks config goes here. You can use an alternate test double
|
46
|
+
# library (such as bogus or mocha) by changing the `mock_with` option here.
|
47
|
+
config.mock_with :rspec do |mocks|
|
48
|
+
# Prevents you from mocking or stubbing a method that does not exist on
|
49
|
+
# a real object. This is generally recommended, and will default to
|
50
|
+
# `true` in RSpec 4.
|
51
|
+
mocks.verify_partial_doubles = true
|
52
|
+
end
|
53
|
+
|
54
|
+
# The settings below are suggested to provide a good initial experience
|
55
|
+
# with RSpec, but feel free to customize to your heart's content.
|
56
|
+
=begin
|
57
|
+
# These two settings work together to allow you to limit a spec run
|
58
|
+
# to individual examples or groups you care about by tagging them with
|
59
|
+
# `:focus` metadata. When nothing is tagged with `:focus`, all examples
|
60
|
+
# get run.
|
61
|
+
config.filter_run :focus
|
62
|
+
config.run_all_when_everything_filtered = true
|
63
|
+
|
64
|
+
# Limits the available syntax to the non-monkey patched syntax that is
|
65
|
+
# recommended. For more details, see:
|
66
|
+
# - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
|
67
|
+
# - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
68
|
+
# - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
|
69
|
+
config.disable_monkey_patching!
|
70
|
+
|
71
|
+
# This setting enables warnings. It's recommended, but in some cases may
|
72
|
+
# be too noisy due to issues in dependencies.
|
73
|
+
config.warnings = true
|
74
|
+
|
75
|
+
# Many RSpec users commonly either run the entire suite or an individual
|
76
|
+
# file, and it's useful to allow more verbose output when running an
|
77
|
+
# individual spec file.
|
78
|
+
if config.files_to_run.one?
|
79
|
+
# Use the documentation formatter for detailed output,
|
80
|
+
# unless a formatter has already been configured
|
81
|
+
# (e.g. via a command-line flag).
|
82
|
+
config.default_formatter = 'doc'
|
83
|
+
end
|
84
|
+
|
85
|
+
# Print the 10 slowest examples and example groups at the
|
86
|
+
# end of the spec run, to help surface which specs are running
|
87
|
+
# particularly slow.
|
88
|
+
config.profile_examples = 10
|
89
|
+
|
90
|
+
# Run specs in random order to surface order dependencies. If you find an
|
91
|
+
# order dependency and want to debug it, you can fix the order by providing
|
92
|
+
# the seed, which is printed after each run.
|
93
|
+
# --seed 1234
|
94
|
+
config.order = :random
|
95
|
+
|
96
|
+
# Seed global randomization in this process using the `--seed` CLI option.
|
97
|
+
# Setting this allows you to use `--seed` to deterministically reproduce
|
98
|
+
# test failures related to randomization by passing the same `--seed` value
|
99
|
+
# as the one that triggered the failure.
|
100
|
+
Kernel.srand config.seed
|
101
|
+
=end
|
102
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simple_google_auth
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Roger Nesbitt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-05-31 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -24,6 +24,20 @@ dependencies:
|
|
24
24
|
- - ">="
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: 3.2.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec-rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '3.2'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '3.2'
|
27
41
|
description: An extremely easy way to protect your site by requiring Google logins
|
28
42
|
without having to set up a traditional authentication system
|
29
43
|
email:
|
@@ -37,12 +51,24 @@ files:
|
|
37
51
|
- Rakefile
|
38
52
|
- config/routes.rb
|
39
53
|
- lib/simple_google_auth.rb
|
54
|
+
- lib/simple_google_auth/auth_data_presenter.rb
|
55
|
+
- lib/simple_google_auth/authorization_uri_builder.rb
|
56
|
+
- lib/simple_google_auth/config.rb
|
40
57
|
- lib/simple_google_auth/controller.rb
|
41
58
|
- lib/simple_google_auth/engine.rb
|
42
59
|
- lib/simple_google_auth/http_client.rb
|
43
60
|
- lib/simple_google_auth/oauth.rb
|
44
61
|
- lib/simple_google_auth/receiver.rb
|
45
62
|
- lib/simple_google_auth/version.rb
|
63
|
+
- spec/simple_google_auth/auth_data_presenter_spec.rb
|
64
|
+
- spec/simple_google_auth/authorization_uri_builder_spec.rb
|
65
|
+
- spec/simple_google_auth/config_spec.rb
|
66
|
+
- spec/simple_google_auth/controller_spec.rb
|
67
|
+
- spec/simple_google_auth/http_client_spec.rb
|
68
|
+
- spec/simple_google_auth/oauth_spec.rb
|
69
|
+
- spec/simple_google_auth/receiver_spec.rb
|
70
|
+
- spec/simple_google_auth_spec.rb
|
71
|
+
- spec/spec_helper.rb
|
46
72
|
homepage: https://github.com/mogest/simple_google_auth
|
47
73
|
licenses:
|
48
74
|
- MIT
|
@@ -67,4 +93,13 @@ rubygems_version: 2.2.2
|
|
67
93
|
signing_key:
|
68
94
|
specification_version: 4
|
69
95
|
summary: Super simple Google authentication for your Rails site
|
70
|
-
test_files:
|
96
|
+
test_files:
|
97
|
+
- spec/simple_google_auth/auth_data_presenter_spec.rb
|
98
|
+
- spec/simple_google_auth/authorization_uri_builder_spec.rb
|
99
|
+
- spec/simple_google_auth/config_spec.rb
|
100
|
+
- spec/simple_google_auth/controller_spec.rb
|
101
|
+
- spec/simple_google_auth/http_client_spec.rb
|
102
|
+
- spec/simple_google_auth/oauth_spec.rb
|
103
|
+
- spec/simple_google_auth/receiver_spec.rb
|
104
|
+
- spec/simple_google_auth_spec.rb
|
105
|
+
- spec/spec_helper.rb
|