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

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.
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