stormpath-sdk 0.4.0 → 1.0.0.beta

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 (89) hide show
  1. data/.gitignore +6 -0
  2. data/.ruby-gemset +1 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +27 -0
  5. data/CHANGES.md +21 -1
  6. data/Gemfile +1 -2
  7. data/README.md +457 -11
  8. data/Rakefile +15 -1
  9. data/lib/stormpath-sdk.rb +52 -33
  10. data/lib/stormpath-sdk/{resource/group_list.rb → api_key.rb} +5 -9
  11. data/lib/stormpath-sdk/auth/authentication_result.rb +3 -13
  12. data/lib/stormpath-sdk/auth/basic_authenticator.rb +5 -11
  13. data/lib/stormpath-sdk/auth/basic_login_attempt.rb +6 -8
  14. data/lib/stormpath-sdk/auth/username_password_request.rb +2 -5
  15. data/lib/stormpath-sdk/cache/cache.rb +54 -0
  16. data/lib/stormpath-sdk/cache/cache_entry.rb +33 -0
  17. data/lib/stormpath-sdk/cache/cache_manager.rb +22 -0
  18. data/lib/stormpath-sdk/cache/cache_stats.rb +35 -0
  19. data/lib/stormpath-sdk/cache/memory_store.rb +29 -0
  20. data/lib/stormpath-sdk/cache/redis_store.rb +32 -0
  21. data/lib/stormpath-sdk/client.rb +111 -0
  22. data/lib/stormpath-sdk/data_store.rb +241 -0
  23. data/lib/stormpath-sdk/{client/api_key.rb → error.rb} +16 -10
  24. data/lib/stormpath-sdk/{util → ext}/hash.rb +1 -2
  25. data/lib/stormpath-sdk/http/authc/sauthc1_signer.rb +8 -4
  26. data/lib/stormpath-sdk/http/http_client_request_executor.rb +8 -7
  27. data/lib/stormpath-sdk/http/request.rb +4 -8
  28. data/lib/stormpath-sdk/{util/request_utils.rb → http/utils.rb} +17 -38
  29. data/lib/stormpath-sdk/resource/account.rb +12 -108
  30. data/lib/stormpath-sdk/resource/application.rb +35 -171
  31. data/lib/stormpath-sdk/resource/associations.rb +97 -0
  32. data/lib/stormpath-sdk/resource/base.rb +256 -0
  33. data/lib/stormpath-sdk/resource/collection.rb +94 -0
  34. data/lib/stormpath-sdk/resource/directory.rb +11 -68
  35. data/lib/stormpath-sdk/resource/email_verification_token.rb +3 -9
  36. data/lib/stormpath-sdk/resource/error.rb +4 -38
  37. data/lib/stormpath-sdk/resource/expansion.rb +28 -0
  38. data/lib/stormpath-sdk/resource/group.rb +8 -66
  39. data/lib/stormpath-sdk/resource/group_membership.rb +4 -55
  40. data/lib/stormpath-sdk/resource/{application_list.rb → instance.rb} +7 -13
  41. data/lib/stormpath-sdk/resource/password_reset_token.rb +5 -23
  42. data/lib/stormpath-sdk/resource/status.rb +22 -28
  43. data/lib/stormpath-sdk/resource/tenant.rb +5 -52
  44. data/lib/stormpath-sdk/resource/utils.rb +43 -13
  45. data/lib/stormpath-sdk/util/assert.rb +5 -15
  46. data/lib/stormpath-sdk/version.rb +3 -3
  47. data/spec/api_key_spec.rb +19 -0
  48. data/spec/auth/basic_authenticator_spec.rb +25 -0
  49. data/spec/auth/sauthc1_signer_spec.rb +42 -0
  50. data/spec/cache/cache_entry_spec.rb +157 -0
  51. data/spec/cache/cache_spec.rb +89 -0
  52. data/spec/cache/cache_stats_spec.rb +106 -0
  53. data/spec/client_spec.rb +538 -0
  54. data/spec/data_store_spec.rb +130 -0
  55. data/spec/resource/account_spec.rb +74 -0
  56. data/spec/resource/application_spec.rb +148 -0
  57. data/spec/resource/base_spec.rb +114 -0
  58. data/spec/resource/collection_spec.rb +169 -0
  59. data/spec/resource/directory_spec.rb +30 -0
  60. data/spec/resource/expansion_spec.rb +100 -0
  61. data/spec/resource/group_spec.rb +49 -0
  62. data/spec/spec_helper.rb +135 -0
  63. data/spec/support/resource_factory.rb +48 -0
  64. data/spec/support/resource_matchers.rb +27 -0
  65. data/spec/support/test_cache_stores.rb +9 -0
  66. data/spec/support/test_request_executor.rb +11 -0
  67. data/stormpath-sdk.gemspec +14 -4
  68. data/support/api.rb +55 -0
  69. metadata +214 -44
  70. data/lib/stormpath-sdk/client/client.rb +0 -38
  71. data/lib/stormpath-sdk/client/client_application.rb +0 -38
  72. data/lib/stormpath-sdk/client/client_application_builder.rb +0 -351
  73. data/lib/stormpath-sdk/client/client_builder.rb +0 -305
  74. data/lib/stormpath-sdk/ds/data_store.rb +0 -210
  75. data/lib/stormpath-sdk/ds/resource_factory.rb +0 -37
  76. data/lib/stormpath-sdk/resource/account_list.rb +0 -32
  77. data/lib/stormpath-sdk/resource/collection_resource.rb +0 -91
  78. data/lib/stormpath-sdk/resource/directory_list.rb +0 -30
  79. data/lib/stormpath-sdk/resource/group_membership_list.rb +0 -32
  80. data/lib/stormpath-sdk/resource/instance_resource.rb +0 -28
  81. data/lib/stormpath-sdk/resource/resource.rb +0 -327
  82. data/lib/stormpath-sdk/resource/resource_error.rb +0 -47
  83. data/test/client/client.yml +0 -16
  84. data/test/client/client_application_builder_spec.rb +0 -114
  85. data/test/client/client_builder_spec.rb +0 -176
  86. data/test/client/read_spec.rb +0 -254
  87. data/test/client/write_spec.rb +0 -420
  88. data/test/resource/resource_spec.rb +0 -41
  89. data/test/resource/test_resource.rb +0 -28
@@ -0,0 +1,32 @@
1
+ require 'redis'
2
+
3
+ module Stormpath
4
+ module Cache
5
+ class RedisStore
6
+ def initialize(opts = {})
7
+ @redis = Redis.new opts
8
+ end
9
+
10
+ def get(key)
11
+ entry = @redis.get key
12
+ entry && Stormpath::Cache::CacheEntry.from_h(MultiJson.load(entry))
13
+ end
14
+
15
+ def put(key, entry)
16
+ @redis.set key, MultiJson.dump(entry.to_h)
17
+ end
18
+
19
+ def delete(key)
20
+ @redis.del key
21
+ end
22
+
23
+ def clear
24
+ @redis.flushdb
25
+ end
26
+
27
+ def size
28
+ @redis.dbsize
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,111 @@
1
+ #
2
+ # Copyright 2012 Stormpath, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ require 'java_properties'
17
+
18
+ module Stormpath
19
+
20
+ class Client
21
+ include Stormpath::Util::Assert
22
+
23
+ attr_reader :data_store, :application
24
+
25
+ def initialize(options)
26
+ api_key = options[:api_key]
27
+ base_url = options[:base_url]
28
+ cache_opts = options[:cache] || {}
29
+
30
+ api_key = if api_key
31
+ case api_key
32
+ when ApiKey then api_key
33
+ when Hash then ApiKey.new api_key[:id], api_key[:secret]
34
+ end
35
+ elsif options[:api_key_file_location]
36
+ load_api_key_file options[:api_key_file_location],
37
+ options[:api_key_id_property_name],
38
+ options[:api_key_secret_property_name]
39
+ end
40
+
41
+ assert_not_nil api_key, "No API key has been provided. Please " +
42
+ "pass an 'api_key' or 'api_key_file_location' to the " +
43
+ "Stormpath::Client constructor."
44
+
45
+ request_executor = Stormpath::Http::HttpClientRequestExecutor.new(api_key, proxy: options[:proxy])
46
+ @data_store = Stormpath::DataStore.new(request_executor, cache_opts, self, base_url)
47
+ end
48
+
49
+ def tenant
50
+ Stormpath::Resource::Tenant.new '/tenants/current', self
51
+ end
52
+
53
+ def client
54
+ self
55
+ end
56
+
57
+ def cache_stats
58
+ @data_source.cache_stats
59
+ end
60
+
61
+ include Stormpath::Resource::Associations
62
+
63
+ has_many :applications, href: '/applications', can: [:get, :create], delegate: true
64
+ has_many :directories, href: '/directories', can: [:get, :create], delegate: true
65
+ has_many(:accounts, href: '/accounts', can: :get) do
66
+ def verify_email_token(token)
67
+ token_href = "#{href}/emailVerificationTokens/#{token}"
68
+ token = Stormpath::Resource::EmailVerificationToken.new(
69
+ token_href,
70
+ client
71
+ )
72
+ data_store.save token, Stormpath::Resource::Account
73
+ end
74
+ end
75
+ has_many :groups, href: '/groups', can: :get
76
+ has_many :group_memberships, href: '/groupMemberships', can: [:get, :create]
77
+
78
+ private
79
+
80
+ def load_api_key_file(api_key_file_location, id_property_name, secret_property_name)
81
+ begin
82
+ api_key_properties = JavaProperties::Properties.new api_key_file_location
83
+ rescue
84
+ raise ArgumentError,
85
+ "No API Key file could be found or loaded from '" +
86
+ api_key_file_location +
87
+ "'."
88
+ end
89
+
90
+ id_property_name ||= 'apiKey.id'
91
+ secret_property_name ||= 'apiKey.secret'
92
+
93
+ api_key_id = api_key_properties[id_property_name]
94
+ assert_not_nil api_key_id,
95
+ "No API id in properties. Please provide a 'apiKey.id' property in '" +
96
+ api_key_file_location +
97
+ "' or pass in an 'api_key_id_property_name' to the Stormpath::Client " +
98
+ "constructor to specify an alternative property."
99
+
100
+ api_key_secret = api_key_properties[secret_property_name]
101
+ assert_not_nil api_key_secret,
102
+ "No API secret in properties. Please provide a 'apiKey.secret' property in '" +
103
+ api_key_file_location +
104
+ "' or pass in an 'api_key_secret_property_name' to the Stormpath::Client " +
105
+ "constructor to specify an alternative property."
106
+
107
+ ApiKey.new api_key_id, api_key_secret
108
+ end
109
+
110
+ end
111
+ end
@@ -0,0 +1,241 @@
1
+ #
2
+ # Copyright 2012 Stormpath, Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+ class Stormpath::DataStore
17
+ include Stormpath::Http
18
+ include Stormpath::Util::Assert
19
+
20
+ DEFAULT_SERVER_HOST = "api.stormpath.com"
21
+ DEFAULT_API_VERSION = 1
22
+
23
+ CACHE_REGIONS = %w( applications directories accounts groups groupMemberships tenants )
24
+
25
+ attr_reader :client, :request_executor
26
+
27
+ def initialize(request_executor, cache_opts, client, *base_url)
28
+ assert_not_nil request_executor, "RequestExecutor cannot be null."
29
+
30
+ @client = client
31
+ @base_url = get_base_url(*base_url)
32
+ @request_executor = request_executor
33
+ initialize_cache cache_opts
34
+ end
35
+
36
+ def initialize_cache(cache_opts)
37
+ @cache_manager = Stormpath::Cache::CacheManager.new
38
+ regions_opts = cache_opts[:regions] || {}
39
+ CACHE_REGIONS.each do |region|
40
+ region_opts = regions_opts[region.to_sym] || {}
41
+ region_opts[:store] ||= cache_opts[:store]
42
+ @cache_manager.create_cache region, region_opts
43
+ end
44
+ end
45
+
46
+ def instantiate(clazz, properties = {})
47
+ clazz.new properties, client
48
+ end
49
+
50
+ 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
56
+
57
+ data = execute_request('get', q_href, nil, query)
58
+ instantiate clazz, data.to_hash
59
+ end
60
+
61
+ def create(parent_href, resource, return_type)
62
+ save_resource(parent_href, resource, return_type).tap do |returned_resource|
63
+ if resource.kind_of? return_type
64
+ resource.set_properties to_hash(returned_resource)
65
+ end
66
+ end
67
+ end
68
+
69
+ def save(resource, clazz = nil)
70
+ assert_not_nil resource, "resource argument cannot be null."
71
+ assert_kind_of Stormpath::Resource::Base, resource, "resource argument must be instance of Stormpath::Resource::Base"
72
+
73
+ href = resource.href
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)."
75
+
76
+ href = if needs_to_be_fully_qualified(href)
77
+ qualify(href)
78
+ else
79
+ href
80
+ end
81
+
82
+ clazz ||= resource.class
83
+
84
+ save_resource(href, resource, clazz).tap do |return_value|
85
+ resource.set_properties return_value
86
+ end
87
+ end
88
+
89
+ def delete(resource)
90
+ assert_not_nil resource, "resource argument cannot be null."
91
+ assert_kind_of Stormpath::Resource::Base, resource, "resource argument must be instance of Stormpath::Resource::Base"
92
+
93
+ execute_request('delete', resource.href)
94
+ end
95
+
96
+ def cache_manager
97
+ @cache_manager
98
+ end
99
+
100
+ protected
101
+
102
+ def needs_to_be_fully_qualified(href)
103
+ !href.downcase.start_with? 'http'
104
+ end
105
+
106
+ def qualify(href)
107
+ slash_added = ''
108
+
109
+ if !href.start_with? '/'
110
+ slash_added = '/'
111
+ end
112
+
113
+ @base_url + slash_added + href
114
+ end
115
+
116
+ private
117
+
118
+ def execute_request(http_method, href, body=nil, query=nil)
119
+ if http_method == 'get' && (cache = cache_for href)
120
+ cached_result = cache.get href
121
+ return cached_result if cached_result
122
+ end
123
+
124
+ request = Request.new(http_method, href, query, Hash.new, body)
125
+ apply_default_request_headers request
126
+ response = @request_executor.execute_request request
127
+
128
+ result = response.body.length > 0 ? MultiJson.load(response.body) : ''
129
+
130
+ if response.error?
131
+ error = Stormpath::Resource::Error.new result
132
+ #puts "Error with request: #{http_method.upcase}: #{href}"
133
+ raise Stormpath::Error.new error
134
+ end
135
+
136
+ if http_method == 'delete'
137
+ cache = cache_for href
138
+ cache.delete href if cache
139
+ return nil
140
+ end
141
+
142
+ if result['href']
143
+ cache_walk result
144
+ else
145
+ result
146
+ end
147
+ end
148
+
149
+ def cache_walk(resource)
150
+ assert_not_nil resource['href'], "resource must have 'href' property"
151
+ items = resource['items']
152
+
153
+ if items # collection resource
154
+ resource['items'] = items.map do |item|
155
+ cache_walk item
156
+ { 'href' => item['href'] }
157
+ end
158
+ else # single resource
159
+ resource.each do |attr, value|
160
+ if value.is_a? Hash
161
+ walked = cache_walk value
162
+ resource[attr] = { 'href' => value['href'] }
163
+ resource[attr]['items'] = walked['items'] if walked['items']
164
+ end
165
+ end
166
+ cache resource if resource.length > 1
167
+ end
168
+ resource
169
+ end
170
+
171
+ def cache(resource)
172
+ cache = cache_for resource['href']
173
+ cache.put resource['href'], resource if cache
174
+ end
175
+
176
+ def cache_for(href)
177
+ @cache_manager.get_cache(region_for href)
178
+ end
179
+
180
+ def region_for(href)
181
+ return nil unless href
182
+ region = href.split('/')[-2]
183
+ CACHE_REGIONS.include?(region) ? region : nil
184
+ end
185
+
186
+ def apply_default_request_headers(request)
187
+ request.http_headers.store 'Accept', 'application/json'
188
+ request.http_headers.store 'User-Agent', 'Stormpath-RubySDK/' + Stormpath::VERSION
189
+
190
+ if !request.body.nil? and request.body.length > 0
191
+ request.http_headers.store 'Content-Type', 'application/json'
192
+ end
193
+ end
194
+
195
+ def save_resource(href, resource, return_type)
196
+ assert_not_nil resource, "resource argument cannot be null."
197
+ assert_not_nil return_type, "returnType class cannot be null."
198
+ assert_kind_of Stormpath::Resource::Base, resource, "resource argument must be instance of Stormpath::Resource::Base"
199
+
200
+ q_href = if needs_to_be_fully_qualified href
201
+ qualify href
202
+ else
203
+ href
204
+ end
205
+
206
+ response = execute_request('post', q_href, MultiJson.dump(to_hash(resource)))
207
+ instantiate return_type, response.to_hash
208
+ end
209
+
210
+ def get_base_url(*base_url)
211
+ (!base_url.empty? and !base_url[0].nil?) ?
212
+ base_url[0] :
213
+ "https://" + DEFAULT_SERVER_HOST + "/v" + DEFAULT_API_VERSION.to_s
214
+ end
215
+
216
+ def to_hash(resource)
217
+ Hash.new.tap do |properties|
218
+ resource.get_property_names.each do |name|
219
+ property = resource.get_property name
220
+
221
+ if property.kind_of? Hash
222
+ property = to_simple_reference name, property
223
+ end
224
+
225
+ properties.store name, property
226
+ end
227
+ end
228
+ end
229
+
230
+ def to_simple_reference(property_name, hash)
231
+ href_prop_name = Stormpath::Resource::Base::HREF_PROP_NAME
232
+ assert_true(
233
+ (hash.kind_of?(Hash) and !hash.empty? and hash.has_key?(href_prop_name)),
234
+ "Nested resource '#{property_name}' must have an 'href' property."
235
+ )
236
+
237
+ href = hash[href_prop_name]
238
+
239
+ {href_prop_name => href}
240
+ end
241
+ end
@@ -13,21 +13,27 @@
13
13
  # See the License for the specific language governing permissions and
14
14
  # limitations under the License.
15
15
  #
16
- module Stormpath
16
+ class Stormpath::Error < RuntimeError
17
17
 
18
- module Client
18
+ def initialize error = nil
19
+ super !error.nil? ? error.message : ''
20
+ @error = error
21
+ end
19
22
 
20
- class ApiKey
23
+ def status
24
+ !@error.nil? ? @error.status : -1
25
+ end
21
26
 
22
- attr_accessor :id, :secret
27
+ def code
28
+ !@error.nil? ? @error.code : -1
29
+ end
23
30
 
24
- def initialize(id, secret)
25
- @id = id
26
- @secret = secret
27
- end
28
- end
31
+ def developer_message
32
+ !@error.nil? ? @error.developer_message : nil
33
+ end
29
34
 
35
+ def more_info
36
+ !@error.nil? ? @error.more_info : nil
30
37
  end
31
38
 
32
39
  end
33
-
@@ -15,8 +15,6 @@
15
15
  #
16
16
  class Hash
17
17
 
18
- # implementation borrowed from the vine project at
19
- # https://github.com/guangnan/vine/blob/master/lib/vine.rb
20
18
  def access(path, separator)
21
19
  ret = self
22
20
  path.split(separator).each do |p|
@@ -29,4 +27,5 @@ class Hash
29
27
  end
30
28
  ret
31
29
  end
30
+
32
31
  end
@@ -23,7 +23,7 @@ module Stormpath
23
23
 
24
24
  include OpenSSL
25
25
  include UUIDTools
26
- include Stormpath::Util
26
+ include Stormpath::Http::Utils
27
27
 
28
28
  DEFAULT_ALGORITHM = "SHA256"
29
29
  HOST_HEADER = "Host"
@@ -40,20 +40,24 @@ module Stormpath
40
40
  #noinspection RubyConstantNamingConvention
41
41
  NL = "\n"
42
42
 
43
+ def initialize(uuid_generator=UUID.method(:random_create))
44
+ @uuid_generator = uuid_generator
45
+ end
46
+
43
47
  def sign_request request, api_key
44
48
 
45
49
  time = Time.now
46
50
  time_stamp = time.utc.strftime TIMESTAMP_FORMAT
47
51
  date_stamp = time.utc.strftime DATE_FORMAT
48
52
 
49
- nonce = UUID.random_create.to_s
53
+ nonce = @uuid_generator.call.to_s
50
54
 
51
55
  uri = request.resource_uri
52
56
 
53
57
  # SAuthc1 requires that we sign the Host header so we
54
58
  # have to have it in the request by the time we sign.
55
59
  host_header = uri.host
56
- if !RequestUtils.default_port?(uri)
60
+ if !default_port?(uri)
57
61
 
58
62
  host_header << ":" << uri.port.to_s
59
63
  end
@@ -180,7 +184,7 @@ module Stormpath
180
184
  if resource_path.nil? or resource_path.empty?
181
185
  '/'
182
186
  else
183
- RequestUtils.encode_url resource_path, true, true
187
+ encode_url resource_path, true, true
184
188
  end
185
189
  end
186
190