g5_authentication_client 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.
@@ -0,0 +1,138 @@
1
+ require 'configlet'
2
+ require 'logger'
3
+
4
+ module G5AuthenticationClient
5
+ # All valid configurations options
6
+ VALID_CONFIG_OPTIONS = [:debug, :logger, :username, :password, :client_id,
7
+ :client_secret, :redirect_uri, :endpoint,
8
+ :authorization_code, :access_token,
9
+ :allow_password_credentials]
10
+
11
+ DEFAULT_ENDPOINT = "http://auth.g5search.com"
12
+ DEFAULT_CLIENT_ID = "theid"
13
+ DEFAULT_CLIENT_SECRET = "thesecret"
14
+ DEFAULT_REDIRECT_URI = "theurl"
15
+ DEFAULT_ALLOW_PASSWORD_CREDENTIALS = 'true'
16
+
17
+ module Configuration
18
+ include Configlet
19
+
20
+ def self.extended(base)
21
+ # Default configuration - happens whether or not .configure is called
22
+ base.config :g5_auth do
23
+ default debug: 'false'
24
+ default username: nil
25
+ default password: nil
26
+ default endpoint: DEFAULT_ENDPOINT
27
+ default client_id: DEFAULT_CLIENT_ID
28
+ default client_secret: DEFAULT_CLIENT_SECRET
29
+ default redirect_uri: DEFAULT_REDIRECT_URI
30
+ default authorization_code: nil
31
+ default access_token: nil
32
+ default allow_password_credentials: DEFAULT_ALLOW_PASSWORD_CREDENTIALS
33
+ end
34
+ end
35
+
36
+ # Mutators and accessors for configuration options
37
+ VALID_CONFIG_OPTIONS.each do |config_opt|
38
+ define_method(config_opt) do
39
+ self[config_opt]
40
+ end
41
+
42
+ define_method("#{config_opt}=".to_sym) do |val|
43
+ self[config_opt] = val.nil? ? nil : val.to_s
44
+ end
45
+ end
46
+
47
+ # !@attribute [rw] allow_password_credentials
48
+ # @return [String] set to true or 'true' to enable use of the username and password
49
+
50
+ # !@attribute [rw] debug
51
+ # @return [String] set to true or 'true' to enable debug logging (defaults to false)
52
+
53
+ # !@attribute [rw] endpoint
54
+ # @return [String] the service endpoint URL (Defaults to G5AuthenticationClient::DEFAULT_ENDPOINT)
55
+
56
+ # !@attribute [rw] username
57
+ # @return [String] the username for password credentials flow
58
+
59
+ # !@attribute [rw] password
60
+ # @return [String] the password for password credentials flow
61
+
62
+ # !@attribute [rw] client_id
63
+ # @return [String] the client id for the client application (Defaults to G5Authentication::DEFAULT_CLIENT_ID)
64
+
65
+ # !@attribute [rw] client_secret
66
+ # @return [String] the client secret for the client application (Defaults to G5Authentication::DEFAULT_CLIENT_SECRET)
67
+
68
+ # !@attribute [rw] redirect_uri
69
+ # @return [String] the redirect URI for the client application (Defaults to G5Authentication::DEFAULT_REDIRECT_URI)
70
+
71
+ # !@attribute [rw] authorization_code
72
+ # @return [String] the authorization code provided by the authorization server for authentication
73
+
74
+ # !@attribute [rw] access_token
75
+ # @return [String] the OAuth access token provided by the authorization server after authentication
76
+
77
+ # @return [true,false] true if debug logging is enabled; false otherwie.
78
+ def debug?
79
+ self[:debug] == 'true'
80
+ end
81
+
82
+ # Sets the logger to use for debug messages
83
+ attr_writer :logger
84
+
85
+ # @return [Logger] the logger to use for debug messages (defaults to STDOUT)
86
+ def logger
87
+ @logger ||= Logger.new(STDOUT)
88
+ end
89
+
90
+ # Configures this module through the given +block+.
91
+ # Default configuration options will be applied unless
92
+ # they are explicitly overridden in the +block+.
93
+ #
94
+ # @yield [_self] configures service connection options
95
+ # @yieldparam [G5AuthenticationClient] _self the object on which the configure method was called
96
+ # @example Typical case utilizing defaults
97
+ # G5AuthenticationClient.configure do |config|
98
+ # config.username = 'my_user'
99
+ # config.password = 'my_pass'
100
+ # end
101
+ # @example Overriding defaults
102
+ # G5AuthenticationClient.configure do |config|
103
+ # config.username = 'my_user'
104
+ # config.password = 'my_pass'
105
+ # config.endpoint = 'http://my.endpoint.com'
106
+ # end
107
+ # @return [G5AuthenticationClient] _self
108
+ # @see VALID_CONFIG_OPTIONS
109
+ def configure
110
+ config :g5_auth do
111
+ yield self
112
+ end
113
+
114
+ self
115
+ end
116
+
117
+ # Create a hash of configuration options and their
118
+ # values.
119
+ #
120
+ # @return [Hash<Symbol,Object>] the options hash
121
+ def options
122
+ VALID_CONFIG_OPTIONS.inject({}) do |option, key|
123
+ option.merge!(key => send(key))
124
+ end
125
+ end
126
+
127
+ # Resets this module's configuration.
128
+ # Configuration options will be set to default values
129
+ # if they exist; otherwise, they will be set to nil.
130
+ #
131
+ # @see VALID_CONFIG_OPTIONS
132
+
133
+ def reset
134
+ VALID_CONFIG_OPTIONS.each { |opt| self.send("#{opt}=", nil) }
135
+ self
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,27 @@
1
+ require 'modelish'
2
+
3
+ module G5AuthenticationClient
4
+ # G5 Authentication access token info
5
+ class TokenInfo < Modelish::Base
6
+ # @!attribute [rw] resource_owner_id
7
+ # @return [String]
8
+ # The ID of the user that owns the resource
9
+ property :resource_owner_id, type: String
10
+
11
+ # @!attribute [rw] scopes
12
+ # @return [Array]
13
+ # The OAuth scopes associated with this token
14
+ property :scopes, type: Array, default: []
15
+
16
+ # @!attribute [rw] expires_in_seconds
17
+ # @return [Integer]
18
+ # The amount of time until the token expires
19
+ property :expires_in_seconds, type: Integer
20
+
21
+ # @!attribute [rw] application_uid
22
+ # @return [String]
23
+ # The UID of the OAuth application that requested this token
24
+ property :application_uid, from: :application,
25
+ type: lambda { |val| (val[:uid] || val['uid']).to_s }
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ require 'modelish'
2
+
3
+ module G5AuthenticationClient
4
+
5
+ #A G5 Authentication User
6
+ class User < Modelish::Base
7
+ # @!attribute [rw] email
8
+ # @return [String]
9
+ # The user's email address.
10
+ property :email, type: String, required: true
11
+
12
+ # @!attribute [rw] password
13
+ # @return [String]
14
+ # The user's password. Required to create a user.
15
+ property :password, type: String
16
+
17
+ # @!attribute [rw] password_confirmation
18
+ # @return [String]
19
+ # The user's password_confirmation.
20
+ property :password_confirmation, type: String
21
+
22
+ # @!attribute [rw] id
23
+ # @return [Integer]
24
+ # The user's id. Not required to create a user.
25
+ property :id, type: Integer
26
+
27
+ def validate_for_create!
28
+ validate!
29
+ raise ArgumentError.new("Password required for new user.") unless !password.nil?
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,3 @@
1
+ module G5AuthenticationClient
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,11 @@
1
+ require 'oauth2'
2
+ require 'g5_authentication_client/version'
3
+ require 'g5_authentication_client/configuration'
4
+ require 'g5_authentication_client/user'
5
+ require 'g5_authentication_client/token_info'
6
+
7
+ module G5AuthenticationClient
8
+ extend Configuration
9
+
10
+ require 'g5_authentication_client/client'
11
+ end
@@ -0,0 +1,335 @@
1
+ require 'spec_helper'
2
+ require 'json'
3
+
4
+ describe G5AuthenticationClient::Client do
5
+ subject { client }
6
+
7
+ after { G5AuthenticationClient.reset }
8
+
9
+ let(:client) { G5AuthenticationClient::Client.new(options) }
10
+
11
+ let(:debug) { true }
12
+ let(:logger) { double() }
13
+ let(:username) {'username'}
14
+ let(:password) {'password'}
15
+ let(:client_id) {'client id'}
16
+ let(:client_secret) {'client secret'}
17
+ let(:redirect_uri) {'/stuff'}
18
+ let(:endpoint){ 'http://endpoint.com' }
19
+ let(:authorization_code){ 'code' }
20
+ let(:allow_password_credentials){ 'false' }
21
+
22
+ let(:options) do
23
+ {
24
+ debug: debug,
25
+ logger: logger,
26
+ endpoint: endpoint,
27
+ username: username,
28
+ password: password,
29
+ client_id: client_id,
30
+ client_secret: client_secret,
31
+ redirect_uri: redirect_uri,
32
+ authorization_code: authorization_code,
33
+ access_token: access_token,
34
+ allow_password_credentials: allow_password_credentials
35
+ }
36
+ end
37
+
38
+ let(:access_token) { access_token_value }
39
+ let(:access_token_value) { 'test_token' }
40
+ let(:token_type) { 'Bearer' }
41
+
42
+ let(:auth_header_value) { "#{token_type} #{access_token_value}" }
43
+
44
+ let(:new_user_options) do
45
+ {email: email,
46
+ password: "#{password}x",
47
+ id: user_id}
48
+ end
49
+
50
+ let(:email){'foo@blah.com'}
51
+ let(:password){'mybigtestpasswored'}
52
+ let(:user_id){1}
53
+ let(:returned_user){{id: user_id,email: email}}
54
+
55
+ context 'with default configuration' do
56
+ let(:client) { G5AuthenticationClient::Client.new }
57
+
58
+ it { should_not be_debug }
59
+ its(:logger) { should be_an_instance_of(Logger) }
60
+ its(:username) { should be_nil }
61
+ its(:password) { should be_nil }
62
+ its(:client_id) { should == G5AuthenticationClient::DEFAULT_CLIENT_ID }
63
+ its(:client_secret) { should == G5AuthenticationClient::DEFAULT_CLIENT_SECRET }
64
+ its(:redirect_uri) { should == G5AuthenticationClient::DEFAULT_REDIRECT_URI }
65
+ its(:endpoint){ should == G5AuthenticationClient::DEFAULT_ENDPOINT}
66
+ its(:authorization_code){ should be_nil}
67
+ its(:access_token) { should be_nil }
68
+ its(:allow_password_credentials) { should == 'true'}
69
+ end
70
+
71
+ context 'with non-default configuration' do
72
+ it { should be_debug }
73
+ its(:logger) { should == logger }
74
+
75
+ describe '#debug=' do
76
+ subject { client.debug = new_debug }
77
+
78
+ context 'with nil debug' do
79
+ let(:new_debug) { nil }
80
+
81
+ context 'when there is a debug flag configured at the top-level module' do
82
+ let(:configured_debug) { 'true' }
83
+ before { G5AuthenticationClient.configure { |config| config.debug = configured_debug } }
84
+
85
+ it 'should set the debug flag according to the configuration' do
86
+ expect { subject }.to_not change { client.debug? }
87
+ end
88
+ end
89
+
90
+ context 'when there is no debug flag configured at the top level' do
91
+ it 'should set the debug flag to the default' do
92
+ expect { subject }.to change { client.debug? }.to(false)
93
+ end
94
+ end
95
+ end
96
+
97
+ context 'with new setting' do
98
+ let(:new_debug) { 'false' }
99
+
100
+ it 'should change the value of the debug flag to match the new value' do
101
+ expect { subject }.to change { client.debug? }.from(true).to(false)
102
+ end
103
+ end
104
+ end
105
+
106
+ describe '#logger=' do
107
+ subject { client.logger = new_logger }
108
+
109
+ context 'with nil logger' do
110
+ let(:new_logger) { nil }
111
+
112
+ context 'when there is a logger configured at the top-level module' do
113
+ let(:configured_logger) { double() }
114
+ before { G5AuthenticationClient.configure { |config| config.logger = configured_logger } }
115
+
116
+ it 'should change the value of the logger to match the configuration' do
117
+ expect { subject }.to change { client.logger }.from(logger).to(configured_logger)
118
+ end
119
+ end
120
+
121
+ context 'when there is no logger configured at the top level' do
122
+ it 'should change the value of the logger to the default' do
123
+ expect { subject }.to change { client.logger }
124
+ client.logger.should be_an_instance_of(Logger)
125
+ end
126
+ end
127
+ end
128
+
129
+ context 'with new logger' do
130
+ let(:new_logger) { double() }
131
+
132
+ it 'should change the value of the logger to match the new value' do
133
+ expect { subject }.to change { client.logger }.from(logger).to(new_logger)
134
+ end
135
+ end
136
+ end
137
+
138
+ its(:username) { should == username }
139
+
140
+ it_should_behave_like 'a module configured attribute',:username, nil
141
+
142
+ its(:password) { should == password }
143
+
144
+ it_should_behave_like 'a module configured attribute', :password, nil
145
+
146
+ its(:endpoint){ should == endpoint}
147
+
148
+ it_should_behave_like 'a module configured attribute', :endpoint,G5AuthenticationClient::DEFAULT_ENDPOINT
149
+
150
+ its(:client_id) { should == client_id}
151
+
152
+ it_should_behave_like 'a module configured attribute', :client_id, G5AuthenticationClient::DEFAULT_CLIENT_ID
153
+
154
+ its(:client_secret) {should ==client_secret}
155
+
156
+ it_should_behave_like 'a module configured attribute', :client_secret, G5AuthenticationClient::DEFAULT_CLIENT_SECRET
157
+
158
+ its(:redirect_uri) {should ==redirect_uri}
159
+
160
+ it_should_behave_like 'a module configured attribute', :redirect_uri, G5AuthenticationClient::DEFAULT_REDIRECT_URI
161
+
162
+ its(:authorization_code) { should == authorization_code}
163
+ it_should_behave_like 'a module configured attribute', :authorization_code, nil
164
+
165
+ its(:access_token) { should == access_token_value }
166
+ it_should_behave_like 'a module configured attribute', :access_token, nil
167
+
168
+ its(:allow_password_credentials) { should == allow_password_credentials }
169
+ it_should_behave_like 'a module configured attribute', :allow_password_credentials, 'true'
170
+ end
171
+
172
+ describe '#allow_password_credentials??' do
173
+ subject{ client.allow_password_credentials? }
174
+
175
+ context 'when the allow_password_credentials is set to true' do
176
+ let(:allow_password_credentials) {'true'}
177
+
178
+ context 'with non-nil username and password' do
179
+
180
+ it 'should be true' do
181
+ expect(subject).to be_true
182
+ end
183
+ end
184
+
185
+ context 'when username is nil' do
186
+ let(:username) {}
187
+
188
+ it 'should be false' do
189
+ expect(subject).to be_false
190
+ end
191
+ end
192
+
193
+ context 'when password is nil' do
194
+ let(:password) {}
195
+
196
+ it 'should be false' do
197
+ expect(subject).to be_false
198
+ end
199
+ end
200
+ end
201
+
202
+ context 'when the allow_password_credentials is set to false' do
203
+ let(:allow_password_credentials) {'false'}
204
+
205
+ it 'should be false' do
206
+ expect(subject).to be_false
207
+ end
208
+ end
209
+ end
210
+
211
+ describe '#get_access_token' do
212
+ subject(:get_access_token) { client.get_access_token }
213
+
214
+ it "should return the access token" do
215
+ expect(subject).to eq(access_token)
216
+ end
217
+ end
218
+
219
+ describe '#create_user' do
220
+ subject(:create_user) { client.create_user(new_user_options) }
221
+
222
+ before do
223
+ stub_request(:post, /#{endpoint}\/v1\/users/).
224
+ with(headers: {'Authorization' => auth_header_value}).
225
+ to_return(status: 200,
226
+ body: returned_user.to_json,
227
+ headers: {'Content-Type' => 'application/json'})
228
+ end
229
+
230
+ it_should_behave_like 'an oauth protected resource', G5AuthenticationClient::User
231
+ end
232
+
233
+ describe '#update_user' do
234
+ subject(:update_user) { client.update_user(new_user_options) }
235
+
236
+ before do
237
+ stub_request(:put, /#{endpoint}\/v1\/users\/#{user_id}/).
238
+ with(headers: {'Authorization' => auth_header_value}).
239
+ to_return(status: 200,
240
+ body: returned_user.to_json,
241
+ headers: {'Content-Type' => 'application/json'})
242
+ end
243
+
244
+ it_should_behave_like 'an oauth protected resource', G5AuthenticationClient::User
245
+ end
246
+
247
+ describe '#get_user' do
248
+ subject(:get_user) { client.get_user(user_id) }
249
+
250
+ before do
251
+ stub_request(:get, /#{endpoint}\/v1\/users\/#{user_id}/).
252
+ with(headers: {'Authorization' => auth_header_value}).
253
+ to_return(status: 200,
254
+ body: returned_user.to_json,
255
+ headers: {'Content-Type' => 'application/json'})
256
+ end
257
+
258
+ it_should_behave_like 'an oauth protected resource', G5AuthenticationClient::User
259
+ end
260
+
261
+ describe '#delete_user' do
262
+ subject(:delete_user) { client.delete_user(user_id) }
263
+
264
+ before do
265
+ stub_request(:delete, /#{endpoint}\/v1\/users\/#{user_id}/).
266
+ with(headers: {'Authorization' => auth_header_value}).
267
+ to_return(status: 200,
268
+ body: returned_user.to_json,
269
+ headers: {'Content-Type' => 'application/json'})
270
+ end
271
+
272
+ it_should_behave_like 'an oauth protected resource', G5AuthenticationClient::User
273
+ end
274
+
275
+ describe '#me' do
276
+ subject(:me) { client.me }
277
+
278
+ before do
279
+ stub_request(:get, /#{endpoint}\/v1\/me/).
280
+ with(headers: {'Authorization' => auth_header_value}).
281
+ to_return(status: 200,
282
+ body: returned_user.to_json,
283
+ headers: {'Content-Type' => 'application/json'})
284
+ end
285
+
286
+ it_should_behave_like 'an oauth protected resource', G5AuthenticationClient::User
287
+ end
288
+
289
+ describe '#sign_out_url' do
290
+ subject { sign_out_url }
291
+
292
+ context 'without redirect_url' do
293
+ let(:sign_out_url) { client.sign_out_url }
294
+
295
+ it 'should add the sign out path to the configured endpoint' do
296
+ expect(sign_out_url).to eq("#{endpoint}/users/sign_out")
297
+ end
298
+ end
299
+
300
+ context 'with redirect_url' do
301
+ let(:sign_out_url) { client.sign_out_url('https://test.host/home')}
302
+
303
+ it 'should add the sign out path to the endpoint' do
304
+ expect(sign_out_url).to match /^#{endpoint}\/users\/sign_out/
305
+ end
306
+
307
+ it 'should add the redirect_url as a query param' do
308
+ expect(sign_out_url). to match /\?redirect_url=https%3A%2F%2Ftest.host%2Fhome$/
309
+ end
310
+ end
311
+ end
312
+
313
+ describe '#token_info' do
314
+ subject(:token_info) { client.token_info }
315
+
316
+ let(:returned_token_info) do
317
+ {
318
+ resource_owner_id: '42',
319
+ scopes: [],
320
+ expires_in_seconds: 3600,
321
+ application: { uid: 'application-uid-abc123' }
322
+ }
323
+ end
324
+
325
+ before do
326
+ stub_request(:get, /#{endpoint}\/oauth\/token\/info/).
327
+ with(headers: {'Authorization' => auth_header_value}).
328
+ to_return(status: 200,
329
+ body: returned_token_info.to_json,
330
+ headers: {'Content-Type' => 'application/json'})
331
+ end
332
+
333
+ it_should_behave_like 'an oauth protected resource', G5AuthenticationClient::TokenInfo
334
+ end
335
+ end