stormpath-sdk 0.1.0

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 (47) hide show
  1. data/Gemfile +4 -0
  2. data/README.md +24 -0
  3. data/Rakefile +16 -0
  4. data/lib/stormpath-sdk.rb +49 -0
  5. data/lib/stormpath-sdk/auth/authentication_result.rb +17 -0
  6. data/lib/stormpath-sdk/auth/basic_authenticator.rb +42 -0
  7. data/lib/stormpath-sdk/auth/basic_login_attempt.rb +30 -0
  8. data/lib/stormpath-sdk/auth/username_password_request.rb +43 -0
  9. data/lib/stormpath-sdk/client/api_key.rb +18 -0
  10. data/lib/stormpath-sdk/client/client.rb +23 -0
  11. data/lib/stormpath-sdk/client/client_builder.rb +291 -0
  12. data/lib/stormpath-sdk/ds/data_store.rb +150 -0
  13. data/lib/stormpath-sdk/ds/resource_factory.rb +22 -0
  14. data/lib/stormpath-sdk/http/authc/sauthc1_signer.rb +216 -0
  15. data/lib/stormpath-sdk/http/http_client_request_executor.rb +69 -0
  16. data/lib/stormpath-sdk/http/request.rb +83 -0
  17. data/lib/stormpath-sdk/http/response.rb +35 -0
  18. data/lib/stormpath-sdk/resource/account.rb +110 -0
  19. data/lib/stormpath-sdk/resource/account_list.rb +17 -0
  20. data/lib/stormpath-sdk/resource/application.rb +95 -0
  21. data/lib/stormpath-sdk/resource/application_list.rb +17 -0
  22. data/lib/stormpath-sdk/resource/collection_resource.rb +76 -0
  23. data/lib/stormpath-sdk/resource/directory.rb +76 -0
  24. data/lib/stormpath-sdk/resource/directory_list.rb +15 -0
  25. data/lib/stormpath-sdk/resource/email_verification_token.rb +11 -0
  26. data/lib/stormpath-sdk/resource/error.rb +42 -0
  27. data/lib/stormpath-sdk/resource/group.rb +73 -0
  28. data/lib/stormpath-sdk/resource/group_list.rb +18 -0
  29. data/lib/stormpath-sdk/resource/group_membership.rb +55 -0
  30. data/lib/stormpath-sdk/resource/group_membership_list.rb +17 -0
  31. data/lib/stormpath-sdk/resource/instance_resource.rb +13 -0
  32. data/lib/stormpath-sdk/resource/password_reset_token.rb +27 -0
  33. data/lib/stormpath-sdk/resource/resource.rb +173 -0
  34. data/lib/stormpath-sdk/resource/resource_error.rb +32 -0
  35. data/lib/stormpath-sdk/resource/status.rb +19 -0
  36. data/lib/stormpath-sdk/resource/tenant.rb +55 -0
  37. data/lib/stormpath-sdk/resource/utils.rb +16 -0
  38. data/lib/stormpath-sdk/util/assert.rb +26 -0
  39. data/lib/stormpath-sdk/util/hash.rb +17 -0
  40. data/lib/stormpath-sdk/util/request_utils.rb +57 -0
  41. data/lib/stormpath-sdk/version.rb +4 -0
  42. data/stormpath-sdk.gemspec +29 -0
  43. data/test/client/client.yml +16 -0
  44. data/test/client/clientbuilder_spec.rb +176 -0
  45. data/test/client/read_spec.rb +243 -0
  46. data/test/client/write_spec.rb +315 -0
  47. metadata +226 -0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
data/README.md ADDED
@@ -0,0 +1,24 @@
1
+ Stormpath SDK For Ruby
2
+ Copyright (c) 2012 Stormpath, Inc. and contributors.
3
+
4
+ This project is licensed under the Apache 2.0 Open Source License:
5
+
6
+ http://www.apache.org/licenses/LICENSE-2.0
7
+
8
+ Project Documentation is here:
9
+
10
+ https://github.com/stormpath/stormpath-sdk-ruby/wiki
11
+
12
+ # Build Instructions
13
+
14
+ Via rubygems.org:
15
+
16
+ $ gem install stormpath-sdk
17
+ To build and install the development branch yourself from the latest source:
18
+
19
+ ```
20
+ $ git clone git@github.com:stormpath/stormpath-sdk-ruby.git
21
+ $ cd stormpath-sdk-ruby
22
+ $ rake gem
23
+ $ gem install pkg/stormpath-sdk-{version}
24
+ ```
data/Rakefile ADDED
@@ -0,0 +1,16 @@
1
+ require 'rubygems'
2
+ require 'rubygems/package_task'
3
+ require 'rspec/core/rake_task'
4
+
5
+ spec = eval(File.read('stormpath-sdk.gemspec'))
6
+
7
+ Gem::PackageTask.new(spec) do |p|
8
+ p.gem_spec = spec
9
+ end
10
+
11
+ RSpec::Core::RakeTask.new do |t|
12
+ t.pattern = '**/*_spec.rb'
13
+ t.rspec_opts = ['-c']
14
+ end
15
+
16
+ task :default => :spec
@@ -0,0 +1,49 @@
1
+ require "base64"
2
+ require "httpclient"
3
+ require "multi_json"
4
+ require "openssl"
5
+ require "open-uri"
6
+ require "uri"
7
+ require "uuidtools"
8
+ require "yaml"
9
+
10
+ require "stormpath-sdk/version" unless defined? Stormpath::VERSION
11
+ require "stormpath-sdk/util/assert"
12
+ require "stormpath-sdk/client/api_key"
13
+ require "stormpath-sdk/client/client"
14
+ require "stormpath-sdk/util/hash"
15
+ require "stormpath-sdk/client/client_builder"
16
+ require "stormpath-sdk/auth/username_password_request"
17
+ require "stormpath-sdk/resource/status"
18
+ require "stormpath-sdk/resource/utils"
19
+ require "stormpath-sdk/util/request_utils"
20
+ require "stormpath-sdk/resource/resource"
21
+ require "stormpath-sdk/resource/collection_resource"
22
+ require "stormpath-sdk/resource/instance_resource"
23
+ require "stormpath-sdk/resource/resource"
24
+ require "stormpath-sdk/resource/error"
25
+ require "stormpath-sdk/resource/resource_error"
26
+ require "stormpath-sdk/resource/error"
27
+ require "stormpath-sdk/resource/tenant"
28
+ require "stormpath-sdk/resource/directory"
29
+ require "stormpath-sdk/resource/tenant"
30
+ require "stormpath-sdk/resource/email_verification_token"
31
+ require "stormpath-sdk/resource/group_list"
32
+ require "stormpath-sdk/resource/account"
33
+ require "stormpath-sdk/resource/account_list"
34
+ require "stormpath-sdk/resource/password_reset_token"
35
+ require "stormpath-sdk/resource/application"
36
+ require "stormpath-sdk/resource/group"
37
+ require "stormpath-sdk/resource/application_list"
38
+ require "stormpath-sdk/resource/directory_list"
39
+ require "stormpath-sdk/resource/group_membership"
40
+ require "stormpath-sdk/resource/group_membership_list"
41
+ require "stormpath-sdk/http/request"
42
+ require "stormpath-sdk/http/response"
43
+ require "stormpath-sdk/http/authc/sauthc1_signer"
44
+ require "stormpath-sdk/http/http_client_request_executor"
45
+ require "stormpath-sdk/auth/basic_login_attempt"
46
+ require "stormpath-sdk/auth/authentication_result"
47
+ require "stormpath-sdk/auth/basic_authenticator"
48
+ require "stormpath-sdk/ds/resource_factory"
49
+ require "stormpath-sdk/ds/data_store"
@@ -0,0 +1,17 @@
1
+ module Stormpath
2
+
3
+ module Authentication
4
+
5
+ class AuthenticationResult < Stormpath::Resource::Resource
6
+
7
+ ACCOUNT = "account"
8
+
9
+ def get_account
10
+ get_resource_property ACCOUNT, Account
11
+ end
12
+
13
+ end
14
+
15
+ end
16
+
17
+ end
@@ -0,0 +1,42 @@
1
+ module Stormpath
2
+
3
+ module Authentication
4
+
5
+ class BasicAuthenticator
6
+
7
+ include Stormpath::Util::Assert
8
+
9
+ def initialize data_store
10
+ @data_store = data_store
11
+ end
12
+
13
+ def authenticate parent_href, request
14
+
15
+ assert_not_nil parent_href, "parentHref argument must be specified"
16
+ assert_kind_of UsernamePasswordRequest, request, "Only UsernamePasswordRequest instances are supported."
17
+
18
+ username = request.get_principals
19
+ username = (username != nil) ? username : ''
20
+
21
+ password = request.get_credentials
22
+ pw_string = password.to_s
23
+
24
+ value = username + ':' + pw_string
25
+
26
+ value = Base64.encode64(value).tr("\n", '')
27
+
28
+ attempt = @data_store.instantiate BasicLoginAttempt, nil
29
+ attempt.set_type 'basic'
30
+ attempt.set_value value
31
+
32
+ href = parent_href + '/loginAttempts'
33
+ result = @data_store.create href, attempt, AuthenticationResult
34
+ result.get_account
35
+
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,30 @@
1
+ module Stormpath
2
+
3
+ module Authentication
4
+
5
+ class BasicLoginAttempt < Stormpath::Resource::Resource
6
+
7
+ TYPE = "type"
8
+ VALUE = "value"
9
+
10
+ def get_type
11
+ get_property TYPE
12
+ end
13
+
14
+ def set_type type
15
+ set_property TYPE, type
16
+ end
17
+
18
+ def get_value
19
+ get_property VALUE
20
+ end
21
+
22
+ def set_value value
23
+ set_property VALUE, value
24
+ end
25
+
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,43 @@
1
+ module Stormpath
2
+
3
+ module Authentication
4
+
5
+ class UsernamePasswordRequest
6
+
7
+ attr_reader :host
8
+
9
+ def initialize username, password, host
10
+ @username = username
11
+ @password = (password != nil and password.length > 0) ? password.chars.to_a : "".chars.to_a
12
+ @host = host
13
+ end
14
+
15
+ def get_principals
16
+ @username
17
+ end
18
+
19
+ def get_credentials
20
+ @password
21
+ end
22
+
23
+ ##
24
+ # Clears out (nulls) the username, password, and host. The password bytes are explicitly set to
25
+ # <tt>0x00</tt> to eliminate the possibility of memory access at a later time.
26
+ def clear
27
+ @username = nil
28
+ @host = nil
29
+
30
+ @password.each { |pass_char|
31
+
32
+ pass_char = 0x00
33
+ }
34
+
35
+ @password = nil
36
+ end
37
+
38
+ end
39
+
40
+ end
41
+
42
+ end
43
+
@@ -0,0 +1,18 @@
1
+ module Stormpath
2
+
3
+ module Client
4
+
5
+ class ApiKey
6
+
7
+ attr_accessor :id, :secret
8
+
9
+ def initialize(id, secret)
10
+ @id = id
11
+ @secret = secret
12
+ end
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+
@@ -0,0 +1,23 @@
1
+ module Stormpath
2
+
3
+ module Client
4
+
5
+ class Client
6
+
7
+ attr_reader :data_store
8
+
9
+ def initialize(api_key, *base_url)
10
+ request_executor = Stormpath::Http::HttpClientRequestExecutor.new(api_key)
11
+ @data_store = Stormpath::DataStore::DataStore.new(request_executor, *base_url)
12
+ end
13
+
14
+
15
+ def current_tenant
16
+ @data_store.get_resource("/tenants/current", Stormpath::Resource::Tenant)
17
+ end
18
+ end
19
+ end
20
+
21
+
22
+ end
23
+
@@ -0,0 +1,291 @@
1
+ module Stormpath
2
+
3
+ module Client
4
+
5
+ # A <a href="http://en.wikipedia.org/wiki/Builder_pattern">Builder design pattern</a> implementation used to
6
+ # construct {@link Client} instances.
7
+ # <p/>
8
+ # The {@code ClientBuilder} is especially useful for constructing Client instances with Stormpath API Key
9
+ # information loaded from an external {@code .yml} file (or YAML instance) to ensure the API Key secret
10
+ # (password) does not reside in plaintext in code.
11
+ # <p/>
12
+ # Example usage:
13
+ # <pre>
14
+ # String location = "/home/jsmith/.stormpath/apiKey.yml";
15
+ #
16
+ # client = ClientBuilder.new.set_api_key_file_location(location).build()
17
+ # </pre>
18
+ # <p/>
19
+ # You may load files from the filesystem or URLs by specifying the file location.
20
+ # See {@link #set_api_key_file_location(location)} for more information.
21
+ #
22
+ # @see #set_api_key_file_location(location)
23
+ class ClientBuilder
24
+
25
+ include Stormpath::Client
26
+ include Stormpath::Util::Assert
27
+
28
+ def initialize
29
+ @api_key_id_property_name = "apiKey.id"
30
+ @api_key_secret_property_name = "apiKey.secret"
31
+ end
32
+
33
+ # Allows usage of a YAML loadable object (IO object or the result of invoking Object.to_yaml)
34
+ # instead of loading a YAML file via {@link #set_api_key_file_location apiKeyFileLocation} configuration.
35
+ # <p/>
36
+ # The YAML contents and property name overrides function the same as described in the
37
+ # {@link #set_api_key_file_location setApiKeyFileLocation} RDoc.
38
+ #
39
+ # @param properties the YAML object to use to load the API Key ID and Secret.
40
+ # @return the ClientBuilder instance for method chaining.
41
+ def set_api_key_properties properties
42
+
43
+ @api_key_properties = properties
44
+ self
45
+
46
+ end
47
+
48
+ # Creates an API Key YAML object based on the specified File instead of loading a YAML
49
+ # file via {@link #set_api_key_file_location apiKeyFileLocation} configuration. This file argument
50
+ # needs to be an IO instance.
51
+ # <p/>
52
+ # The constructed YAML contents and property name overrides function the same as described in the
53
+ # {@link #set_api_key_file_location setApiKeyFileLocation} RDoc.
54
+ # @param file the file to use to construct a YAML object.
55
+ # @return the ClientBuilder instance for method chaining.
56
+ def set_api_key_file file
57
+ assert_kind_of IO, file, 'file must be kind of IO'
58
+ @api_key_file = file
59
+ self
60
+ end
61
+
62
+
63
+ # Sets the location of the YAML file to load containing the API Key (Id and secret) used by the
64
+ # Client to communicate with the Stormpath REST API.
65
+ # <p/>
66
+ # You may load files from the filesystem, or URLs just specifying the file location.
67
+ # <h3>File Contents</h3>
68
+ # <p/>
69
+ # When the file is loaded, the following name/value pairs are expected to be present by default:
70
+ # <table>
71
+ # <tr>
72
+ # <th>Key</th>
73
+ # <th>Value</th>
74
+ # </tr>
75
+ # <tr>
76
+ # <td>apiKey.id</td>
77
+ # <td>An individual account's API Key ID</td>
78
+ # </tr>
79
+ # <tr>
80
+ # <td>apiKey.secret</td>
81
+ # <td>The API Key Secret (password) that verifies the paired API Key ID.</td>
82
+ # </tr>
83
+ # </table>
84
+ # <p/>
85
+ # Assuming you were using these default property names, your {@code ClientBuilder} usage might look like the
86
+ # following:
87
+ # <pre>
88
+ # String location = "/home/jsmith/.stormpath/apiKey.yml";
89
+ #
90
+ # client = ClientBuilder.new.set_api_key_file_location(location).build()
91
+ # </pre>
92
+ # <h3>Custom Property Names</h3>
93
+ # If you want to control the property names used in the file, you may configure them via
94
+ # {@link #set_api_key_id_property_name(String) set_api_key_id_property_name} and
95
+ # {@link #set_api_key_secret_property_name(String) set_api_key_secret_property_name}.
96
+ # <p/>
97
+ # For example, if you had a {@code /home/jsmith/.stormpath/apiKey.properties} file with the following
98
+ # name/value pairs:
99
+ # <pre>
100
+ # myStormpathApiKeyId = 'foo'
101
+ # myStormpathApiKeySecret = 'mySuperSecretValue'
102
+ # </pre>
103
+ # Your {@code ClientBuilder} usage would look like the following:
104
+ # <pre>
105
+ # location = "/home/jsmith/.stormpath/apiKey.yml";
106
+ #
107
+ # client =
108
+ # ClientBuilder.new.
109
+ # set_api_key_file_location(location).
110
+ # set_api_key_id_property_name("myStormpathApiKeyId").
111
+ # set_api_key_secret_property_name("myStormpathApiKeySecret").
112
+ # build
113
+ # </pre>
114
+ #
115
+ # @param location the file or url location of the API Key {@code .yml} file to load when
116
+ # constructing the API Key to use for communicating with the Stormpath REST API.
117
+ #
118
+ # @return the ClientBuilder instance for method chaining.
119
+ #/
120
+ def set_api_key_file_location location
121
+ assert_kind_of String, location, 'location must be kind of String'
122
+ @api_key_file_location = location
123
+ self
124
+ end
125
+
126
+
127
+ # Sets the name used to query for the API Key ID from a YAML instance. That is:
128
+ # <pre>
129
+ # apiKeyId = yml.access(<b>api_key_id_property_name</b>)
130
+ # </pre>
131
+ #
132
+ # The Hash#access method searches through the provided path and returns the found value.
133
+ #
134
+ # The <b>api_key_id_property_name</b> key can be as deep as needed, as long as it comes
135
+ # in the exact path order.
136
+ # Example: Having the file 'apiKey.yml' with the following content:
137
+ #
138
+ # stormpath:
139
+ # apiKey:
140
+ # id: myStormpathApiKeyId
141
+ #
142
+ # The method should be called as follows:
143
+ #
144
+ # ClientBuilder#set_api_key_id_property_name('stormpath', 'apiKey', 'id')
145
+ #
146
+ # @param api_key_id_property_name the name used to query for the API Key ID from a YAML instance.
147
+ # @return the ClientBuilder instance for method chaining.
148
+ def set_api_key_id_property_name *api_key_id_property_name
149
+
150
+ @api_key_id_property_name = *api_key_id_property_name
151
+ self
152
+
153
+ end
154
+
155
+
156
+ # Sets the name used to query for the API Key Secret from a YAML instance. That is:
157
+ # <pre>
158
+ # apiKeyId = yml.access(<b>api_key_secret_property_name</b>)
159
+ # </pre>
160
+ #
161
+ # The Hash#access method searches through the provided path and returns the found value.
162
+ #
163
+ # The <b>api_key_secret_property_name</b> key can be as deep as needed, as long as it comes
164
+ # in the exact path order.
165
+ # Example: Having the file 'apiKey.yml' with the following content:
166
+ #
167
+ # stormpath:
168
+ # apiKey:
169
+ # secret: myStormpathApiKeyId
170
+ #
171
+ # The method should be called as follows:
172
+ #
173
+ # ClientBuilder#set_api_key_id_property_name('stormpath', 'apiKey', 'secret')
174
+ #
175
+ # @param api_key_secret_property_name the name used to query for the API Key Secret from a YAML instance.
176
+ # @return the ClientBuilder instance for method chaining.
177
+ def set_api_key_secret_property_name *api_key_secret_property_name
178
+
179
+ @api_key_secret_property_name = *api_key_secret_property_name
180
+ self
181
+
182
+ end
183
+
184
+ # Constructs a new {@link Client} instance based on the ClientBuilder's current configuration state.
185
+ #
186
+ # @return a new {@link Client} instance based on the ClientBuilder's current configuration state.
187
+ #
188
+ def build
189
+
190
+ if @api_key_properties.nil? or (@api_key_properties.respond_to? 'empty?' and @api_key_properties.empty?)
191
+
192
+
193
+ #need to load the properties file
194
+
195
+ file = get_available_file
196
+
197
+ if file.nil?
198
+ raise ArgumentError, "No API Key file could be found or loaded from a file location. Please " +
199
+ "configure the 'apiKeyFileLocation' property or alternatively configure a " +
200
+ "YAML loadable instance."
201
+ end
202
+
203
+ yaml_obj = YAML::load(file)
204
+
205
+ else
206
+
207
+ yaml_obj = YAML::load(@api_key_properties)
208
+
209
+ end
210
+
211
+ api_key_id = get_required_property_value yaml_obj, 'api_key_id', @api_key_id_property_name
212
+
213
+ api_key_secret = get_required_property_value yaml_obj, 'api_key_secret', @api_key_secret_property_name
214
+
215
+ assert_not_nil api_key_id, 'api_key_id must not be nil when acquiring it from the YAML object'
216
+ assert_not_nil api_key_secret, 'api_key_secret must not be nil when acquiring it from the YAML object'
217
+
218
+ api_key = ApiKey.new api_key_id, api_key_secret
219
+
220
+ Client.new api_key, @base_url
221
+
222
+ end
223
+
224
+ def set_base_url base_url
225
+ @base_url = base_url
226
+ self
227
+ end
228
+
229
+ private
230
+
231
+
232
+ def get_property_value yml, prop_name, separator
233
+
234
+ value = yml.access(prop_name, separator)
235
+
236
+ if !value.nil?
237
+
238
+ if value.kind_of? String
239
+
240
+ value = value.strip
241
+
242
+ end
243
+
244
+ if value.empty?
245
+ value = nil
246
+ end
247
+
248
+ end
249
+
250
+ value
251
+
252
+ end
253
+
254
+ def get_required_property_value yml, masterName, *prop_name
255
+
256
+
257
+ prop_name_value = prop_name[0]
258
+
259
+ separator = '--YAMLKeysSeparator--'
260
+ value = get_property_value(yml, prop_name_value.respond_to?('join') ?
261
+ prop_name[0].join(separator) :
262
+ prop_name_value,
263
+ separator)
264
+
265
+ if value.nil?
266
+
267
+ raise ArgumentError, "There is no '" + prop_name.join(':') + "' property in the " +
268
+ "configured apiKey YAML. You can either specify that property or " +
269
+ "configure the " + masterName + "PropertyName value on the ClientBuilder to specify a " +
270
+ "custom property name."
271
+ end
272
+
273
+ value
274
+
275
+ end
276
+
277
+ def get_available_file
278
+
279
+ if @api_key_file
280
+ return @api_key_file
281
+ end
282
+
283
+ open(@api_key_file_location) { |f| f.read }
284
+
285
+ end
286
+
287
+ end
288
+
289
+ end
290
+
291
+ end