stormpath-sdk 0.1.0

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