stormpath-sdk 1.6.0 → 1.7.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.
- checksums.yaml +4 -4
- data/.rubocop.yml +21 -0
- data/CHANGES.md +11 -0
- data/README.md +23 -25
- data/lib/stormpath-sdk.rb +11 -2
- data/lib/stormpath-sdk/api_key.rb +0 -1
- data/lib/stormpath-sdk/auth/basic_authenticator.rb +7 -7
- data/lib/stormpath-sdk/auth/basic_login_attempt.rb +7 -11
- data/lib/stormpath-sdk/auth/create_factor.rb +1 -1
- data/lib/stormpath-sdk/auth/register_service_provider.rb +41 -0
- data/lib/stormpath-sdk/auth/username_password_request.rb +3 -5
- data/lib/stormpath-sdk/cache/cache.rb +3 -3
- data/lib/stormpath-sdk/cache/cache_entry.rb +2 -2
- data/lib/stormpath-sdk/cache/cache_manager.rb +3 -4
- data/lib/stormpath-sdk/cache/cache_stats.rb +1 -3
- data/lib/stormpath-sdk/cache/disabled_cache_store.rb +5 -8
- data/lib/stormpath-sdk/cache/memory_store.rb +1 -1
- data/lib/stormpath-sdk/cache/redis_store.rb +4 -4
- data/lib/stormpath-sdk/client.rb +35 -33
- data/lib/stormpath-sdk/data_store.rb +278 -257
- data/lib/stormpath-sdk/error.rb +18 -7
- data/lib/stormpath-sdk/http/authc/sauthc1_signer.rb +76 -82
- data/lib/stormpath-sdk/http/http_client_request_executor.rb +10 -8
- data/lib/stormpath-sdk/http/response.rb +5 -7
- data/lib/stormpath-sdk/id_site/id_site_result.rb +5 -6
- data/lib/stormpath-sdk/oauth/access_token_authentication_result.rb +5 -9
- data/lib/stormpath-sdk/oauth/authenticator.rb +2 -2
- data/lib/stormpath-sdk/oauth/error.rb +4 -4
- data/lib/stormpath-sdk/oauth/id_site_grant_request.rb +1 -1
- data/lib/stormpath-sdk/oauth/password_grant_request.rb +1 -1
- data/lib/stormpath-sdk/oauth/refresh_grant_request.rb +2 -2
- data/lib/stormpath-sdk/oauth/stormpath_grant_request.rb +2 -2
- data/lib/stormpath-sdk/provider/account_access.rb +0 -2
- data/lib/stormpath-sdk/provider/account_result.rb +1 -2
- data/lib/stormpath-sdk/provider/facebook/facebook_provider.rb +6 -2
- data/lib/stormpath-sdk/provider/facebook/facebook_provider_data.rb +7 -3
- data/lib/stormpath-sdk/provider/github/github_provider.rb +6 -2
- data/lib/stormpath-sdk/provider/github/github_provider_data.rb +6 -2
- data/lib/stormpath-sdk/provider/google/google_provider.rb +7 -3
- data/lib/stormpath-sdk/provider/google/google_provider_data.rb +6 -2
- data/lib/stormpath-sdk/provider/linkedin/linkedin_provider.rb +6 -2
- data/lib/stormpath-sdk/provider/linkedin/linkedin_provider_data.rb +6 -2
- data/lib/stormpath-sdk/provider/provider.rb +8 -4
- data/lib/stormpath-sdk/provider/provider_data.rb +6 -2
- data/lib/stormpath-sdk/provider/saml/saml_provider.rb +10 -4
- data/lib/stormpath-sdk/provider/saml/saml_provider_data.rb +6 -3
- data/lib/stormpath-sdk/provider/stormpath/stormpath_provider.rb +6 -2
- data/lib/stormpath-sdk/provider/stormpath/stormpath_provider_data.rb +6 -2
- data/lib/stormpath-sdk/provider/twitter/twitter_provider.rb +6 -2
- data/lib/stormpath-sdk/provider/twitter/twitter_provider_data.rb +6 -2
- data/lib/stormpath-sdk/resource/account.rb +46 -40
- data/lib/stormpath-sdk/resource/account_link.rb +9 -5
- data/lib/stormpath-sdk/resource/account_linking_policy.rb +8 -4
- data/lib/stormpath-sdk/resource/account_membership.rb +1 -1
- data/lib/stormpath-sdk/resource/account_overrides.rb +20 -16
- data/lib/stormpath-sdk/resource/account_store.rb +15 -11
- data/lib/stormpath-sdk/resource/account_store_mapping.rb +14 -13
- data/lib/stormpath-sdk/resource/application.rb +147 -136
- data/lib/stormpath-sdk/resource/application_web_config.rb +11 -7
- data/lib/stormpath-sdk/resource/associations.rb +36 -43
- data/lib/stormpath-sdk/resource/attribute_statement_mapping_rules.rb +8 -0
- data/lib/stormpath-sdk/resource/base.rb +201 -200
- data/lib/stormpath-sdk/resource/challenge.rb +12 -8
- data/lib/stormpath-sdk/resource/collection.rb +77 -76
- data/lib/stormpath-sdk/resource/custom_data.rb +60 -61
- data/lib/stormpath-sdk/resource/custom_data_hash_methods.rb +28 -25
- data/lib/stormpath-sdk/resource/custom_data_storage.rb +18 -16
- data/lib/stormpath-sdk/resource/directory.rb +37 -60
- data/lib/stormpath-sdk/resource/email_verification_token.rb +7 -3
- data/lib/stormpath-sdk/resource/error.rb +8 -4
- data/lib/stormpath-sdk/resource/expansion.rb +22 -20
- data/lib/stormpath-sdk/resource/factor.rb +12 -8
- data/lib/stormpath-sdk/resource/field.rb +8 -4
- data/lib/stormpath-sdk/resource/group.rb +21 -16
- data/lib/stormpath-sdk/resource/group_membership.rb +7 -5
- data/lib/stormpath-sdk/resource/instance.rb +10 -6
- data/lib/stormpath-sdk/resource/linked_account.rb +7 -3
- data/lib/stormpath-sdk/resource/oauth_policy.rb +7 -3
- data/lib/stormpath-sdk/resource/organization.rb +14 -10
- data/lib/stormpath-sdk/resource/organization_account_store_mapping.rb +8 -4
- data/lib/stormpath-sdk/resource/password_reset_token.rb +9 -5
- data/lib/stormpath-sdk/resource/phone.rb +8 -4
- data/lib/stormpath-sdk/resource/registered_saml_service_provider.rb +8 -0
- data/lib/stormpath-sdk/resource/saml_identity_provider.rb +14 -0
- data/lib/stormpath-sdk/resource/saml_identity_provider_metadata.rb +9 -0
- data/lib/stormpath-sdk/resource/saml_policy.rb +10 -0
- data/lib/stormpath-sdk/resource/saml_service_provider.rb +7 -0
- data/lib/stormpath-sdk/{provider/saml/saml_mapping_rules.rb → resource/saml_service_provider_metadata.rb} +6 -5
- data/lib/stormpath-sdk/resource/saml_service_provider_registration.rb +11 -0
- data/lib/stormpath-sdk/resource/schema.rb +8 -4
- data/lib/stormpath-sdk/resource/tenant.rb +11 -8
- data/lib/stormpath-sdk/resource/user_info_mapping_rules.rb +7 -3
- data/lib/stormpath-sdk/resource/utils.rb +7 -10
- data/lib/stormpath-sdk/resource/verification_email.rb +7 -3
- data/lib/stormpath-sdk/resource/x_509_certificate.rb +7 -0
- data/lib/stormpath-sdk/util/assert.rb +1 -3
- data/lib/stormpath-sdk/version.rb +2 -2
- data/spec/auth/basic_authenticator_spec.rb +28 -24
- data/spec/auth/register_service_provider_spec.rb +68 -0
- data/spec/auth/sauthc1_signer_spec.rb +8 -4
- data/spec/cache/cache_entry_spec.rb +28 -29
- data/spec/cache/cache_spec.rb +9 -9
- data/spec/cache/cache_stats_spec.rb +1 -1
- data/spec/client_spec.rb +63 -63
- data/spec/data_store_spec.rb +23 -14
- data/spec/oauth/access_token_authentication_result_spec.rb +8 -2
- data/spec/provider/account_resolver_spec.rb +6 -4
- data/spec/provider/provider_spec.rb +6 -6
- data/spec/resource/account_creation_policy_spec.rb +1 -1
- data/spec/resource/account_link_spec.rb +7 -15
- data/spec/resource/account_spec.rb +17 -17
- data/spec/resource/account_store_mapping_spec.rb +16 -22
- data/spec/resource/account_store_spec.rb +3 -3
- data/spec/resource/application_spec.rb +324 -330
- data/spec/resource/base_spec.rb +7 -31
- data/spec/resource/collection_spec.rb +63 -114
- data/spec/resource/custom_data_spec.rb +1 -1
- data/spec/resource/directory_spec.rb +91 -87
- data/spec/resource/expansion_spec.rb +10 -10
- data/spec/resource/factor_spec.rb +1 -1
- data/spec/resource/group_spec.rb +1 -1
- data/spec/resource/linked_account_spec.rb +7 -7
- data/spec/resource/organization_spec.rb +12 -11
- data/spec/resource/phone_spec.rb +1 -1
- data/spec/resource/registered_saml_service_provider_spec.rb +35 -0
- data/spec/resource/saml_identity_provider_metadata_spec.rb +27 -0
- data/spec/resource/saml_identity_provider_spec.rb +94 -0
- data/spec/resource/saml_policy_spec.rb +27 -0
- data/spec/resource/saml_service_provider_registration_spec.rb +58 -0
- data/spec/resource/saml_service_provider_spec.rb +19 -0
- data/spec/resource/status_spec.rb +4 -3
- data/spec/resource/tenant_spec.rb +4 -6
- data/spec/spec_helper.rb +1 -1
- data/spec/support/custom_data_save_period.rb +4 -0
- data/spec/support/custom_data_storage_behavior.rb +7 -8
- data/spec/support/mocked_provider_accounts.rb +101 -101
- data/spec/support/mocked_saml_responses.rb +130 -0
- data/spec/support/resource_factory.rb +4 -4
- data/spec/support/resource_helpers.rb +10 -4
- data/spec/support/resource_matchers.rb +4 -4
- data/spec/support/test_request_executor.rb +2 -2
- metadata +21 -8
- data/lib/stormpath-sdk/provider/saml/saml_provider_metadata.rb +0 -19
- data/spec/fixtures/response/create_saml_directory.json +0 -26
- data/spec/fixtures/response/create_saml_directory_mapping_rules.json +0 -12
- data/spec/fixtures/response/get_saml_directory_provider.json +0 -16
- data/spec/fixtures/response/get_saml_directory_provider_metadata.json +0 -12
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
module Stormpath
|
|
2
2
|
module Cache
|
|
3
3
|
class CacheManager
|
|
4
|
-
|
|
5
|
-
def initialize(opts = nil)
|
|
4
|
+
def initialize(_opts = nil)
|
|
6
5
|
@caches = {}
|
|
7
6
|
end
|
|
8
7
|
|
|
9
8
|
def create_cache(region, opts)
|
|
10
|
-
@caches[region] = Cache.new
|
|
9
|
+
@caches[region] = Cache.new(opts)
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
def get_cache(region)
|
|
@@ -15,7 +14,7 @@ module Stormpath
|
|
|
15
14
|
end
|
|
16
15
|
|
|
17
16
|
def stats
|
|
18
|
-
Hash[
|
|
17
|
+
Hash[@caches.map { |region, cache| [region, cache.stats] }]
|
|
19
18
|
end
|
|
20
19
|
end
|
|
21
20
|
end
|
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
module Stormpath
|
|
2
2
|
module Cache
|
|
3
3
|
class DisabledCacheStore
|
|
4
|
-
def initialize(opts = nil)
|
|
5
|
-
end
|
|
4
|
+
def initialize(opts = nil); end
|
|
6
5
|
|
|
7
|
-
def get(key)
|
|
8
|
-
end
|
|
6
|
+
def get(key); end
|
|
9
7
|
|
|
10
|
-
def put(
|
|
8
|
+
def put(_key, entry)
|
|
11
9
|
entry
|
|
12
10
|
end
|
|
13
11
|
|
|
14
|
-
def delete(key)
|
|
15
|
-
end
|
|
12
|
+
def delete(key); end
|
|
16
13
|
|
|
17
14
|
def clear
|
|
18
15
|
{}
|
|
@@ -23,4 +20,4 @@ module Stormpath
|
|
|
23
20
|
end
|
|
24
21
|
end
|
|
25
22
|
end
|
|
26
|
-
end
|
|
23
|
+
end
|
|
@@ -4,20 +4,20 @@ module Stormpath
|
|
|
4
4
|
module Cache
|
|
5
5
|
class RedisStore
|
|
6
6
|
def initialize(opts = {})
|
|
7
|
-
@redis = Redis.new
|
|
7
|
+
@redis = Redis.new(opts)
|
|
8
8
|
end
|
|
9
9
|
|
|
10
10
|
def get(key)
|
|
11
|
-
entry = @redis.get
|
|
11
|
+
entry = @redis.get(key)
|
|
12
12
|
entry && Stormpath::Cache::CacheEntry.from_h(MultiJson.load(entry))
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def put(key, entry)
|
|
16
|
-
@redis.set
|
|
16
|
+
@redis.set(key, MultiJson.dump(entry.to_h))
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def delete(key)
|
|
20
|
-
@redis.del
|
|
20
|
+
@redis.del(key)
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def clear
|
data/lib/stormpath-sdk/client.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Stormpath
|
|
|
28
28
|
|
|
29
29
|
api_key = ApiKey(options)
|
|
30
30
|
|
|
31
|
-
assert_not_nil api_key, "No API key has been provided. Please pass an 'api_key' or "
|
|
31
|
+
assert_not_nil api_key, "No API key has been provided. Please pass an 'api_key' or " \
|
|
32
32
|
"'api_key_file_location' to the Stormpath::Client constructor."
|
|
33
33
|
|
|
34
34
|
request_executor = Stormpath::Http::HttpClientRequestExecutor.new(proxy: options[:proxy])
|
|
@@ -36,7 +36,7 @@ module Stormpath
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def tenant(expansion = nil)
|
|
39
|
-
tenants.get
|
|
39
|
+
tenants.get('current', expansion)
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
def client
|
|
@@ -49,7 +49,7 @@ module Stormpath
|
|
|
49
49
|
has_many :accounts, href: '/accounts', can: :get do
|
|
50
50
|
def verify_email_token(token)
|
|
51
51
|
token_href = "#{href}/emailVerificationTokens/#{token}"
|
|
52
|
-
token = Stormpath::Resource::EmailVerificationToken.new
|
|
52
|
+
token = Stormpath::Resource::EmailVerificationToken.new(token_href, client)
|
|
53
53
|
data_store.save token, Stormpath::Resource::Account
|
|
54
54
|
end
|
|
55
55
|
end
|
|
@@ -61,46 +61,48 @@ module Stormpath
|
|
|
61
61
|
has_many :access_tokens, href: '/accessTokens', can: [:get]
|
|
62
62
|
has_many :refresh_tokens, href: '/refreshTokens', can: [:get]
|
|
63
63
|
has_many :account_links, href: '/accountLinks', can: :create
|
|
64
|
+
has_many :registered_saml_service_providers, href: '/registeredSamlServiceProviders', can: :create
|
|
64
65
|
|
|
65
66
|
private
|
|
66
67
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
end
|
|
73
|
-
elsif options[:api_key_file_location]
|
|
74
|
-
load_api_key_file(options[:api_key_file_location],
|
|
75
|
-
options[:api_key_id_property_name],
|
|
76
|
-
options[:api_key_secret_property_name])
|
|
68
|
+
def ApiKey(options = {})
|
|
69
|
+
if api_key = options[:api_key]
|
|
70
|
+
case api_key
|
|
71
|
+
when ApiKey then api_key
|
|
72
|
+
when Hash then ApiKey.new(api_key[:id], api_key[:secret])
|
|
77
73
|
end
|
|
74
|
+
elsif options[:api_key_file_location]
|
|
75
|
+
load_api_key_file(
|
|
76
|
+
options[:api_key_file_location],
|
|
77
|
+
options[:api_key_id_property_name],
|
|
78
|
+
options[:api_key_secret_property_name]
|
|
79
|
+
)
|
|
78
80
|
end
|
|
81
|
+
end
|
|
79
82
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
id_property_name ||= 'apiKey.id'
|
|
88
|
-
secret_property_name ||= 'apiKey.secret'
|
|
83
|
+
def load_api_key_file(api_key_file_location, id_property_name, secret_property_name)
|
|
84
|
+
begin
|
|
85
|
+
api_key_properties = JavaProperties::Properties.new(api_key_file_location)
|
|
86
|
+
rescue
|
|
87
|
+
raise ArgumentError, "No API Key file could be found or loaded from '#{api_key_file_location}'."
|
|
88
|
+
end
|
|
89
89
|
|
|
90
|
-
|
|
91
|
-
|
|
90
|
+
id_property_name ||= 'apiKey.id'
|
|
91
|
+
secret_property_name ||= 'apiKey.secret'
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
api_key_id = api_key_properties[id_property_name]
|
|
94
|
+
assert_not_nil api_key_id, api_key_warning_message(:id, api_key_file_location)
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
api_key_secret = api_key_properties[secret_property_name]
|
|
97
|
+
assert_not_nil api_key_secret, api_key_warning_message(:secret, api_key_file_location)
|
|
98
98
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
"in '#{api_key_file_location}' or pass in an 'api_key_#{id_or_secret}_property_name' " +
|
|
102
|
-
"to the Stormpath::Client constructor to specify an alternative property."
|
|
103
|
-
end
|
|
99
|
+
ApiKey.new(api_key_id, api_key_secret)
|
|
100
|
+
end
|
|
104
101
|
|
|
102
|
+
def api_key_warning_message(id_or_secret, api_key_file_location)
|
|
103
|
+
"No API #{id_or_secret} in properties. Please provide a 'apiKey.#{id_or_secret}' property " \
|
|
104
|
+
"in '#{api_key_file_location}' or pass in an 'api_key_#{id_or_secret}_property_name' " \
|
|
105
|
+
'to the Stormpath::Client constructor to specify an alternative property.'
|
|
106
|
+
end
|
|
105
107
|
end
|
|
106
108
|
end
|
|
@@ -13,346 +13,367 @@
|
|
|
13
13
|
# See the License for the specific language governing permissions and
|
|
14
14
|
# limitations under the License.
|
|
15
15
|
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
module Stormpath
|
|
17
|
+
class DataStore
|
|
18
|
+
include Stormpath::Http
|
|
19
|
+
include Stormpath::Util::Assert
|
|
19
20
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
DEFAULT_SERVER_HOST = 'api.stormpath.com'.freeze
|
|
22
|
+
DEFAULT_API_VERSION = 1
|
|
23
|
+
DEFAULT_BASE_URL = 'https://' + DEFAULT_SERVER_HOST + '/v' + DEFAULT_API_VERSION.to_s
|
|
24
|
+
HREF_PROP_NAME = Stormpath::Resource::Base::HREF_PROP_NAME
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
CACHE_REGIONS = %w(applications directories accounts groups groupMemberships accountMemberships
|
|
27
|
+
tenants customData provider providerData).freeze
|
|
26
28
|
|
|
27
|
-
|
|
29
|
+
attr_reader :client, :request_executor, :cache_manager, :api_key, :base_url
|
|
28
30
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
def initialize(request_executor, api_key, cache_opts, client, base_url = nil)
|
|
32
|
+
assert_not_nil request_executor, 'RequestExecutor cannot be null.'
|
|
31
33
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
@client = client
|
|
35
|
+
@base_url = base_url || DEFAULT_BASE_URL
|
|
36
|
+
@request_executor = request_executor
|
|
37
|
+
@api_key = api_key
|
|
38
|
+
initialize_cache(cache_opts)
|
|
39
|
+
end
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
def initialize_cache(cache_opts)
|
|
42
|
+
@cache_manager = Stormpath::Cache::CacheManager.new
|
|
43
|
+
regions_opts = cache_opts[:regions] || {}
|
|
44
|
+
CACHE_REGIONS.each do |region|
|
|
45
|
+
region_opts = regions_opts[region.to_sym] || {}
|
|
46
|
+
region_opts[:store] ||= cache_opts[:store]
|
|
47
|
+
region_opts[:store_opts] ||= cache_opts[:store_opts]
|
|
48
|
+
@cache_manager.create_cache(region, region_opts)
|
|
49
|
+
end
|
|
47
50
|
end
|
|
48
|
-
end
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
52
|
+
def instantiate(clazz, properties = {})
|
|
53
|
+
clazz.new(properties, client)
|
|
54
|
+
end
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
def get_resource(href, clazz, query = nil)
|
|
57
|
+
q_href = qualify href
|
|
56
58
|
|
|
57
|
-
|
|
59
|
+
data = execute_request('get', q_href, nil, query)
|
|
58
60
|
|
|
59
|
-
|
|
61
|
+
clazz = clazz.call(data) if clazz.respond_to? :call
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
instantiate(clazz, data.to_hash)
|
|
64
|
+
end
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
def create(parent_href, resource, return_type, options = {})
|
|
67
|
+
# TODO: assuming there is no ? in url
|
|
68
|
+
parent_href = "#{parent_href}?#{URI.encode_www_form(options)}" unless options.empty?
|
|
67
69
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
save_resource(parent_href, resource, return_type).tap do |returned_resource|
|
|
71
|
+
if resource.is_a?(return_type)
|
|
72
|
+
resource.set_properties(returned_resource.properties)
|
|
73
|
+
end
|
|
71
74
|
end
|
|
72
75
|
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def save(resource, clazz = nil)
|
|
76
|
-
assert_not_nil resource, "resource argument cannot be null."
|
|
77
|
-
assert_kind_of Stormpath::Resource::Base, resource, "resource argument must be instance of Stormpath::Resource::Base"
|
|
78
|
-
href = resource.href
|
|
79
|
-
assert_not_nil href, "href or resource.href cannot be null."
|
|
80
|
-
assert_true href.length > 0, "save may only be called on objects that have already been persisted (i.e. they have an existing href)."
|
|
81
76
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
77
|
+
def save(resource, clazz = nil)
|
|
78
|
+
assert_not_nil(resource, 'resource argument cannot be null.')
|
|
79
|
+
assert_kind_of(
|
|
80
|
+
Stormpath::Resource::Base,
|
|
81
|
+
resource,
|
|
82
|
+
'resource argument must be instance of Stormpath::Resource::Base'
|
|
83
|
+
)
|
|
84
|
+
href = resource.href
|
|
85
|
+
assert_not_nil(href, 'href or resource.href cannot be null.')
|
|
86
|
+
assert_true(
|
|
87
|
+
!href.empty?,
|
|
88
|
+
'save may only be called on objects that have already been persisted'\
|
|
89
|
+
' (i.e. they have an existing href).'
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
href = qualify(href)
|
|
93
|
+
|
|
94
|
+
clazz ||= resource.class
|
|
95
|
+
|
|
96
|
+
save_resource(href, resource, clazz).tap do |return_value|
|
|
97
|
+
resource.set_properties(return_value)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
resource.
|
|
101
|
+
def delete(resource, property_name = nil)
|
|
102
|
+
assert_not_nil(resource, 'resource argument cannot be null.')
|
|
103
|
+
assert_kind_of(
|
|
104
|
+
Stormpath::Resource::Base,
|
|
105
|
+
resource,
|
|
106
|
+
'resource argument must be instance of Stormpath::Resource::Base'
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
href = resource.href
|
|
110
|
+
href += "/#{property_name}" if property_name
|
|
111
|
+
href = qualify(href)
|
|
112
|
+
|
|
113
|
+
execute_request('delete', href)
|
|
114
|
+
clear_cache_on_delete(href)
|
|
115
|
+
nil
|
|
88
116
|
end
|
|
89
|
-
end
|
|
90
117
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
118
|
+
def execute_raw_request(href, body, klass)
|
|
119
|
+
request = Request.new('POST', href, nil, {}, body.to_json, @api_key)
|
|
120
|
+
apply_default_request_headers(request)
|
|
121
|
+
response = @request_executor.execute_request(request)
|
|
122
|
+
result = !response.body.empty? ? MultiJson.load(response.body) : ''
|
|
94
123
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
124
|
+
if response.error?
|
|
125
|
+
error = Stormpath::Resource::Error.new(result)
|
|
126
|
+
raise Stormpath::Error, error
|
|
127
|
+
end
|
|
98
128
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
end
|
|
129
|
+
cache_walk(result)
|
|
130
|
+
instantiate(klass, result)
|
|
131
|
+
end
|
|
103
132
|
|
|
104
|
-
|
|
105
|
-
request = Request.new('POST', href, nil, {}, body.to_json, @api_key)
|
|
106
|
-
apply_default_request_headers(request)
|
|
107
|
-
response = @request_executor.execute_request(request)
|
|
108
|
-
result = response.body.length > 0 ? MultiJson.load(response.body) : ''
|
|
133
|
+
private
|
|
109
134
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
raise Stormpath::Error.new(error)
|
|
135
|
+
def needs_to_be_fully_qualified?(href)
|
|
136
|
+
!href.downcase.start_with?('http')
|
|
113
137
|
end
|
|
114
138
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
139
|
+
def qualify(href)
|
|
140
|
+
needs_to_be_fully_qualified?(href) ? @base_url + href : href
|
|
141
|
+
end
|
|
118
142
|
|
|
119
|
-
|
|
143
|
+
def execute_request(http_method, href, resource = nil, query = nil)
|
|
144
|
+
if http_method == 'get' && (cache = cache_for href)
|
|
145
|
+
cached_result = cache.get(href)
|
|
146
|
+
return cached_result if cached_result
|
|
147
|
+
end
|
|
120
148
|
|
|
121
|
-
|
|
122
|
-
!href.downcase.start_with? 'http'
|
|
123
|
-
end
|
|
149
|
+
body = extract_body_from_resource(resource)
|
|
124
150
|
|
|
125
|
-
|
|
126
|
-
needs_to_be_fully_qualified?(href) ? @base_url + href : href
|
|
127
|
-
end
|
|
151
|
+
request = Request.new(http_method, href, query, {}, body, @api_key)
|
|
128
152
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
153
|
+
if resource.try(:form_data?)
|
|
154
|
+
apply_form_data_request_headers(request)
|
|
155
|
+
else
|
|
156
|
+
apply_default_request_headers(request)
|
|
157
|
+
end
|
|
134
158
|
|
|
135
|
-
|
|
159
|
+
response = @request_executor.execute_request(request)
|
|
136
160
|
|
|
137
|
-
|
|
161
|
+
result = !response.body.empty? ? MultiJson.load(response.body) : ''
|
|
138
162
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
end
|
|
163
|
+
if response.error?
|
|
164
|
+
error = Stormpath::Resource::Error.new(result)
|
|
165
|
+
raise Stormpath::Error, error
|
|
166
|
+
end
|
|
144
167
|
|
|
145
|
-
|
|
168
|
+
if resource.is_a?(Stormpath::Provider::AccountAccess)
|
|
169
|
+
is_new_account = response.http_status == 201
|
|
170
|
+
result = { is_new_account: is_new_account, account: result }
|
|
171
|
+
end
|
|
146
172
|
|
|
147
|
-
|
|
173
|
+
return if http_method == 'delete'
|
|
148
174
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
175
|
+
if result[HREF_PROP_NAME] && !resource_is_saml_mapping_rules?(resource) && !user_info_mapping_rules?(resource)
|
|
176
|
+
cache_walk result
|
|
177
|
+
else
|
|
178
|
+
result
|
|
179
|
+
end
|
|
152
180
|
end
|
|
153
181
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
182
|
+
def clear_cache_on_delete(href)
|
|
183
|
+
if href =~ custom_data_delete_field_url_regex
|
|
184
|
+
href = href.split('/')[0..-2].join('/')
|
|
185
|
+
end
|
|
186
|
+
clear_cache(href)
|
|
157
187
|
end
|
|
158
188
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if result[HREF_PROP_NAME] && !resource_is_saml_mapping_rules?(resource) && !user_info_mapping_rules?(resource)
|
|
162
|
-
cache_walk result
|
|
163
|
-
else
|
|
164
|
-
result
|
|
189
|
+
def custom_data_delete_field_url_regex
|
|
190
|
+
/#{@base_url}\/(accounts|groups)\/\w+\/customData\/\w+[\/]{0,1}$/
|
|
165
191
|
end
|
|
166
|
-
end
|
|
167
192
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
href
|
|
193
|
+
def clear_cache(href)
|
|
194
|
+
cache = cache_for(href)
|
|
195
|
+
cache.delete(href) if cache
|
|
171
196
|
end
|
|
172
|
-
clear_cache href
|
|
173
|
-
end
|
|
174
197
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def clear_cache(href)
|
|
180
|
-
cache = cache_for href
|
|
181
|
-
cache.delete href if cache
|
|
182
|
-
end
|
|
198
|
+
def cache_walk(resource)
|
|
199
|
+
assert_not_nil(resource[HREF_PROP_NAME], "resource must have 'href' property")
|
|
200
|
+
items = resource['items']
|
|
183
201
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
else # single resource
|
|
194
|
-
resource.each do |attr, value|
|
|
195
|
-
if value.is_a? Hash and value[HREF_PROP_NAME]
|
|
196
|
-
walked = cache_walk value
|
|
202
|
+
if items # collection resource
|
|
203
|
+
resource['items'] = items.map do |item|
|
|
204
|
+
cache_walk(item)
|
|
205
|
+
{ HREF_PROP_NAME => item[HREF_PROP_NAME] }
|
|
206
|
+
end
|
|
207
|
+
else # single resource
|
|
208
|
+
resource.each do |attr, value|
|
|
209
|
+
next unless value.is_a?(Hash) && value[HREF_PROP_NAME]
|
|
210
|
+
walked = cache_walk(value)
|
|
197
211
|
resource[attr] = { HREF_PROP_NAME => value[HREF_PROP_NAME] }
|
|
198
212
|
resource[attr]['items'] = walked['items'] if walked['items']
|
|
199
213
|
end
|
|
214
|
+
cache(resource) if resource.length > 1
|
|
200
215
|
end
|
|
201
|
-
|
|
216
|
+
resource
|
|
202
217
|
end
|
|
203
|
-
resource
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def cache(resource)
|
|
207
|
-
cache = cache_for resource[HREF_PROP_NAME]
|
|
208
|
-
cache.put resource[HREF_PROP_NAME], resource if cache
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
def cache_for(href)
|
|
212
|
-
@cache_manager.get_cache(region_for href)
|
|
213
|
-
end
|
|
214
218
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
region = href.split('/')[-1]
|
|
219
|
-
else
|
|
220
|
-
region = href.split('/')[-2]
|
|
219
|
+
def cache(resource)
|
|
220
|
+
cache = cache_for(resource[HREF_PROP_NAME])
|
|
221
|
+
cache.put(resource[HREF_PROP_NAME], resource) if cache
|
|
221
222
|
end
|
|
222
|
-
CACHE_REGIONS.include?(region) ? region : nil
|
|
223
|
-
end
|
|
224
|
-
|
|
225
|
-
def apply_default_request_headers(request)
|
|
226
|
-
request.http_headers.store 'Accept', 'application/json'
|
|
227
|
-
apply_default_user_agent(request)
|
|
228
223
|
|
|
229
|
-
|
|
230
|
-
|
|
224
|
+
def cache_for(href)
|
|
225
|
+
@cache_manager.get_cache(region_for(href))
|
|
231
226
|
end
|
|
232
|
-
end
|
|
233
227
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
end
|
|
244
|
-
|
|
245
|
-
def save_resource(href, resource, return_type)
|
|
246
|
-
assert_not_nil resource, "resource argument cannot be null."
|
|
247
|
-
assert_not_nil return_type, "returnType class cannot be null."
|
|
248
|
-
assert_kind_of Stormpath::Resource::Base, resource, "resource argument must be instance of Stormpath::Resource::Base"
|
|
249
|
-
|
|
250
|
-
q_href = qualify href
|
|
251
|
-
|
|
252
|
-
clear_cache_on_save(resource)
|
|
228
|
+
def region_for(href)
|
|
229
|
+
return nil if href.nil?
|
|
230
|
+
region = if href.include?('/customData')
|
|
231
|
+
href.split('/')[-1]
|
|
232
|
+
else
|
|
233
|
+
href.split('/')[-2]
|
|
234
|
+
end
|
|
235
|
+
CACHE_REGIONS.include?(region) ? region : nil
|
|
236
|
+
end
|
|
253
237
|
|
|
254
|
-
|
|
238
|
+
def apply_default_request_headers(request)
|
|
239
|
+
request.http_headers.store('Accept', 'application/json')
|
|
240
|
+
apply_default_user_agent(request)
|
|
255
241
|
|
|
256
|
-
|
|
257
|
-
|
|
242
|
+
if request.body && !request.body.empty?
|
|
243
|
+
request.http_headers.store('Content-Type', 'application/json')
|
|
244
|
+
end
|
|
245
|
+
end
|
|
258
246
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
247
|
+
def apply_form_data_request_headers(request)
|
|
248
|
+
request.http_headers.store('Content-Type', 'application/x-www-form-urlencoded')
|
|
249
|
+
apply_default_user_agent(request)
|
|
250
|
+
end
|
|
263
251
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
252
|
+
def apply_default_user_agent(request)
|
|
253
|
+
request.http_headers.store(
|
|
254
|
+
'User-Agent', 'stormpath-sdk-ruby/' + Stormpath::VERSION +
|
|
255
|
+
" ruby/#{RUBY_VERSION}-p#{RUBY_PATCHLEVEL}" \
|
|
256
|
+
' ' + Gem::Platform.local.os.to_s + '/' + Gem::Platform.local.version.to_s
|
|
257
|
+
)
|
|
269
258
|
end
|
|
270
|
-
end
|
|
271
259
|
|
|
260
|
+
def save_resource(href, resource, return_type)
|
|
261
|
+
assert_not_nil(resource, 'resource argument cannot be null.')
|
|
262
|
+
assert_not_nil(return_type, 'returnType class cannot be null.')
|
|
263
|
+
assert_kind_of(
|
|
264
|
+
Stormpath::Resource::Base,
|
|
265
|
+
resource,
|
|
266
|
+
'resource argument must be instance of Stormpath::Resource::Base'
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
q_href = qualify(href)
|
|
270
|
+
clear_cache_on_save(resource)
|
|
271
|
+
response = execute_request('post', q_href, resource)
|
|
272
|
+
instantiate(return_type, parse_response(response))
|
|
273
|
+
end
|
|
272
274
|
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
clear_cache cached_href
|
|
275
|
+
def parse_response(response)
|
|
276
|
+
return {} if response.is_a?(String) && response.blank?
|
|
277
|
+
response.to_hash
|
|
277
278
|
end
|
|
278
|
-
end
|
|
279
279
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
else
|
|
286
|
-
if resource.dirty_properties["isDefaultAccountStore"] != nil || resource.dirty_properties["isDefaultGroupStore"] != nil
|
|
287
|
-
clear_cache resource.application.href
|
|
280
|
+
def clear_cache_on_save(resource)
|
|
281
|
+
if resource.is_a?(Stormpath::Resource::CustomDataStorage)
|
|
282
|
+
clear_custom_data_cache_on_custom_data_storage_save(resource)
|
|
283
|
+
elsif resource.is_a?(Stormpath::Resource::AccountStoreMapping)
|
|
284
|
+
clear_application_cache_on_account_store_save(resource)
|
|
288
285
|
end
|
|
289
286
|
end
|
|
290
|
-
end
|
|
291
287
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
288
|
+
def clear_custom_data_cache_on_custom_data_storage_save(resource)
|
|
289
|
+
if resource.dirty_properties.key?('customData') && (resource.new? == false)
|
|
290
|
+
cached_href = resource.href + '/customData'
|
|
291
|
+
clear_cache(cached_href)
|
|
292
|
+
end
|
|
293
|
+
end
|
|
295
294
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
295
|
+
def clear_application_cache_on_account_store_save(resource)
|
|
296
|
+
if resource.new?
|
|
297
|
+
if resource.default_account_store? == true || resource.default_group_store? == true
|
|
298
|
+
clear_cache(resource.application.href)
|
|
299
|
+
end
|
|
300
|
+
else
|
|
301
|
+
if !resource.dirty_properties['isDefaultAccountStore'].nil? || !resource.dirty_properties['isDefaultGroupStore'].nil?
|
|
302
|
+
clear_cache(resource.application.href)
|
|
303
|
+
end
|
|
304
|
+
end
|
|
300
305
|
end
|
|
301
|
-
end
|
|
302
306
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
307
|
+
def extract_body_from_resource(resource)
|
|
308
|
+
return if resource.nil?
|
|
309
|
+
form_data = resource.try(:form_data?)
|
|
306
310
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
311
|
+
if form_data
|
|
312
|
+
form_request_parse(resource)
|
|
313
|
+
else
|
|
314
|
+
MultiJson.dump(to_hash(resource))
|
|
315
|
+
end
|
|
316
|
+
end
|
|
312
317
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
property = to_simple_reference name, property
|
|
317
|
-
end
|
|
318
|
+
def form_request_parse(resource)
|
|
319
|
+
URI.encode_www_form(resource.form_properties.to_a)
|
|
320
|
+
end
|
|
318
321
|
|
|
319
|
-
|
|
320
|
-
|
|
322
|
+
def to_hash(resource)
|
|
323
|
+
{}.tap do |properties|
|
|
324
|
+
resource.get_dirty_property_names.each do |name|
|
|
325
|
+
ignore_camelcasing = resource_is_custom_data(resource, name)
|
|
326
|
+
property = resource.get_property name, ignore_camelcasing: ignore_camelcasing
|
|
327
|
+
|
|
328
|
+
# Special use cases are with Custom Data, Provider and ProviderData, their hashes should not be simplified
|
|
329
|
+
# As of the implementation for MFA, Phone resource is added too
|
|
330
|
+
if property.is_a?(Hash) && !resource_nested_submittable(resource, name) && name != 'items' && name != 'phone'
|
|
331
|
+
property = to_simple_reference(name, property)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
if name == 'items' && resource_is_saml_mapping_rules?(resource)
|
|
335
|
+
property = property.map do |item|
|
|
336
|
+
item.transform_keys { |key| key.to_s.camelize(:lower).to_sym }
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
properties.store(name, property)
|
|
321
341
|
end
|
|
322
|
-
|
|
323
|
-
properties.store name, property
|
|
324
342
|
end
|
|
325
343
|
end
|
|
326
|
-
end
|
|
327
344
|
|
|
328
|
-
|
|
329
|
-
|
|
345
|
+
def to_simple_reference(property_name, hash)
|
|
346
|
+
assert_true(
|
|
347
|
+
hash.key?(HREF_PROP_NAME),
|
|
348
|
+
"Nested resource '#{property_name}' must have an 'href' property."
|
|
349
|
+
)
|
|
330
350
|
|
|
331
|
-
|
|
351
|
+
href = hash[HREF_PROP_NAME]
|
|
332
352
|
|
|
333
|
-
|
|
334
|
-
|
|
353
|
+
{ HREF_PROP_NAME => href }
|
|
354
|
+
end
|
|
335
355
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
356
|
+
def resource_nested_submittable(resource, name)
|
|
357
|
+
['provider', 'providerData', 'accountStore'].include?(name) ||
|
|
358
|
+
resource_is_custom_data(resource, name) ||
|
|
359
|
+
resource_is_application_web_config(resource, name)
|
|
360
|
+
end
|
|
341
361
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
362
|
+
def resource_is_custom_data(resource, name)
|
|
363
|
+
resource.is_a?(Stormpath::Resource::CustomData) || name == 'customData'
|
|
364
|
+
end
|
|
345
365
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
366
|
+
def resource_is_application_web_config(resource, name)
|
|
367
|
+
resource.is_a?(Stormpath::Resource::ApplicationWebConfig) &&
|
|
368
|
+
Stormpath::Resource::ApplicationWebConfig::ENDPOINTS.include?(name.underscore.to_sym)
|
|
369
|
+
end
|
|
350
370
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
371
|
+
def resource_is_saml_mapping_rules?(resource)
|
|
372
|
+
resource.is_a?(Stormpath::Resource::AttributeStatementMappingRules)
|
|
373
|
+
end
|
|
354
374
|
|
|
355
|
-
|
|
356
|
-
|
|
375
|
+
def user_info_mapping_rules?(resource)
|
|
376
|
+
resource.is_a?(Stormpath::Resource::UserInfoMappingRules)
|
|
377
|
+
end
|
|
357
378
|
end
|
|
358
379
|
end
|