stormpath-sdk 1.0.0.beta.4 → 1.0.0.beta.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/.gitignore +1 -0
  2. data/CHANGES.md +15 -0
  3. data/README.md +35 -3
  4. data/lib/stormpath-sdk.rb +5 -6
  5. data/lib/stormpath-sdk/auth/basic_authenticator.rb +2 -0
  6. data/lib/stormpath-sdk/auth/basic_login_attempt.rb +11 -0
  7. data/lib/stormpath-sdk/auth/username_password_request.rb +6 -12
  8. data/lib/stormpath-sdk/data_store.rb +118 -127
  9. data/lib/stormpath-sdk/http/http_client_request_executor.rb +10 -42
  10. data/lib/stormpath-sdk/resource/account.rb +13 -3
  11. data/lib/stormpath-sdk/resource/account_membership.rb +16 -0
  12. data/lib/stormpath-sdk/resource/account_status.rb +26 -0
  13. data/lib/stormpath-sdk/resource/account_store_mapping.rb +4 -2
  14. data/lib/stormpath-sdk/resource/application.rb +4 -2
  15. data/lib/stormpath-sdk/resource/associations.rb +7 -3
  16. data/lib/stormpath-sdk/resource/base.rb +21 -15
  17. data/lib/stormpath-sdk/resource/custom_data.rb +86 -0
  18. data/lib/stormpath-sdk/resource/custom_data_hash_methods.rb +33 -0
  19. data/lib/stormpath-sdk/resource/custom_data_storage.rb +39 -0
  20. data/lib/stormpath-sdk/resource/directory.rb +4 -4
  21. data/lib/stormpath-sdk/resource/expansion.rb +15 -0
  22. data/lib/stormpath-sdk/resource/group.rb +10 -0
  23. data/lib/stormpath-sdk/resource/status.rb +16 -5
  24. data/lib/stormpath-sdk/version.rb +2 -2
  25. data/spec/client_spec.rb +6 -1
  26. data/spec/data_store_spec.rb +7 -2
  27. data/spec/resource/account_spec.rb +73 -30
  28. data/spec/resource/account_store_mapping_spec.rb +20 -5
  29. data/spec/resource/application_spec.rb +135 -0
  30. data/spec/resource/custom_data_spec.rb +198 -0
  31. data/spec/resource/directory_spec.rb +192 -9
  32. data/spec/resource/group_membership_spec.rb +35 -0
  33. data/spec/resource/group_spec.rb +44 -26
  34. data/spec/resource/status_spec.rb +81 -0
  35. data/spec/resource/tenant_spec.rb +19 -0
  36. data/stormpath-sdk.gemspec +2 -2
  37. metadata +13 -3
data/.gitignore CHANGED
@@ -11,3 +11,4 @@ spec/fixtures/vcr_cassettes
11
11
  Guardfile
12
12
  tmp/*
13
13
  *.gem
14
+ .bundle/
data/CHANGES.md CHANGED
@@ -1,6 +1,21 @@
1
1
  stormpath-sdk-ruby Changelog
2
2
  ============================
3
3
 
4
+ Version 1.0.0.beta.5
5
+ --------------------
6
+
7
+ Released on March 3, 2014
8
+
9
+ - Added the Custom Data resource
10
+ - Added CustomDataStorage module
11
+ - Specify an AccountStore during authentication
12
+ - Added AccountStatus module (specialization of the Status module)
13
+ - Added Status spec
14
+ - Added AccountMemberships
15
+ - Added GroupMemberships spec
16
+ - Send only dirty properties to the API
17
+ - Fixed REDIRECTS_LIMIT issue
18
+
4
19
  Version 1.0.0.beta.4
5
20
  --------------------
6
21
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://api.travis-ci.org/stormpath/stormpath-sdk-ruby.png?branch=master)](https://travis-ci.org/stormpath/stormpath-sdk-ruby)
1
+ [![Build Status](https://api.travis-ci.org/stormpath/stormpath-sdk-ruby.png?branch=master,development)](https://travis-ci.org/stormpath/stormpath-sdk-ruby)
2
2
 
3
3
  # Stormpath Ruby SDK
4
4
 
@@ -416,7 +416,7 @@ Group membership can be created by:
416
416
  * Explicitly creating a group membership resource with your client:
417
417
 
418
418
  ```ruby
419
- group_memebership = client.group_memberships.create group, account
419
+ group_membership = client.group_memberships.create group: group, account: account
420
420
  ```
421
421
 
422
422
  * Using the <code>add_group</code> method on the account instance:
@@ -428,11 +428,43 @@ Group membership can be created by:
428
428
  * Using the <code>add_account</code> method on the group instance:
429
429
 
430
430
  ```ruby
431
- group.add_group account
431
+ group.add_account account
432
432
  ```
433
433
 
434
434
  You will need to reload the account or group resource after these
435
435
  operations to ensure they've picked up the changes.
436
+ ### Add Custom Data to Accounts or Groups
437
+
438
+ Account and Group resources have predefined fields that are useful to many applications, but you are likely to have your own custom data that you need to associate with an account or group as well.
439
+
440
+ For this reason, both the account and group resources support a linked custom_data resource that you can use for your own needs.
441
+
442
+ *Set Custom Data*
443
+ ```ruby
444
+ account = Stormpath::Resource::Account.new({ email: "test@example.com", given_name: 'Ruby SDK', password: 'P@$$w0rd', surname: 'SDK',})
445
+
446
+ account.custom_data["rank"] = "Captain"
447
+ account.custom_data["birth_date"] = "2305-07-13"
448
+ account.custom_data["birth_place"] = "La Barre, France"
449
+
450
+ directory.create_account account
451
+ ```
452
+
453
+ Notice how we did not call account.custom_data.save - creating the account (or updating it later via save) will automatically persist the account's customData resource. The account 'knows' that the custom data resource has been changed and it will propogate those changes automatically when you persist the account.
454
+
455
+ Groups work the same way - you can save a group and it's custom data resource will be saved as well.
456
+
457
+ *Delete a specific Custom Data field*
458
+ ```ruby
459
+ account.custom_data["birth_date"] #=> "2305-07-13"
460
+ account.custom_data.delete("birth_date")
461
+ account.custom_data.save
462
+ ```
463
+
464
+ *Delete all Custom Data*
465
+ ```ruby
466
+ account.custom_data.delete
467
+ ```
436
468
 
437
469
  ## Testing
438
470
 
data/lib/stormpath-sdk.rb CHANGED
@@ -25,28 +25,27 @@ module Stormpath
25
25
  module Resource
26
26
  autoload :Expansion, 'stormpath-sdk/resource/expansion'
27
27
  autoload :Status, 'stormpath-sdk/resource/status'
28
+ autoload :AccountStatus, 'stormpath-sdk/resource/account_status'
28
29
  autoload :Utils, 'stormpath-sdk/resource/utils'
29
30
  autoload :Associations, 'stormpath-sdk/resource/associations'
30
31
  autoload :Base, 'stormpath-sdk/resource/base'
31
32
  autoload :Error, 'stormpath-sdk/resource/error'
32
33
  autoload :Instance, 'stormpath-sdk/resource/instance'
33
34
  autoload :Collection, 'stormpath-sdk/resource/collection'
35
+ autoload :CustomData, 'stormpath-sdk/resource/custom_data'
36
+ autoload :CustomDataStorage, 'stormpath-sdk/resource/custom_data_storage'
37
+ autoload :CustomDataHashMethods, 'stormpath-sdk/resource/custom_data_hash_methods'
34
38
  autoload :Tenant, 'stormpath-sdk/resource/tenant'
35
39
  autoload :Application, 'stormpath-sdk/resource/application'
36
- autoload :Applications, 'stormpath-sdk/resource/applications'
37
40
  autoload :Directory, 'stormpath-sdk/resource/directory'
38
- autoload :Directories, 'stormpath-sdk/resource/directories'
39
41
  autoload :Account, 'stormpath-sdk/resource/account'
40
- autoload :Accounts, 'stormpath-sdk/resource/accounts'
41
42
  autoload :AccountStore, 'stormpath-sdk/resource/account_store'
42
43
  autoload :AccountStoreMapping, 'stormpath-sdk/resource/account_store_mapping'
43
44
  autoload :Group, 'stormpath-sdk/resource/group'
44
- autoload :Groups, 'stormpath-sdk/resource/groups'
45
45
  autoload :EmailVerificationToken, 'stormpath-sdk/resource/email_verification_token'
46
46
  autoload :GroupMembership, 'stormpath-sdk/resource/group_membership'
47
- autoload :GroupMemberships, 'stormpath-sdk/resource/group_memberships'
47
+ autoload :AccountMembership, 'stormpath-sdk/resource/account_membership'
48
48
  autoload :PasswordResetToken, 'stormpath-sdk/resource/password_reset_token'
49
- autoload :PasswordResetTokens, 'stormpath-sdk/resource/password_reset_tokens'
50
49
  end
51
50
 
52
51
  module Cache
@@ -40,6 +40,8 @@ module Stormpath
40
40
  attempt.type = 'basic'
41
41
  attempt.value = value
42
42
 
43
+ attempt.account_store = request.account_store if request.account_store
44
+
43
45
  href = parent_href + '/loginAttempts'
44
46
 
45
47
  @data_store.create href, attempt, AuthenticationResult
@@ -19,6 +19,17 @@ module Stormpath
19
19
 
20
20
  TYPE = "type"
21
21
  VALUE = "value"
22
+ ACCOUNT_STORE = "account_store"
23
+
24
+ def account_store
25
+ get_property ACCOUNT_STORE
26
+ end
27
+
28
+ def account_store=(account_store)
29
+ if account_store.kind_of? Stormpath::Resource::Base
30
+ set_property ACCOUNT_STORE, {HREF_PROP_NAME => account_store.href}
31
+ end
32
+ end
22
33
 
23
34
  def type
24
35
  get_property TYPE
@@ -14,17 +14,16 @@
14
14
  # limitations under the License.
15
15
  #
16
16
  module Stormpath
17
-
18
17
  module Authentication
19
-
20
18
  class UsernamePasswordRequest
21
19
 
22
- attr_reader :host
20
+ attr_reader :host, :account_store
23
21
 
24
- def initialize username, password, host = nil
22
+ def initialize username, password, options = {}
25
23
  @username = username
26
24
  @password = (password != nil and password.length > 0) ? password.chars.to_a : "".chars.to_a
27
- @host = host
25
+ @host = options[:host]
26
+ @account_store = options[:account_store]
28
27
  end
29
28
 
30
29
  def principals
@@ -38,18 +37,13 @@ module Stormpath
38
37
  def clear
39
38
  @username = nil
40
39
  @host = nil
40
+ @account_store = nil
41
41
 
42
- @password.each { |pass_char|
43
-
44
- pass_char = 0x00
45
- }
46
-
42
+ @password.each { |pass_char| pass_char = 0x00 }
47
43
  @password = nil
48
44
  end
49
45
 
50
46
  end
51
-
52
47
  end
53
-
54
48
  end
55
49
 
@@ -19,16 +19,17 @@ class Stormpath::DataStore
19
19
 
20
20
  DEFAULT_SERVER_HOST = "api.stormpath.com"
21
21
  DEFAULT_API_VERSION = 1
22
+ HREF_PROP_NAME = Stormpath::Resource::Base::HREF_PROP_NAME
22
23
 
23
- CACHE_REGIONS = %w( applications directories accounts groups groupMemberships tenants )
24
+ CACHE_REGIONS = %w( applications directories accounts groups groupMemberships accountMemberships tenants customData )
24
25
 
25
- attr_reader :client, :request_executor
26
+ attr_reader :client, :request_executor, :cache_manager
26
27
 
27
- def initialize(request_executor, cache_opts, client, *base_url)
28
+ def initialize(request_executor, cache_opts, client, base_url = nil)
28
29
  assert_not_nil request_executor, "RequestExecutor cannot be null."
29
30
 
30
31
  @client = client
31
- @base_url = get_base_url(*base_url)
32
+ @base_url = get_base_url(base_url)
32
33
  @request_executor = request_executor
33
34
  initialize_cache cache_opts
34
35
  end
@@ -48,11 +49,7 @@ class Stormpath::DataStore
48
49
  end
49
50
 
50
51
  def get_resource(href, clazz, query=nil)
51
- q_href = if needs_to_be_fully_qualified href
52
- qualify href
53
- else
54
- href
55
- end
52
+ q_href = qualify href
56
53
 
57
54
  data = execute_request('get', q_href, nil, query)
58
55
  instantiate clazz, data.to_hash
@@ -63,7 +60,7 @@ class Stormpath::DataStore
63
60
  parent_href = "#{parent_href}?#{URI.encode_www_form(options)}" unless options.empty?
64
61
  save_resource(parent_href, resource, return_type).tap do |returned_resource|
65
62
  if resource.kind_of? return_type
66
- resource.set_properties to_hash(returned_resource)
63
+ resource.set_properties returned_resource.properties
67
64
  end
68
65
  end
69
66
  end
@@ -73,13 +70,10 @@ class Stormpath::DataStore
73
70
  assert_kind_of Stormpath::Resource::Base, resource, "resource argument must be instance of Stormpath::Resource::Base"
74
71
 
75
72
  href = resource.href
73
+ assert_not_nil href, "href or resource.href cannot be null."
76
74
  assert_true href.length > 0, "save may only be called on objects that have already been persisted (i.e. they have an existing href)."
77
75
 
78
- href = if needs_to_be_fully_qualified(href)
79
- qualify(href)
80
- else
81
- href
82
- end
76
+ href = qualify(href)
83
77
 
84
78
  clazz ||= resource.class
85
79
 
@@ -88,156 +82,153 @@ class Stormpath::DataStore
88
82
  end
89
83
  end
90
84
 
91
- def delete(resource)
85
+ def delete(resource, property_name = nil)
92
86
  assert_not_nil resource, "resource argument cannot be null."
93
87
  assert_kind_of Stormpath::Resource::Base, resource, "resource argument must be instance of Stormpath::Resource::Base"
94
88
 
95
- execute_request('delete', resource.href)
89
+ href = resource.href
90
+ href += "/#{property_name}" if property_name
91
+ href = qualify(href)
92
+
93
+ execute_request('delete', href)
96
94
  end
97
95
 
98
- def cache_manager
99
- @cache_manager
100
- end
96
+ private
101
97
 
102
- protected
98
+ def needs_to_be_fully_qualified(href)
99
+ !href.downcase.start_with? 'http'
100
+ end
103
101
 
104
- def needs_to_be_fully_qualified(href)
105
- !href.downcase.start_with? 'http'
106
- end
102
+ def qualify(href)
103
+ if needs_to_be_fully_qualified(href)
104
+ slash_added = href.start_with?('/') ? '' : '/'
105
+ @base_url + slash_added + href
106
+ else
107
+ href
108
+ end
109
+ end
107
110
 
108
- def qualify(href)
109
- slash_added = ''
111
+ def execute_request(http_method, href, body=nil, query=nil)
112
+ if http_method == 'get' && (cache = cache_for href)
113
+ cached_result = cache.get href
114
+ return cached_result if cached_result
115
+ end
110
116
 
111
- if !href.start_with? '/'
112
- slash_added = '/'
113
- end
117
+ request = Request.new(http_method, href, query, Hash.new, body)
118
+ apply_default_request_headers request
119
+ response = @request_executor.execute_request request
120
+ result = response.body.length > 0 ? MultiJson.load(response.body) : ''
114
121
 
115
- @base_url + slash_added + href
116
- end
122
+ if response.error?
123
+ error = Stormpath::Resource::Error.new result
124
+ #puts "Error with request: #{http_method.upcase}: #{href}"
125
+ raise Stormpath::Error.new error
126
+ end
117
127
 
118
- private
128
+ if http_method == 'delete'
129
+ cache = cache_for href
130
+ cache.delete href if cache
131
+ return nil
132
+ end
119
133
 
120
- def execute_request(http_method, href, body=nil, query=nil)
121
- if http_method == 'get' && (cache = cache_for href)
122
- cached_result = cache.get href
123
- return cached_result if cached_result
134
+ if result['href']
135
+ cache_walk result
136
+ else
137
+ result
138
+ end
124
139
  end
125
140
 
126
- request = Request.new(http_method, href, query, Hash.new, body)
127
- apply_default_request_headers request
128
- response = @request_executor.execute_request request
141
+ def cache_walk(resource)
142
+ assert_not_nil resource['href'], "resource must have 'href' property"
143
+ items = resource['items']
129
144
 
130
- result = response.body.length > 0 ? MultiJson.load(response.body) : ''
145
+ if items # collection resource
146
+ resource['items'] = items.map do |item|
147
+ cache_walk item
148
+ { 'href' => item['href'] }
149
+ end
150
+ else # single resource
151
+ resource.each do |attr, value|
152
+ if value.is_a? Hash and value['href']
153
+ walked = cache_walk value
154
+ resource[attr] = { 'href' => value['href'] } if value["href"]
155
+ resource[attr]['items'] = walked['items'] if walked['items']
156
+ end
157
+ end
158
+ cache resource if resource.length > 1
159
+ end
160
+ resource
161
+ end
131
162
 
132
- if response.error?
133
- error = Stormpath::Resource::Error.new result
134
- #puts "Error with request: #{http_method.upcase}: #{href}"
135
- raise Stormpath::Error.new error
163
+ def cache(resource)
164
+ cache = cache_for resource['href']
165
+ cache.put resource['href'], resource if cache
136
166
  end
137
167
 
138
- if http_method == 'delete'
139
- cache = cache_for href
140
- cache.delete href if cache
141
- return nil
168
+ def cache_for(href)
169
+ @cache_manager.get_cache(region_for href)
142
170
  end
143
171
 
144
- if result['href']
145
- cache_walk result
146
- else
147
- result
172
+ def region_for(href)
173
+ return nil unless href
174
+ if href.include? "/customData"
175
+ region = href.split('/')[-1]
176
+ else
177
+ region = href.split('/')[-2]
178
+ end
179
+ CACHE_REGIONS.include?(region) ? region : nil
148
180
  end
149
- end
150
181
 
151
- def cache_walk(resource)
152
- assert_not_nil resource['href'], "resource must have 'href' property"
153
- items = resource['items']
182
+ def apply_default_request_headers(request)
183
+ request.http_headers.store 'Accept', 'application/json'
184
+ request.http_headers.store 'User-Agent', 'Stormpath-RubySDK/' + Stormpath::VERSION
154
185
 
155
- if items # collection resource
156
- resource['items'] = items.map do |item|
157
- cache_walk item
158
- { 'href' => item['href'] }
159
- end
160
- else # single resource
161
- resource.each do |attr, value|
162
- if value.is_a? Hash
163
- walked = cache_walk value
164
- resource[attr] = { 'href' => value['href'] }
165
- resource[attr]['items'] = walked['items'] if walked['items']
166
- end
186
+ if !request.body.nil? and request.body.length > 0
187
+ request.http_headers.store 'Content-Type', 'application/json'
167
188
  end
168
- cache resource if resource.length > 1
169
189
  end
170
- resource
171
- end
172
-
173
- def cache(resource)
174
- cache = cache_for resource['href']
175
- cache.put resource['href'], resource if cache
176
- end
177
190
 
178
- def cache_for(href)
179
- @cache_manager.get_cache(region_for href)
180
- end
191
+ def save_resource(href, resource, return_type)
192
+ assert_not_nil resource, "resource argument cannot be null."
193
+ assert_not_nil return_type, "returnType class cannot be null."
194
+ assert_kind_of Stormpath::Resource::Base, resource, "resource argument must be instance of Stormpath::Resource::Base"
181
195
 
182
- def region_for(href)
183
- return nil unless href
184
- region = href.split('/')[-2]
185
- CACHE_REGIONS.include?(region) ? region : nil
186
- end
196
+ q_href = qualify href
187
197
 
188
- def apply_default_request_headers(request)
189
- request.http_headers.store 'Accept', 'application/json'
190
- request.http_headers.store 'User-Agent', 'Stormpath-RubySDK/' + Stormpath::VERSION
198
+ response = execute_request('post', q_href, MultiJson.dump(to_hash(resource)))
191
199
 
192
- if !request.body.nil? and request.body.length > 0
193
- request.http_headers.store 'Content-Type', 'application/json'
200
+ instantiate return_type, response.to_hash
194
201
  end
195
- end
196
-
197
- def save_resource(href, resource, return_type)
198
- assert_not_nil resource, "resource argument cannot be null."
199
- assert_not_nil return_type, "returnType class cannot be null."
200
- assert_kind_of Stormpath::Resource::Base, resource, "resource argument must be instance of Stormpath::Resource::Base"
201
-
202
- q_href = if needs_to_be_fully_qualified href
203
- qualify href
204
- else
205
- href
206
- end
207
202
 
208
- response = execute_request('post', q_href, MultiJson.dump(to_hash(resource)))
209
- instantiate return_type, response.to_hash
210
- end
203
+ def get_base_url(base_url)
204
+ base_url || "https://" + DEFAULT_SERVER_HOST + "/v" + DEFAULT_API_VERSION.to_s
205
+ end
211
206
 
212
- def get_base_url(*base_url)
213
- (!base_url.empty? and !base_url[0].nil?) ?
214
- base_url[0] :
215
- "https://" + DEFAULT_SERVER_HOST + "/v" + DEFAULT_API_VERSION.to_s
216
- end
207
+ def to_hash(resource)
208
+ Hash.new.tap do |properties|
209
+ resource.get_dirty_property_names.each do |name|
210
+ property = resource.get_property name
217
211
 
218
- def to_hash(resource)
219
- Hash.new.tap do |properties|
220
- resource.get_property_names.each do |name|
221
- property = resource.get_property name
212
+ # Special use case is with Custom Data, it's hashes should not be simplified
213
+ if property.kind_of?(Hash) and resource_not_custom_data resource, name
214
+ property = to_simple_reference name, property
215
+ end
222
216
 
223
- if property.kind_of? Hash
224
- property = to_simple_reference name, property
217
+ properties.store name, property
225
218
  end
226
-
227
- properties.store name, property
228
219
  end
229
220
  end
230
- end
231
221
 
232
- def to_simple_reference(property_name, hash)
233
- href_prop_name = Stormpath::Resource::Base::HREF_PROP_NAME
234
- assert_true(
235
- (hash.kind_of?(Hash) and !hash.empty? and hash.has_key?(href_prop_name)),
236
- "Nested resource '#{property_name}' must have an 'href' property."
237
- )
222
+ def to_simple_reference(property_name, hash)
223
+ assert_true hash.has_key?(HREF_PROP_NAME), "Nested resource '#{property_name}' must have an 'href' property."
238
224
 
239
- href = hash[href_prop_name]
225
+ href = hash[HREF_PROP_NAME]
226
+
227
+ {HREF_PROP_NAME => href}
228
+ end
229
+
230
+ def resource_not_custom_data resource, name
231
+ resource.class != Stormpath::Resource::CustomData and name != "customData"
232
+ end
240
233
 
241
- {href_prop_name => href}
242
- end
243
234
  end