stormpath-sdk 0.4.0 → 1.0.0.beta

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