g5_authentication_client 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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