stormpath-sdk 1.6.0 → 1.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright
|
2
|
+
# Copyright 2016 Stormpath, Inc.
|
3
3
|
#
|
4
4
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
5
|
# you may not use this file except in compliance with the License.
|
@@ -13,14 +13,18 @@
|
|
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
|
+
module Resource
|
18
|
+
class Challenge < Stormpath::Resource::Instance
|
19
|
+
prop_accessor :message
|
20
|
+
prop_reader :status, :created_at, :modified_at
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
+
belongs_to :factor
|
23
|
+
belongs_to :account
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
+
def validate(code)
|
26
|
+
data_store.execute_raw_request(href, { code: code }, Stormpath::Resource::Challenge)
|
27
|
+
end
|
28
|
+
end
|
25
29
|
end
|
26
30
|
end
|
@@ -13,101 +13,102 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
client.data_store
|
31
|
-
end
|
16
|
+
module Stormpath
|
17
|
+
module Resource
|
18
|
+
class Collection
|
19
|
+
include Enumerable
|
20
|
+
|
21
|
+
attr_reader :href, :client, :item_class, :collection_href, :criteria
|
22
|
+
|
23
|
+
def initialize(href, item_class, client, options = {})
|
24
|
+
@client = client
|
25
|
+
@href = href
|
26
|
+
@item_class = item_class
|
27
|
+
@collection_href = options[:collection_href] || @href
|
28
|
+
@criteria ||= {}
|
29
|
+
end
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
elsif query.is_a? Hash
|
37
|
-
query
|
38
|
-
end
|
31
|
+
def data_store
|
32
|
+
client.data_store
|
33
|
+
end
|
39
34
|
|
40
|
-
|
41
|
-
|
42
|
-
|
35
|
+
def search(query)
|
36
|
+
query_hash = if query.is_a? String
|
37
|
+
{ q: query }
|
38
|
+
elsif query.is_a? Hash
|
39
|
+
query
|
40
|
+
end
|
43
41
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
42
|
+
criteria.merge! query_hash
|
43
|
+
self
|
44
|
+
end
|
48
45
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
46
|
+
def offset(offset)
|
47
|
+
criteria[:offset] = offset
|
48
|
+
self
|
49
|
+
end
|
53
50
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
51
|
+
def limit(limit)
|
52
|
+
criteria[:limit] = limit
|
53
|
+
self
|
54
|
+
end
|
58
55
|
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
def order(statement)
|
57
|
+
criteria[:order_by] = statement
|
58
|
+
self
|
59
|
+
end
|
62
60
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
page
|
67
|
-
end
|
61
|
+
def each(&block)
|
62
|
+
PaginatedIterator.iterate(collection_href, client, item_class, @criteria, &block)
|
63
|
+
end
|
68
64
|
|
69
|
-
|
65
|
+
def current_page
|
66
|
+
page = CollectionPage.new(collection_href, client, criteria)
|
67
|
+
page.item_type = item_class
|
68
|
+
page
|
69
|
+
end
|
70
70
|
|
71
|
-
|
71
|
+
private
|
72
72
|
|
73
|
-
|
74
|
-
|
75
|
-
|
73
|
+
module PaginatedIterator
|
74
|
+
def self.iterate(collection_href, client, item_class, criteria, &block)
|
75
|
+
page = CollectionPage.new(collection_href, client, criteria)
|
76
|
+
page.item_type = item_class
|
76
77
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
78
|
+
unless page.items.count.zero?
|
79
|
+
page.items.each(&block)
|
80
|
+
criteria[:offset] = page.offset + page.limit
|
81
|
+
iterate(collection_href, client, item_class, criteria, &block)
|
82
|
+
end
|
81
83
|
end
|
82
84
|
end
|
83
|
-
end
|
84
85
|
|
85
|
-
|
86
|
-
|
86
|
+
class CollectionPage < Stormpath::Resource::Base
|
87
|
+
ITEMS = 'items'.freeze
|
87
88
|
|
88
|
-
|
89
|
+
prop_accessor :offset, :limit, :size
|
89
90
|
|
90
|
-
|
91
|
+
attr_accessor :item_type
|
91
92
|
|
92
|
-
|
93
|
-
|
94
|
-
|
93
|
+
def items
|
94
|
+
to_resource_array(get_property(ITEMS))
|
95
|
+
end
|
95
96
|
|
96
|
-
|
97
|
-
|
98
|
-
|
97
|
+
def to_resource(properties)
|
98
|
+
data_store.instantiate(item_type, properties)
|
99
|
+
end
|
99
100
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
101
|
+
def to_resource_array(vals)
|
102
|
+
[].tap do |items|
|
103
|
+
if vals.is_a? Array
|
104
|
+
vals.each do |val|
|
105
|
+
resource = to_resource val
|
106
|
+
items << resource
|
107
|
+
end
|
106
108
|
end
|
107
109
|
end
|
108
110
|
end
|
109
|
-
end
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end#Stormpath::Resource::Collection
|
111
|
+
end # class Stormpath::Resource::Collection::CollectionPage
|
112
|
+
end # Stormpath::Resource::Collection
|
113
|
+
end
|
114
|
+
end
|
@@ -13,82 +13,81 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
|
17
|
-
|
16
|
+
module Stormpath
|
17
|
+
module Resource
|
18
|
+
class CustomData < Stormpath::Resource::Instance
|
19
|
+
include Stormpath::Resource::CustomDataHashMethods
|
18
20
|
|
19
|
-
|
21
|
+
prop_reader :created_at, :modified_at
|
20
22
|
|
21
|
-
|
22
|
-
|
23
|
-
|
23
|
+
def [](property_name)
|
24
|
+
get_property property_name, ignore_camelcasing: true
|
25
|
+
end
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
27
|
+
def []=(property_name, property_value)
|
28
|
+
set_property property_name, property_value, ignore_camelcasing: true
|
29
|
+
end
|
28
30
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
if has_new_properties?
|
34
|
-
super
|
35
|
-
end
|
36
|
-
end
|
31
|
+
def save
|
32
|
+
delete_removed_properties if has_removed_properties?
|
33
|
+
super if has_new_properties?
|
34
|
+
end
|
37
35
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
36
|
+
def delete(name = nil)
|
37
|
+
if name.nil?
|
38
|
+
@properties = { HREF_PROP_NAME => @properties[HREF_PROP_NAME] }
|
39
|
+
@dirty_properties.clear
|
40
|
+
@deleted_properties.clear
|
41
|
+
return super()
|
42
|
+
end
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
44
|
+
@write_lock.lock
|
45
|
+
property_name = name.to_s
|
46
|
+
begin
|
47
|
+
@properties.delete(property_name)
|
48
|
+
@dirty_properties.delete(property_name)
|
49
|
+
@deleted_properties << property_name
|
50
|
+
@dirty = true
|
51
|
+
ensure
|
52
|
+
@write_lock.unlock
|
53
|
+
end
|
54
|
+
end
|
57
55
|
|
58
|
-
|
56
|
+
private
|
59
57
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
58
|
+
def sanitize(properties)
|
59
|
+
{}.tap do |sanitized_properties|
|
60
|
+
properties.map do |key, value|
|
61
|
+
property_name = key.to_s
|
62
|
+
sanitized_properties[property_name] = value
|
63
|
+
end
|
65
64
|
end
|
66
65
|
end
|
67
|
-
end
|
68
66
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
67
|
+
def has_removed_properties?
|
68
|
+
@read_lock.lock
|
69
|
+
begin
|
70
|
+
!@deleted_properties.empty?
|
71
|
+
ensure
|
72
|
+
@read_lock.unlock
|
73
|
+
end
|
75
74
|
end
|
76
|
-
end
|
77
75
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
76
|
+
def has_new_properties?
|
77
|
+
@read_lock.lock
|
78
|
+
begin
|
79
|
+
!@dirty_properties.empty?
|
80
|
+
ensure
|
81
|
+
@read_lock.unlock
|
82
|
+
end
|
84
83
|
end
|
85
|
-
end
|
86
84
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
85
|
+
def delete_removed_properties
|
86
|
+
@deleted_properties.delete_if do |deleted_property_name|
|
87
|
+
data_store.delete(self, deleted_property_name)
|
88
|
+
true
|
89
|
+
end
|
91
90
|
end
|
92
91
|
end
|
93
|
-
|
92
|
+
end
|
94
93
|
end
|
@@ -1,33 +1,36 @@
|
|
1
|
-
module Stormpath
|
2
|
-
|
1
|
+
module Stormpath
|
2
|
+
module Resource
|
3
|
+
module CustomDataHashMethods
|
4
|
+
extend ActiveSupport::Concern
|
3
5
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
included do
|
7
|
+
def has_key?(key)
|
8
|
+
materialize unless materialized?
|
9
|
+
properties.key? key.to_s
|
10
|
+
end
|
9
11
|
|
10
|
-
|
12
|
+
alias_method :include?, :has_key?
|
11
13
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
14
|
+
def has_value?(value)
|
15
|
+
materialize unless materialized?
|
16
|
+
properties.value? value
|
17
|
+
end
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
def store(key, value)
|
20
|
+
materialize unless materialized?
|
21
|
+
self[key] = value
|
22
|
+
end
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
24
|
+
def keys
|
25
|
+
materialize unless materialized?
|
26
|
+
properties.keys
|
27
|
+
end
|
26
28
|
|
27
|
-
|
28
|
-
|
29
|
-
|
29
|
+
def values
|
30
|
+
materialize unless materialized?
|
31
|
+
properties.values
|
32
|
+
end
|
33
|
+
end
|
30
34
|
end
|
31
35
|
end
|
32
|
-
|
33
|
-
end
|
36
|
+
end
|
@@ -13,27 +13,29 @@
|
|
13
13
|
# See the License for the specific language governing permissions and
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
|
-
module Stormpath
|
17
|
-
|
16
|
+
module Stormpath
|
17
|
+
module Resource
|
18
|
+
module CustomDataStorage
|
19
|
+
extend ActiveSupport::Concern
|
18
20
|
|
19
|
-
|
21
|
+
CUSTOM_DATA = 'custom_data'.freeze
|
20
22
|
|
21
|
-
|
23
|
+
included do
|
24
|
+
def save
|
25
|
+
apply_custom_data_updates_if_necessary
|
26
|
+
super
|
27
|
+
end
|
22
28
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
def apply_custom_data_updates_if_necessary
|
30
|
+
if custom_data.send(:has_removed_properties?)
|
31
|
+
custom_data.send(:delete_removed_properties)
|
32
|
+
end
|
27
33
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
if custom_data.send :has_new_properties?
|
33
|
-
self.set_property CUSTOM_DATA, custom_data.dirty_properties
|
34
|
+
if custom_data.send(:has_new_properties?)
|
35
|
+
set_property(CUSTOM_DATA, custom_data.dirty_properties)
|
36
|
+
end
|
37
|
+
end
|
34
38
|
end
|
35
39
|
end
|
36
|
-
|
37
40
|
end
|
38
|
-
|
39
41
|
end
|