evoleap-licensing 1.0.0.12

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 (31) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +480 -0
  3. data/lib/evoleap_licensing/configuration.rb +34 -0
  4. data/lib/evoleap_licensing/control_logic.rb +38 -0
  5. data/lib/evoleap_licensing/control_manager_helper.rb +230 -0
  6. data/lib/evoleap_licensing/control_strategy.rb +16 -0
  7. data/lib/evoleap_licensing/encryption_handler.rb +42 -0
  8. data/lib/evoleap_licensing/errors.rb +9 -0
  9. data/lib/evoleap_licensing/identity/instance_identity.rb +39 -0
  10. data/lib/evoleap_licensing/identity/user_identity.rb +32 -0
  11. data/lib/evoleap_licensing/platform_info.rb +75 -0
  12. data/lib/evoleap_licensing/results/component_checkin_result.rb +16 -0
  13. data/lib/evoleap_licensing/results/component_checkout_result.rb +18 -0
  14. data/lib/evoleap_licensing/results/components_status.rb +18 -0
  15. data/lib/evoleap_licensing/results/instance_validity.rb +46 -0
  16. data/lib/evoleap_licensing/results/license_info.rb +19 -0
  17. data/lib/evoleap_licensing/results/registration_result.rb +16 -0
  18. data/lib/evoleap_licensing/results/session_validity.rb +47 -0
  19. data/lib/evoleap_licensing/server_control_manager.rb +50 -0
  20. data/lib/evoleap_licensing/state/server_state.rb +78 -0
  21. data/lib/evoleap_licensing/state/session_state.rb +68 -0
  22. data/lib/evoleap_licensing/state/user_state.rb +78 -0
  23. data/lib/evoleap_licensing/types/component_license_model.rb +22 -0
  24. data/lib/evoleap_licensing/types/invalid_reason.rb +49 -0
  25. data/lib/evoleap_licensing/types/validation_status.rb +91 -0
  26. data/lib/evoleap_licensing/user_control_manager.rb +198 -0
  27. data/lib/evoleap_licensing/version.rb +6 -0
  28. data/lib/evoleap_licensing/web_client.rb +65 -0
  29. data/lib/evoleap_licensing/web_service.rb +168 -0
  30. data/lib/evoleap_licensing.rb +41 -0
  31. metadata +129 -0
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "time"
4
+
5
+ module EvoleapLicensing
6
+ module ControlManagerHelper
7
+ def self.register(state)
8
+ initialize_state(state)
9
+ response = yield
10
+
11
+ if response["success"]
12
+ state.registered_at = Time.parse(response["first_registered_at"])
13
+ state.grace_period_for_validation_failures = response["validation_grace_period"]
14
+ state.reset_registration_failures
15
+ state.take_registration_params(response)
16
+ RegistrationResult.new(success: true)
17
+ else
18
+ state.add_registration_failure(Time.now.utc)
19
+ RegistrationResult.new(success: false, error_message: response["error"])
20
+ end
21
+ end
22
+
23
+ def self.validate_instance(strategy, state)
24
+ initialize_state(state)
25
+ current_time = Time.now.utc
26
+
27
+ unless state.registered?
28
+ return get_unregistered_instance_validity(strategy, state, current_time)
29
+ end
30
+
31
+ response = yield
32
+
33
+ is_server_time = false
34
+ if response.key?("time")
35
+ current_time = Time.parse(response["time"])
36
+ is_server_time = true
37
+ end
38
+
39
+ status = response["status"]
40
+
41
+ if status == ValidationStatus::SUCCESS
42
+ grace_period = response["validation_grace_period"].to_i
43
+ state.grace_period_for_validation_failures = grace_period
44
+ state.features = response["features"] || []
45
+ else
46
+ grace_period = state.grace_period_for_validation_failures
47
+ end
48
+
49
+ if status == ValidationStatus::SERVICE_UNREACHABLE || status == ValidationStatus::GENERAL_ERROR
50
+ get_server_unreachable_instance_validity(state, grace_period, current_time, is_server_time)
51
+ else
52
+ get_server_reached_instance_validity(state, current_time, response)
53
+ end
54
+ end
55
+
56
+ def self.validate_session(strategy, user_state, session_state, web_service_call)
57
+ initialize_state(user_state)
58
+ current_time = Time.now.utc
59
+
60
+ unless user_state.registered?
61
+ return get_unregistered_session_validity(strategy, user_state, current_time)
62
+ end
63
+
64
+ response = web_service_call.call
65
+
66
+ is_server_time = false
67
+ if response.key?("time")
68
+ current_time = Time.parse(response["time"])
69
+ is_server_time = true
70
+ end
71
+
72
+ status = response["status"]
73
+
74
+ if status == ValidationStatus::SUCCESS
75
+ grace_period = response["validation_grace_period"].to_i
76
+ user_state.grace_period_for_validation_failures = grace_period
77
+ user_state.features = response["features"] || []
78
+ session_state.update_from_session_response(response) if session_state.active? == false
79
+ session_state.update_from_extend_response(response) if session_state.active?
80
+ # Handle first session setup
81
+ unless session_state.active?
82
+ session_state.update_from_session_response(response)
83
+ end
84
+ else
85
+ grace_period = user_state.grace_period_for_validation_failures
86
+ end
87
+
88
+ if status == ValidationStatus::SERVICE_UNREACHABLE || status == ValidationStatus::GENERAL_ERROR
89
+ get_server_unreachable_session_validity(user_state, grace_period, current_time, is_server_time)
90
+ else
91
+ get_server_reached_session_validity(user_state, current_time, response)
92
+ end
93
+ end
94
+
95
+ # --- Private methods ---
96
+
97
+ def self.initialize_state(state)
98
+ state.first_launch_time ||= Time.now.utc
99
+ end
100
+ private_class_method :initialize_state
101
+
102
+ def self.get_unregistered_instance_validity(strategy, state, current_time)
103
+ if !ControlLogic.first_launch_time_valid?(state.first_launch_time, current_time)
104
+ state.number_of_failed_validation_attempts += 1
105
+ state.last_validation_status = ValidationStatus::USER_TAMPERING_DETECTED
106
+ return InstanceValidity.invalid(ValidationStatus::USER_TAMPERING_DETECTED)
107
+ end
108
+
109
+ if ControlLogic.time_inconsistency_detected?(state.first_launch_time, state.failed_registration_times, current_time, false)
110
+ state.number_of_failed_validation_attempts += 1
111
+ state.last_validation_status = ValidationStatus::USER_TAMPERING_DETECTED
112
+ return InstanceValidity.invalid(ValidationStatus::USER_TAMPERING_DETECTED)
113
+ end
114
+
115
+ ok, expiration = ControlLogic.in_grace_period_for_unregistered_product?(strategy, state.first_launch_time, current_time)
116
+ return InstanceValidity.unregistered_grace_period(expiration) if ok
117
+
118
+ state.number_of_failed_validation_attempts += 1
119
+ state.last_validation_status = ValidationStatus::REGISTRATION_REQUIRED
120
+ InstanceValidity.invalid(ValidationStatus::REGISTRATION_REQUIRED)
121
+ end
122
+ private_class_method :get_unregistered_instance_validity
123
+
124
+ def self.get_server_unreachable_instance_validity(state, grace_period, current_time, is_server_time)
125
+ if ControlLogic.time_inconsistency_detected?(state.last_successful_validation_time, state.failed_validation_times, current_time, is_server_time)
126
+ state.number_of_failed_validation_attempts += 1
127
+ state.last_validation_status = ValidationStatus::USER_TAMPERING_DETECTED
128
+ return InstanceValidity.invalid(ValidationStatus::USER_TAMPERING_DETECTED)
129
+ end
130
+
131
+ state.number_of_failed_validation_attempts += 1
132
+ state.add_validation_failure(current_time)
133
+
134
+ if state.last_successful_validation_time.nil?
135
+ return InstanceValidity.invalid(ValidationStatus::SERVICE_UNREACHABLE)
136
+ end
137
+
138
+ if state.last_validation_status == ValidationStatus::SUCCESS
139
+ ok, expiration = ControlLogic.in_grace_period_for_validation_failures?(
140
+ grace_period,
141
+ state.number_of_failed_validation_attempts,
142
+ state.last_successful_validation_time,
143
+ current_time
144
+ )
145
+ if ok
146
+ return InstanceValidity.validation_failure_grace_period(expiration)
147
+ end
148
+ end
149
+
150
+ InstanceValidity.invalid(ValidationStatus::SERVICE_UNREACHABLE)
151
+ end
152
+ private_class_method :get_server_unreachable_instance_validity
153
+
154
+ def self.get_server_reached_instance_validity(state, current_time, response)
155
+ state.reset_validation_failures
156
+ state.last_successful_validation_time = current_time
157
+ state.last_validation_status = response["status"]
158
+
159
+ if response["status"] == ValidationStatus::SUCCESS
160
+ InstanceValidity.valid
161
+ else
162
+ InstanceValidity.invalid(response["status"])
163
+ end
164
+ end
165
+ private_class_method :get_server_reached_instance_validity
166
+
167
+ def self.get_unregistered_session_validity(strategy, state, current_time)
168
+ if !ControlLogic.first_launch_time_valid?(state.first_launch_time, current_time)
169
+ state.number_of_failed_validation_attempts += 1
170
+ state.last_validation_status = ValidationStatus::USER_TAMPERING_DETECTED
171
+ return SessionValidity.invalid(ValidationStatus::USER_TAMPERING_DETECTED)
172
+ end
173
+
174
+ if ControlLogic.time_inconsistency_detected?(state.first_launch_time, state.failed_registration_times, current_time, false)
175
+ state.number_of_failed_validation_attempts += 1
176
+ state.last_validation_status = ValidationStatus::USER_TAMPERING_DETECTED
177
+ return SessionValidity.invalid(ValidationStatus::USER_TAMPERING_DETECTED)
178
+ end
179
+
180
+ ok, expiration = ControlLogic.in_grace_period_for_unregistered_product?(strategy, state.first_launch_time, current_time)
181
+ return SessionValidity.unregistered_grace_period(expiration) if ok
182
+
183
+ state.number_of_failed_validation_attempts += 1
184
+ state.last_validation_status = ValidationStatus::REGISTRATION_REQUIRED
185
+ SessionValidity.invalid(ValidationStatus::REGISTRATION_REQUIRED)
186
+ end
187
+ private_class_method :get_unregistered_session_validity
188
+
189
+ def self.get_server_unreachable_session_validity(state, grace_period, current_time, is_server_time)
190
+ if ControlLogic.time_inconsistency_detected?(state.last_successful_validation_time, state.failed_validation_times, current_time, is_server_time)
191
+ state.number_of_failed_validation_attempts += 1
192
+ state.last_validation_status = ValidationStatus::USER_TAMPERING_DETECTED
193
+ return SessionValidity.invalid(ValidationStatus::USER_TAMPERING_DETECTED)
194
+ end
195
+
196
+ state.number_of_failed_validation_attempts += 1
197
+ state.add_validation_failure(current_time)
198
+
199
+ if state.last_successful_validation_time.nil?
200
+ return SessionValidity.invalid(ValidationStatus::SERVICE_UNREACHABLE)
201
+ end
202
+
203
+ if state.last_validation_status == ValidationStatus::SUCCESS
204
+ ok, expiration = ControlLogic.in_grace_period_for_validation_failures?(
205
+ grace_period,
206
+ state.number_of_failed_validation_attempts,
207
+ state.last_successful_validation_time,
208
+ current_time
209
+ )
210
+ return SessionValidity.validation_failure_grace_period(expiration) if ok
211
+ end
212
+
213
+ SessionValidity.invalid(ValidationStatus::SERVICE_UNREACHABLE)
214
+ end
215
+ private_class_method :get_server_unreachable_session_validity
216
+
217
+ def self.get_server_reached_session_validity(state, current_time, response)
218
+ state.reset_validation_failures
219
+ state.last_successful_validation_time = current_time
220
+ state.last_validation_status = response["status"]
221
+
222
+ if response["status"] == ValidationStatus::SUCCESS
223
+ SessionValidity.valid(duration: response["session_duration"])
224
+ else
225
+ SessionValidity.invalid(response["status"])
226
+ end
227
+ end
228
+ private_class_method :get_server_reached_session_validity
229
+ end
230
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class ControlStrategy
5
+ attr_accessor :grace_period_for_unregistered_product, :start_session_for_expired_session
6
+
7
+ def initialize(grace_period_for_unregistered_product: 0, start_session_for_expired_session: false)
8
+ @grace_period_for_unregistered_product = grace_period_for_unregistered_product
9
+ @start_session_for_expired_session = start_session_for_expired_session
10
+ end
11
+
12
+ def self.default
13
+ new
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+ require "json"
6
+
7
+ module EvoleapLicensing
8
+ module EncryptionHandler
9
+ def self.encrypt_payload(params, rsa_cipher)
10
+ aes = OpenSSL::Cipher::AES.new(128, :CBC)
11
+ aes.encrypt
12
+ aes_key = aes.random_key
13
+ iv = aes.random_iv
14
+
15
+ encrypted_aes_key = rsa_cipher.public_encrypt(aes_key)
16
+ encrypted_params = aes.update(params.to_json) + aes.final
17
+
18
+ final = []
19
+ final += encrypted_aes_key.bytes
20
+ final.push(0) # padding
21
+ final += iv.bytes
22
+ final.push(0) # padding
23
+ final += encrypted_params.bytes
24
+
25
+ encoded = Base64.strict_encode64(final.pack("C*"))
26
+ [aes_key, iv, encoded]
27
+ end
28
+
29
+ def self.decrypt_response(body, aes_key, aes_iv)
30
+ aes = OpenSSL::Cipher::AES.new(128, :CBC)
31
+ aes.decrypt
32
+ aes.key = aes_key
33
+ aes.iv = aes_iv
34
+ bytes = Base64.decode64(body)
35
+ aes.update(bytes) + aes.final
36
+ end
37
+
38
+ def self.rsa_key(public_key)
39
+ OpenSSL::PKey::RSA.new(public_key.strip)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class Error < StandardError; end
5
+ class ConfigurationError < Error; end
6
+ class NotRegisteredError < Error; end
7
+ class NoActiveSessionError < Error; end
8
+ class ReentrancyError < Error; end
9
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class InstanceIdentity
5
+ attr_reader :data
6
+
7
+ def initialize(data = {})
8
+ @data = data.transform_keys(&:to_s)
9
+ end
10
+
11
+ def add(key, value)
12
+ @data[key.to_s] = value
13
+ self
14
+ end
15
+
16
+ def [](key)
17
+ @data[key.to_s]
18
+ end
19
+
20
+ def to_api_format
21
+ @data.map { |k, v| { "key" => k, "value" => v.to_s } }
22
+ end
23
+
24
+ def to_h
25
+ @data.dup
26
+ end
27
+
28
+ def self.from_hardware
29
+ identity = new
30
+ info = PlatformInfo.get_info
31
+ info.each { |k, v| identity.add(k, v.is_a?(Array) ? v.join(",") : v) }
32
+ identity
33
+ end
34
+
35
+ def self.from_hash(hash)
36
+ new(hash)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class UserIdentity
5
+ attr_reader :data
6
+
7
+ def initialize(data = {})
8
+ @data = data.transform_keys(&:to_s)
9
+ end
10
+
11
+ def add(key, value)
12
+ @data[key.to_s] = value
13
+ self
14
+ end
15
+
16
+ def [](key)
17
+ @data[key.to_s]
18
+ end
19
+
20
+ def to_api_format
21
+ @data.map { |k, v| { "key" => k, "value" => v.to_s } }
22
+ end
23
+
24
+ def to_h
25
+ @data.dup
26
+ end
27
+
28
+ def self.from_hash(hash)
29
+ new(hash)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module EvoleapLicensing
6
+ module PlatformInfo
7
+ def self.architecture
8
+ RUBY_PLATFORM
9
+ end
10
+
11
+ def self.mac_addresses
12
+ case RUBY_PLATFORM
13
+ when /linux/
14
+ read_linux_mac_addresses
15
+ when /darwin/
16
+ read_mac_mac_addresses
17
+ when /mswin|mingw/
18
+ read_windows_mac_addresses
19
+ else
20
+ []
21
+ end
22
+ rescue StandardError
23
+ []
24
+ end
25
+
26
+ def self.hostname
27
+ Socket.gethostname
28
+ rescue StandardError
29
+ nil
30
+ end
31
+
32
+ def self.ip_address
33
+ Socket.ip_address_list
34
+ .select { |addr| addr.ipv4? && !addr.ipv4_loopback? }
35
+ .first&.ip_address
36
+ rescue StandardError
37
+ nil
38
+ end
39
+
40
+ def self.get_info
41
+ info = {}
42
+ merge_if_available(info, :architecture) { architecture }
43
+ merge_if_available(info, :mac_addresses) { mac_addresses }
44
+ merge_if_available(info, :hostname) { hostname }
45
+ merge_if_available(info, :ip_address) { ip_address }
46
+ info
47
+ end
48
+
49
+ def self.merge_if_available(hash, key)
50
+ value = yield
51
+ hash[key] = value if value && !(value.respond_to?(:empty?) && value.empty?)
52
+ rescue StandardError
53
+ # Skip unavailable info
54
+ end
55
+ private_class_method :merge_if_available
56
+
57
+ def self.read_linux_mac_addresses
58
+ Dir.glob("/sys/class/net/*/address").filter_map do |path|
59
+ addr = File.read(path).strip
60
+ addr unless addr == "00:00:00:00:00:00"
61
+ end
62
+ end
63
+ private_class_method :read_linux_mac_addresses
64
+
65
+ def self.read_mac_mac_addresses
66
+ `ifconfig 2>/dev/null`.scan(/ether\s+([0-9a-f:]+)/i).flatten
67
+ end
68
+ private_class_method :read_mac_mac_addresses
69
+
70
+ def self.read_windows_mac_addresses
71
+ `getmac /fo csv /nh 2>nul`.scan(/([0-9A-F-]{17})/i).flatten
72
+ end
73
+ private_class_method :read_windows_mac_addresses
74
+ end
75
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class ComponentCheckinResult
5
+ attr_reader :failure_reason
6
+
7
+ def initialize(success:, failure_reason: nil)
8
+ @success = success
9
+ @failure_reason = failure_reason
10
+ end
11
+
12
+ def success?
13
+ @success
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class ComponentCheckoutResult
5
+ attr_reader :failure_reason, :components, :component_entitlements
6
+
7
+ def initialize(success:, failure_reason: nil, components: [], component_entitlements: [])
8
+ @success = success
9
+ @failure_reason = failure_reason
10
+ @components = components
11
+ @component_entitlements = component_entitlements
12
+ end
13
+
14
+ def success?
15
+ @success
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class ComponentsStatus
5
+ attr_reader :components, :component_entitlements, :error_message
6
+
7
+ def initialize(success:, components: [], component_entitlements: [], error_message: nil)
8
+ @success = success
9
+ @components = components
10
+ @component_entitlements = component_entitlements
11
+ @error_message = error_message
12
+ end
13
+
14
+ def success?
15
+ @success
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class InstanceValidity
5
+ attr_reader :invalid_reason, :grace_period_expiration
6
+
7
+ def initialize(valid:, invalid_reason: nil,
8
+ in_validation_failure_grace_period: false,
9
+ in_unregistered_grace_period: false,
10
+ grace_period_expiration: nil)
11
+ @valid = valid
12
+ @invalid_reason = invalid_reason
13
+ @in_validation_failure_grace_period = in_validation_failure_grace_period
14
+ @in_unregistered_grace_period = in_unregistered_grace_period
15
+ @grace_period_expiration = grace_period_expiration
16
+ end
17
+
18
+ def valid?
19
+ @valid
20
+ end
21
+
22
+ def in_validation_failure_grace_period?
23
+ @in_validation_failure_grace_period
24
+ end
25
+
26
+ def in_unregistered_grace_period?
27
+ @in_unregistered_grace_period
28
+ end
29
+
30
+ def self.invalid(reason)
31
+ new(valid: false, invalid_reason: InvalidReason.from_validation_status(reason))
32
+ end
33
+
34
+ def self.valid
35
+ new(valid: true)
36
+ end
37
+
38
+ def self.unregistered_grace_period(expiration)
39
+ new(valid: true, in_unregistered_grace_period: true, grace_period_expiration: expiration)
40
+ end
41
+
42
+ def self.validation_failure_grace_period(expiration)
43
+ new(valid: true, in_validation_failure_grace_period: true, grace_period_expiration: expiration)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class LicenseInfo
5
+ attr_reader :owner_name, :owner_logo_url, :expiry, :error_message
6
+
7
+ def initialize(success:, owner_name: nil, owner_logo_url: nil, expiry: nil, error_message: nil)
8
+ @success = success
9
+ @owner_name = owner_name
10
+ @owner_logo_url = owner_logo_url
11
+ @expiry = expiry
12
+ @error_message = error_message
13
+ end
14
+
15
+ def success?
16
+ @success
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class RegistrationResult
5
+ attr_reader :error_message
6
+
7
+ def initialize(success:, error_message: nil)
8
+ @success = success
9
+ @error_message = error_message
10
+ end
11
+
12
+ def success?
13
+ @success
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class SessionValidity
5
+ attr_reader :invalid_reason, :validity_duration, :grace_period_expiration
6
+
7
+ def initialize(valid:, invalid_reason: nil, validity_duration: nil,
8
+ in_validation_failure_grace_period: false,
9
+ in_unregistered_grace_period: false,
10
+ grace_period_expiration: nil)
11
+ @valid = valid
12
+ @invalid_reason = invalid_reason
13
+ @validity_duration = validity_duration
14
+ @in_validation_failure_grace_period = in_validation_failure_grace_period
15
+ @in_unregistered_grace_period = in_unregistered_grace_period
16
+ @grace_period_expiration = grace_period_expiration
17
+ end
18
+
19
+ def valid?
20
+ @valid
21
+ end
22
+
23
+ def in_validation_failure_grace_period?
24
+ @in_validation_failure_grace_period
25
+ end
26
+
27
+ def in_unregistered_grace_period?
28
+ @in_unregistered_grace_period
29
+ end
30
+
31
+ def self.invalid(reason)
32
+ new(valid: false, invalid_reason: InvalidReason.from_validation_status(reason))
33
+ end
34
+
35
+ def self.valid(duration: nil)
36
+ new(valid: true, validity_duration: duration)
37
+ end
38
+
39
+ def self.unregistered_grace_period(expiration)
40
+ new(valid: true, in_unregistered_grace_period: true, grace_period_expiration: expiration)
41
+ end
42
+
43
+ def self.validation_failure_grace_period(expiration)
44
+ new(valid: true, in_validation_failure_grace_period: true, grace_period_expiration: expiration)
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module EvoleapLicensing
4
+ class ServerControlManager
5
+ attr_reader :product_id, :version, :public_key, :strategy, :server_state
6
+
7
+ def initialize(product_id:, version:, public_key:, strategy: nil, server_state: nil)
8
+ @product_id = product_id
9
+ @version = version
10
+ @public_key = public_key
11
+ @strategy = strategy || ControlStrategy.default
12
+ @server_state = server_state || ServerState.new
13
+ @client = WebClient.new
14
+ end
15
+
16
+ # Register the server instance with the licensing service.
17
+ # Returns a RegistrationResult.
18
+ def register(license_key:, instance_identity:)
19
+ ControlManagerHelper.register(@server_state) do
20
+ WebService.register_instance(
21
+ product_id: @product_id,
22
+ license_key: license_key,
23
+ hardware_identity: instance_identity.to_api_format,
24
+ public_key: @public_key,
25
+ client: @client
26
+ )
27
+ end
28
+ end
29
+
30
+ # Validate the server instance against the licensing service.
31
+ # Returns an InstanceValidity.
32
+ def validate_instance(instance_identity:)
33
+ ControlManagerHelper.validate_instance(@strategy, @server_state) do
34
+ WebService.validate_instance(
35
+ product_id: @product_id,
36
+ instance_id: @server_state.instance_guid,
37
+ version: @version,
38
+ hardware_identity: instance_identity.to_api_format,
39
+ public_key: @public_key,
40
+ client: @client
41
+ )
42
+ end
43
+ end
44
+
45
+ # For testing: allow injecting a custom web client
46
+ def web_client=(client)
47
+ @client = client
48
+ end
49
+ end
50
+ end