simple_google_auth 0.0.6 → 0.2.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 +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
|