stormpath-sdk 1.0.0.beta.7 → 1.0.0.beta.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +8 -29
  3. data/CHANGES.md +10 -0
  4. data/README.md +64 -0
  5. data/lib/stormpath-sdk.rb +5 -1
  6. data/lib/stormpath-sdk/api_key.rb +0 -3
  7. data/lib/stormpath-sdk/auth/basic_authenticator.rb +1 -1
  8. data/lib/stormpath-sdk/auth/username_password_request.rb +1 -1
  9. data/lib/stormpath-sdk/client.rb +16 -17
  10. data/lib/stormpath-sdk/data_store.rb +4 -3
  11. data/lib/stormpath-sdk/error.rb +19 -18
  12. data/lib/stormpath-sdk/http/authc/sauthc1_signer.rb +18 -28
  13. data/lib/stormpath-sdk/http/http_client_request_executor.rb +5 -16
  14. data/lib/stormpath-sdk/http/request.rb +3 -2
  15. data/lib/stormpath-sdk/http/response.rb +3 -3
  16. data/lib/stormpath-sdk/id_site/id_site_result.rb +17 -0
  17. data/lib/stormpath-sdk/resource/application.rb +35 -1
  18. data/lib/stormpath-sdk/resource/collection.rb +1 -1
  19. data/lib/stormpath-sdk/resource/directory.rb +2 -0
  20. data/lib/stormpath-sdk/resource/group.rb +1 -1
  21. data/lib/stormpath-sdk/resource/tenant.rb +2 -0
  22. data/lib/stormpath-sdk/util/assert.rb +4 -4
  23. data/lib/stormpath-sdk/version.rb +2 -2
  24. data/spec/auth/basic_authenticator_spec.rb +1 -1
  25. data/spec/auth/sauthc1_signer_spec.rb +4 -4
  26. data/spec/client_spec.rb +1 -1
  27. data/spec/data_store_spec.rb +1 -1
  28. data/spec/provider/account_resolver_spec.rb +1 -1
  29. data/spec/resource/application_spec.rb +176 -0
  30. data/spec/resource/collection_spec.rb +4 -4
  31. data/spec/resource/directory_spec.rb +18 -0
  32. data/spec/resource/group_spec.rb +11 -0
  33. data/spec/resource/tenant_spec.rb +12 -0
  34. data/spec/spec_helper.rb +5 -1
  35. data/stormpath-sdk.gemspec +1 -0
  36. metadata +61 -81
  37. data/lib/stormpath-sdk/ext/hash.rb +0 -31
@@ -18,9 +18,9 @@ module Stormpath
18
18
  class Request
19
19
  include Stormpath::Http::Utils
20
20
 
21
- attr_accessor :http_method, :href, :query_string, :http_headers, :body
21
+ attr_accessor :http_method, :href, :query_string, :http_headers, :body, :api_key
22
22
 
23
- def initialize(http_method, href, query_string, http_headers, body)
23
+ def initialize(http_method, href, query_string, http_headers, body, api_key)
24
24
 
25
25
  splitted = href.split '?'
26
26
 
@@ -41,6 +41,7 @@ module Stormpath
41
41
  @http_method = http_method.upcase
42
42
  @http_headers = http_headers
43
43
  @body = body
44
+ @api_key = api_key
44
45
 
45
46
  if body
46
47
  @http_headers.store 'Content-Length', @body.bytesize
@@ -17,8 +17,8 @@ module Stormpath
17
17
  module Http
18
18
  class Response
19
19
 
20
- attr_reader :http_status, :headers, :body
21
- attr_writer :headers
20
+ attr_reader :http_status, :body
21
+ attr_accessor :headers
22
22
 
23
23
  def initialize http_status, content_type, body, content_length
24
24
  @http_status = http_status
@@ -42,4 +42,4 @@ module Stormpath
42
42
 
43
43
  end
44
44
  end
45
- end
45
+ end
@@ -0,0 +1,17 @@
1
+ module Stormpath
2
+ module IdSite
3
+ class IdSiteResult
4
+ attr_accessor :account_href, :state, :status, :is_new_account
5
+
6
+ alias_method :new_account?, :is_new_account
7
+
8
+ def initialize(jwt_response)
9
+ @account_href = jwt_response["sub"]
10
+ @status = jwt_response["status"]
11
+ @state = jwt_response["state"]
12
+ @is_new_account = jwt_response["isNewSub"]
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -15,6 +15,8 @@
15
15
  #
16
16
  class Stormpath::Resource::Application < Stormpath::Resource::Instance
17
17
  include Stormpath::Resource::Status
18
+ include Stormpath::Resource::CustomDataStorage
19
+ include UUIDTools
18
20
 
19
21
  class LoadError < Stormpath::Error; end
20
22
 
@@ -26,9 +28,10 @@ class Stormpath::Resource::Application < Stormpath::Resource::Instance
26
28
  has_many :password_reset_tokens, can: [:get, :create]
27
29
  has_many :account_store_mappings, can: [:get, :create]
28
30
  has_many :groups, can: [:get, :create]
29
-
31
+
30
32
  has_one :default_account_store_mapping, class_name: :accountStoreMapping
31
33
  has_one :default_group_store_mapping, class_name: :accountStoreMapping
34
+ has_one :custom_data
32
35
 
33
36
  def self.load composite_url
34
37
  begin
@@ -47,6 +50,37 @@ class Stormpath::Resource::Application < Stormpath::Resource::Instance
47
50
  end
48
51
  end
49
52
 
53
+ def create_id_site_url(options = {})
54
+ base = client.data_store.base_url.sub("v" + Stormpath::DataStore::DEFAULT_API_VERSION.to_s, "sso")
55
+ base += '/logout' if options[:logout]
56
+
57
+ token = JWT.encode({
58
+ 'iat' => Time.now.to_i,
59
+ 'jti' => UUID.method(:random_create).call.to_s,
60
+ 'iss' => client.data_store.api_key.id,
61
+ 'sub' => href,
62
+ 'cb_uri' => options[:callback_uri],
63
+ 'path' => options[:path] || '',
64
+ 'state' => options[:state] || ''
65
+ }, client.data_store.api_key.secret, 'HS256')
66
+
67
+ base + '?jwtRequest=' + token
68
+ end
69
+
70
+ def handle_id_site_callback(response_url)
71
+ assert_not_nil response_url, "No response provided. Please provide response object."
72
+
73
+ uri = URI(response_url)
74
+ params = CGI::parse(uri.query)
75
+ token = params["jwtResponse"].first
76
+
77
+ jwt_response, _header = JWT.decode(token, client.data_store.api_key.secret)
78
+
79
+ raise Stormpath::Error.new if jwt_response["aud"] != client.data_store.api_key.id
80
+
81
+ Stormpath::IdSite::IdSiteResult.new(jwt_response)
82
+ end
83
+
50
84
  def send_password_reset_email email
51
85
  password_reset_token = create_password_reset_token email;
52
86
  password_reset_token.account
@@ -86,7 +86,7 @@ class Stormpath::Resource::Collection
86
86
  class CollectionPage < Stormpath::Resource::Base
87
87
  ITEMS = 'items'
88
88
 
89
- prop_accessor :offset, :limit
89
+ prop_accessor :offset, :limit, :size
90
90
 
91
91
  attr_accessor :item_type
92
92
 
@@ -15,6 +15,7 @@
15
15
  #
16
16
  class Stormpath::Resource::Directory < Stormpath::Resource::Instance
17
17
  include Stormpath::Resource::Status
18
+ include Stormpath::Resource::CustomDataStorage
18
19
 
19
20
  prop_accessor :name, :description
20
21
 
@@ -22,6 +23,7 @@ class Stormpath::Resource::Directory < Stormpath::Resource::Instance
22
23
 
23
24
  has_many :accounts, can: [:get, :create]
24
25
  has_many :groups, can: [:get, :create]
26
+ has_one :custom_data
25
27
 
26
28
  def create_account account, registration_workflow_enabled=nil
27
29
  href = accounts.href
@@ -35,5 +35,5 @@ class Stormpath::Resource::Group < Stormpath::Resource::Instance
35
35
  account_membership = account_memberships.find {|account_membership| account_membership.account.href == account.href }
36
36
  account_membership.delete if account_membership
37
37
  end
38
-
38
+
39
39
  end
@@ -14,10 +14,12 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  class Stormpath::Resource::Tenant < Stormpath::Resource::Instance
17
+ include Stormpath::Resource::CustomDataStorage
17
18
 
18
19
  prop_reader :name, :key
19
20
 
20
21
  has_many :applications
21
22
  has_many :directories
23
+ has_one :custom_data
22
24
 
23
25
  end
@@ -18,19 +18,19 @@ module Stormpath
18
18
  module Assert
19
19
 
20
20
  def assert_not_nil(object, message)
21
- raise ArgumentError, message, caller unless !object.nil?
21
+ raise(ArgumentError, message, caller) if object.nil?
22
22
  end
23
23
 
24
24
  def assert_kind_of(clazz, object, message)
25
- raise ArgumentError, message, caller unless object.kind_of? clazz
25
+ raise(ArgumentError, message, caller) unless object.kind_of? clazz
26
26
  end
27
27
 
28
28
  def assert_true(arg, message)
29
- raise ArgumentError, message, caller unless arg
29
+ raise(ArgumentError, message, caller) unless arg
30
30
  end
31
31
 
32
32
  def assert_false(arg, message)
33
- raise ArgumentError, message, caller unless !arg
33
+ raise(ArgumentError, message, caller) if arg
34
34
  end
35
35
 
36
36
  end
@@ -14,6 +14,6 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  module Stormpath
17
- VERSION = '1.0.0.beta.7'
18
- VERSION_DATE = '2014-07-21'
17
+ VERSION = '1.0.0.beta.8'
18
+ VERSION_DATE = '2015-07-28'
19
19
  end
@@ -4,7 +4,7 @@ describe "BasicAuthenticator" do
4
4
  context "given an instance of BasicAuthenticator" do
5
5
 
6
6
  before do
7
- data_store = Stormpath::DataStore.new "", {}, ""
7
+ data_store = Stormpath::DataStore.new "", "", {}, ""
8
8
  allow(test_api_client).to receive(:data_store).and_return(data_store)
9
9
  auth_result = Stormpath::Authentication::AuthenticationResult.new({}, test_api_client)
10
10
  allow(data_store).to receive(:create).and_return(auth_result)
@@ -19,17 +19,17 @@ describe Stormpath::Http::Authc::Sauthc1Signer do
19
19
  let(:fake_api_key) { Stormpath::ApiKey.new('foo', 'bar') }
20
20
 
21
21
  let(:empty_query_hash_request) do
22
- Stormpath::Http::Request.new 'get', 'http://example.com/resources/abc123?q=red blue', nil, Hash.new, nil
22
+ Stormpath::Http::Request.new 'get', 'http://example.com/resources/abc123?q=red blue', nil, Hash.new, nil, test_api_key
23
23
  end
24
24
 
25
25
  let(:filled_query_hash_request) do
26
- Stormpath::Http::Request.new 'get', 'http://example.com/resources/abc123', {'q' => 'red blue'}, Hash.new, nil
26
+ Stormpath::Http::Request.new 'get', 'http://example.com/resources/abc123', {'q' => 'red blue'}, Hash.new, nil, test_api_key
27
27
  end
28
28
 
29
29
  before do
30
30
  Timecop.freeze(Time.now)
31
- signer.sign_request empty_query_hash_request, fake_api_key
32
- signer.sign_request filled_query_hash_request, fake_api_key
31
+ signer.sign_request empty_query_hash_request
32
+ signer.sign_request filled_query_hash_request
33
33
  end
34
34
 
35
35
  it 'assigns identical headers to both requests' do
@@ -262,7 +262,7 @@ properties
262
262
  it 'initializes the request executor with the proxy' do
263
263
  expect(Stormpath::Http::HttpClientRequestExecutor)
264
264
  .to receive(:new)
265
- .with(api_key, proxy: http_proxy)
265
+ .with(proxy: http_proxy)
266
266
  .and_return request_executor
267
267
 
268
268
  Stormpath::Client.new api_key: api_key, proxy: http_proxy
@@ -4,7 +4,7 @@ describe Stormpath::DataStore do
4
4
  let(:factory) { Stormpath::Test::ResourceFactory.new }
5
5
  let(:request_executor) { Stormpath::Test::TestRequestExecutor.new }
6
6
  let(:store) { Stormpath::Cache::RedisStore }
7
- let(:data_store) { Stormpath::DataStore.new request_executor, {store: store}, nil, nil }
7
+ let(:data_store) { Stormpath::DataStore.new request_executor, test_api_key, {store: store}, nil, nil }
8
8
  let(:application_cache) { data_store.cache_manager.get_cache 'applications' }
9
9
  let(:tenant_cache) { data_store.cache_manager.get_cache 'tenants' }
10
10
  let(:group_cache) { data_store.cache_manager.get_cache 'groups' }
@@ -4,7 +4,7 @@ describe "ProviderAccountResolver" do
4
4
  context "given an instance of ProviderAccountResolver" do
5
5
 
6
6
  before do
7
- data_store = Stormpath::DataStore.new "", {}, ""
7
+ data_store = Stormpath::DataStore.new "", "", {}, ""
8
8
  allow(test_api_client).to receive(:data_store).and_return(data_store)
9
9
  auth_result = Stormpath::Provider::AccountResult.new({}, test_api_client)
10
10
  allow(data_store).to receive(:create).and_return(auth_result)
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ include UUIDTools
2
3
 
3
4
  describe Stormpath::Resource::Application, :vcr do
4
5
  let(:app) { test_api_client.applications.create name: random_application_name, description: 'Dummy desc.' }
@@ -28,6 +29,7 @@ describe Stormpath::Resource::Application, :vcr do
28
29
  expect(application.tenant).to be_a Stormpath::Resource::Tenant
29
30
  expect(application.default_account_store_mapping).to be_a Stormpath::Resource::AccountStoreMapping
30
31
  expect(application.default_group_store_mapping).to be_a Stormpath::Resource::AccountStoreMapping
32
+ expect(application.custom_data).to be_a Stormpath::Resource::CustomData
31
33
 
32
34
  expect(application.groups).to be_a Stormpath::Resource::Collection
33
35
  expect(application.accounts).to be_a Stormpath::Resource::Collection
@@ -288,4 +290,178 @@ describe Stormpath::Resource::Application, :vcr do
288
290
  end
289
291
  end
290
292
  end
293
+
294
+ describe '#create_application_with_custom_data' do
295
+ it 'creates an application with custom data' do
296
+ application.custom_data["category"] = "classified"
297
+ application.save
298
+
299
+ expect(application.custom_data["category"]).to eq("classified")
300
+ end
301
+ end
302
+
303
+ describe '#create_id_site_url' do
304
+ let(:jwt_token) { JWT.encode({
305
+ 'iat' => Time.now.to_i,
306
+ 'jti' => UUID.method(:random_create).call.to_s,
307
+ 'aud' => test_api_key_id,
308
+ 'sub' => application.href,
309
+ 'cb_uri' => 'http://localhost:9292/redirect',
310
+ 'path' => '',
311
+ 'state' => ''
312
+ }, test_api_key_secret, 'HS256')
313
+ }
314
+
315
+ let(:create_id_site_url_result) do
316
+ options = { callback_uri: 'http://localhost:9292/redirect' }
317
+ application.create_id_site_url options
318
+ end
319
+
320
+ it 'should create a url with jwtRequest' do
321
+ expect(create_id_site_url_result).to include('jwtRequest')
322
+ end
323
+
324
+ it 'should create a request to /sso' do
325
+ expect(create_id_site_url_result).to include('/sso')
326
+ end
327
+
328
+ it 'should create a jwtRequest that is signed wit the client secret' do
329
+ uri = Addressable::URI.parse(create_id_site_url_result)
330
+ jwt_token = JWT.decode(uri.query_values["jwtRequest"], test_api_key_secret).first
331
+
332
+ expect(jwt_token["iss"]).to eq test_api_key_id
333
+ expect(jwt_token["sub"]).to eq application.href
334
+ expect(jwt_token["cb_uri"]).to eq 'http://localhost:9292/redirect'
335
+ end
336
+
337
+ context 'with logout option' do
338
+ it 'shoud create a request to /sso/logout' do
339
+ end
340
+ end
341
+ end
342
+
343
+ describe '#handle_id_site_callback' do
344
+ let(:callback_uri_base) { 'http://localhost:9292/somwhere?jwtResponse=' }
345
+
346
+ context 'without the response_url provided' do
347
+ it 'should raise argument error' do
348
+ expect { application.handle_id_site_callback(nil) }.to raise_error(ArgumentError)
349
+ end
350
+ end
351
+
352
+ context 'with a valid jwt response' do
353
+ let(:jwt_token) { JWT.encode({
354
+ 'iat' => Time.now.to_i,
355
+ 'aud' => test_api_key_id,
356
+ 'sub' => application.href,
357
+ 'path' => '',
358
+ 'state' => '',
359
+ 'isNewSub' => true,
360
+ 'status' => "REGISTERED"
361
+ }, test_api_key_secret, 'HS256')
362
+ }
363
+
364
+ before do
365
+ @site_result = application.handle_id_site_callback(callback_uri_base + jwt_token)
366
+ end
367
+
368
+ it 'should return IdSiteResult object' do
369
+ expect(@site_result).to be_kind_of(Stormpath::IdSite::IdSiteResult)
370
+ end
371
+
372
+ it 'should set the correct account on IdSiteResult object' do
373
+ expect(@site_result.account_href).to eq(application.href)
374
+ end
375
+
376
+ it 'should set the correct status on IdSiteResult object' do
377
+ expect(@site_result.status).to eq("REGISTERED")
378
+ end
379
+
380
+ it 'should set the correct state on IdSiteResult object' do
381
+ expect(@site_result.state).to eq("")
382
+ end
383
+
384
+ it 'should set the correct is_new_account on IdSiteResult object' do
385
+ expect(@site_result.new_account?).to eq(true)
386
+ end
387
+ end
388
+
389
+ context 'with an expired token' do
390
+ let(:jwt_token) { JWT.encode({
391
+ 'iat' => Time.now.to_i,
392
+ 'aud' => test_api_key_id,
393
+ 'sub' => application.href,
394
+ 'path' => '',
395
+ 'state' => '',
396
+ 'exp' => Time.now.to_i - 1,
397
+ 'isNewSub' => true,
398
+ 'status' => "REGISTERED"
399
+ }, test_api_key_secret, 'HS256')
400
+ }
401
+
402
+ it 'should raise expiration error' do
403
+ expect {
404
+ application.handle_id_site_callback(callback_uri_base + jwt_token)
405
+ }.to raise_error(JWT::DecodeError)
406
+ end
407
+ end
408
+
409
+ context 'with a different client id (aud)' do
410
+ let(:jwt_token) { JWT.encode({
411
+ 'iat' => Time.now.to_i,
412
+ 'aud' => UUID.method(:random_create).call.to_s,
413
+ 'sub' => application.href,
414
+ 'path' => '',
415
+ 'state' => '',
416
+ 'isNewSub' => true,
417
+ 'status' => "REGISTERED"
418
+ }, test_api_key_secret, 'HS256')
419
+ }
420
+
421
+ it 'should raise error' do
422
+ expect {
423
+ application.handle_id_site_callback(callback_uri_base + jwt_token)
424
+ }.to raise_error(Stormpath::Error)
425
+ end
426
+ end
427
+
428
+ context 'with an invalid exp value' do
429
+ let(:jwt_token) { JWT.encode({
430
+ 'iat' => Time.now.to_i,
431
+ 'aud' => test_api_key_id,
432
+ 'sub' => application.href,
433
+ 'path' => '',
434
+ 'state' => '',
435
+ 'exp' => 'not gona work',
436
+ 'isNewSub' => true,
437
+ 'status' => "REGISTERED"
438
+ }, test_api_key_secret, 'HS256')
439
+ }
440
+
441
+ it 'should error with the expiration error' do
442
+ expect {
443
+ application.handle_id_site_callback(callback_uri_base + jwt_token)
444
+ }.to raise_error(JWT::DecodeError)
445
+ end
446
+ end
447
+
448
+ context 'with an invalid signature' do
449
+ let(:jwt_token) { JWT.encode({
450
+ 'iat' => Time.now.to_i,
451
+ 'aud' => test_api_key_id,
452
+ 'sub' => application.href,
453
+ 'path' => '',
454
+ 'state' => '',
455
+ 'isNewSub' => true,
456
+ 'status' => "REGISTERED"
457
+ }, 'false key', 'HS256')
458
+ }
459
+
460
+ it 'should reject the signature' do
461
+ expect {
462
+ application.handle_id_site_callback(callback_uri_base + jwt_token)
463
+ }.to raise_error(JWT::DecodeError)
464
+ end
465
+ end
466
+ end
291
467
  end