ree 1.2.4 → 1.2.7
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/Gemfile.lock +3 -1
- data/exe/ree +81 -0
- data/lib/ree/benchmark_method_plugin.rb +14 -36
- data/lib/ree/cli/licensing/obfuscate.rb +27 -0
- data/lib/ree/cli/licensing/register_client.rb +43 -0
- data/lib/ree/cli/licensing/renew_license.rb +45 -0
- data/lib/ree/cli.rb +6 -0
- data/lib/ree/contracts/contractable.rb +4 -4
- data/lib/ree/contracts/method_decorator.rb +80 -60
- data/lib/ree/core/package_loader.rb +16 -1
- data/lib/ree/licensing/bytecode_compiler.rb +21 -0
- data/lib/ree/licensing/client_store.rb +75 -0
- data/lib/ree/licensing/decryptor.rb +54 -0
- data/lib/ree/licensing/encryptor.rb +47 -0
- data/lib/ree/licensing/license_generator.rb +40 -0
- data/lib/ree/licensing/obfuscator.rb +109 -0
- data/lib/ree/method_added_hook.rb +106 -14
- data/lib/ree/version.rb +1 -1
- data/lib/ree.rb +23 -0
- metadata +24 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c6f558ae43347082febc5e6f30be8afe0ec5340423b9e2cca23d9b412139ab4e
|
|
4
|
+
data.tar.gz: 5a1dddb63ed9e12178ff17c3150eb8910fb29b63c564d78302dddf77dcc679a0
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f0f8936853687e6facfd21ea46bea355f593b6cf570ac20c6c3406e1764463432623664c829d207bc2e1ccd241baf4e99efc0e409518e596cfcbf5a20af4488a
|
|
7
|
+
data.tar.gz: 5950203f5babaf701bd5a9c48e9e51fabda3844f9e7645f0912fcd98f1cb109c488211c39119f6af1bcb918c8c34c2e74c8d55e933cc8aabe046dd94c0013fdd
|
data/Gemfile.lock
CHANGED
data/exe/ree
CHANGED
|
@@ -223,6 +223,87 @@ class ReeCliRunner
|
|
|
223
223
|
end
|
|
224
224
|
end
|
|
225
225
|
|
|
226
|
+
command :"licensing.register_client" do |c|
|
|
227
|
+
c.syntax = 'ree licensing.register_client --name=NAME --contact=CONTACT'
|
|
228
|
+
c.description = 'register a new licensing client'
|
|
229
|
+
c.summary = '> ' + c.description
|
|
230
|
+
c.example 'register a client', 'ree licensing.register_client --name="Glabix" --contact="hello@glabix.com" --metadata="type=enterprise,country=RU"'
|
|
231
|
+
c.option '--name NAME', String, 'Client name'
|
|
232
|
+
c.option '--contact CONTACT', String, 'Client contact email'
|
|
233
|
+
c.option '--metadata METADATA', String, 'Client metadata (key=value pairs separated by commas)'
|
|
234
|
+
c.option '--clients_dir DIR', String, 'Directory for clients.json (default: current dir)'
|
|
235
|
+
c.action do |args, options|
|
|
236
|
+
options_hash = options.__hash__
|
|
237
|
+
options_hash.delete(:trace)
|
|
238
|
+
|
|
239
|
+
raise Ree::Error.new("--name is required") unless options_hash[:name]
|
|
240
|
+
raise Ree::Error.new("--contact is required") unless options_hash[:contact]
|
|
241
|
+
|
|
242
|
+
Ree::CLI::Licensing::RegisterClient.run(
|
|
243
|
+
name: options_hash[:name],
|
|
244
|
+
contact: options_hash[:contact],
|
|
245
|
+
metadata_str: options_hash[:metadata],
|
|
246
|
+
clients_dir: options_hash[:clients_dir] || Dir.pwd
|
|
247
|
+
)
|
|
248
|
+
end
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
command :"licensing.obfuscate" do |c|
|
|
252
|
+
c.syntax = 'ree licensing.obfuscate --source_path=PATH --target_path=PATH --client_id=ID --expires_at=DATE'
|
|
253
|
+
c.description = 'obfuscate a Ree project for distribution'
|
|
254
|
+
c.summary = '> ' + c.description
|
|
255
|
+
c.example 'obfuscate project', 'ree licensing.obfuscate --source_path=./project --target_path=./dist --client_id=client_abc123 --expires_at="2026-12-31"'
|
|
256
|
+
c.option '--source_path PATH', String, 'Source project path'
|
|
257
|
+
c.option '--target_path PATH', String, 'Target distribution path'
|
|
258
|
+
c.option '--client_id ID', String, 'Client ID from clients.json'
|
|
259
|
+
c.option '--expires_at DATE', String, 'License expiration date (YYYY-MM-DD)'
|
|
260
|
+
c.option '--exclude_files FILES', String, 'Comma-separated list of files to exclude'
|
|
261
|
+
c.option '--clients_dir DIR', String, 'Directory for clients.json (default: current dir)'
|
|
262
|
+
c.action do |args, options|
|
|
263
|
+
options_hash = options.__hash__
|
|
264
|
+
options_hash.delete(:trace)
|
|
265
|
+
|
|
266
|
+
raise Ree::Error.new("--source_path is required") unless options_hash[:source_path]
|
|
267
|
+
raise Ree::Error.new("--target_path is required") unless options_hash[:target_path]
|
|
268
|
+
raise Ree::Error.new("--client_id is required") unless options_hash[:client_id]
|
|
269
|
+
raise Ree::Error.new("--expires_at is required") unless options_hash[:expires_at]
|
|
270
|
+
|
|
271
|
+
Ree::CLI::Licensing::Obfuscate.run(
|
|
272
|
+
source_path: options_hash[:source_path],
|
|
273
|
+
target_path: options_hash[:target_path],
|
|
274
|
+
client_id: options_hash[:client_id],
|
|
275
|
+
expires_at: options_hash[:expires_at],
|
|
276
|
+
exclude_files: options_hash[:exclude_files] || "",
|
|
277
|
+
clients_dir: options_hash[:clients_dir] || Dir.pwd
|
|
278
|
+
)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
command :"licensing.renew_license" do |c|
|
|
283
|
+
c.syntax = 'ree licensing.renew_license --client_id=ID --expires_at=DATE'
|
|
284
|
+
c.description = 'renew a license for an existing client'
|
|
285
|
+
c.summary = '> ' + c.description
|
|
286
|
+
c.example 'renew license', 'ree licensing.renew_license --client_id=client_abc123 --expires_at="2028-01-01"'
|
|
287
|
+
c.option '--client_id ID', String, 'Client ID from clients.json'
|
|
288
|
+
c.option '--expires_at DATE', String, 'New license expiration date (YYYY-MM-DD)'
|
|
289
|
+
c.option '--output_path PATH', String, 'Output path for license.json'
|
|
290
|
+
c.option '--clients_dir DIR', String, 'Directory for clients.json (default: current dir)'
|
|
291
|
+
c.action do |args, options|
|
|
292
|
+
options_hash = options.__hash__
|
|
293
|
+
options_hash.delete(:trace)
|
|
294
|
+
|
|
295
|
+
raise Ree::Error.new("--client_id is required") unless options_hash[:client_id]
|
|
296
|
+
raise Ree::Error.new("--expires_at is required") unless options_hash[:expires_at]
|
|
297
|
+
|
|
298
|
+
Ree::CLI::Licensing::RenewLicense.run(
|
|
299
|
+
client_id: options_hash[:client_id],
|
|
300
|
+
expires_at: options_hash[:expires_at],
|
|
301
|
+
output_path: options_hash[:output_path],
|
|
302
|
+
clients_dir: options_hash[:clients_dir] || Dir.pwd
|
|
303
|
+
)
|
|
304
|
+
end
|
|
305
|
+
end
|
|
306
|
+
|
|
226
307
|
command :"gen.index_project" do |c|
|
|
227
308
|
c.syntax = 'ree gen.index_project'
|
|
228
309
|
c.description = 'generate index schema of methods'
|
|
@@ -12,66 +12,48 @@ class Ree::BenchmarkMethodPlugin
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def call
|
|
15
|
-
return unless @method_name == :call && ree_fn?
|
|
15
|
+
return nil unless @method_name == :call && ree_fn?
|
|
16
16
|
|
|
17
17
|
config = @target.instance_variable_get(:@__ree_benchmark_config)
|
|
18
|
-
|
|
18
|
+
benchmark_name = build_benchmark_name
|
|
19
|
+
|
|
19
20
|
if config
|
|
20
|
-
|
|
21
|
+
build_entry_point_wrapper(benchmark_name, config)
|
|
21
22
|
else
|
|
22
|
-
|
|
23
|
+
build_collector_wrapper(benchmark_name)
|
|
23
24
|
end
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
private
|
|
27
28
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
-
method_name = @method_name
|
|
31
|
-
method_alias = :"__benchmark_#{method_name}"
|
|
32
|
-
|
|
33
|
-
return if alias_target.method_defined?(method_alias)
|
|
34
|
-
|
|
35
|
-
alias_target.alias_method(method_alias, method_name)
|
|
36
|
-
|
|
37
|
-
benchmark_name = build_benchmark_name
|
|
38
|
-
output_proc = config[:output] || -> (res) { $stdout.puts(res) }
|
|
29
|
+
def build_entry_point_wrapper(benchmark_name, config)
|
|
30
|
+
output_proc = config[:output] || ->(res) { $stdout.puts(res) }
|
|
39
31
|
deep = config.fetch(:deep, true)
|
|
40
32
|
once = config.fetch(:once, false)
|
|
41
33
|
benchmark_done = false
|
|
42
34
|
|
|
43
|
-
|
|
35
|
+
Proc.new do |instance, next_layer, *args, **kwargs, &block|
|
|
44
36
|
if Ree::BenchmarkTracer.active?
|
|
45
37
|
Ree::BenchmarkTracer.collect(benchmark_name) do
|
|
46
|
-
|
|
38
|
+
next_layer.call(*args, **kwargs, &block)
|
|
47
39
|
end
|
|
48
40
|
elsif once && benchmark_done
|
|
49
41
|
Ree::BenchmarkTracer.collect(benchmark_name) do
|
|
50
|
-
|
|
42
|
+
next_layer.call(*args, **kwargs, &block)
|
|
51
43
|
end
|
|
52
44
|
else
|
|
53
45
|
benchmark_done = true if once
|
|
54
46
|
Ree::BenchmarkTracer.trace(benchmark_name, output_proc: output_proc, deep: deep) do
|
|
55
|
-
|
|
47
|
+
next_layer.call(*args, **kwargs, &block)
|
|
56
48
|
end
|
|
57
49
|
end
|
|
58
50
|
end
|
|
59
51
|
end
|
|
60
52
|
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
method_name = @method_name
|
|
64
|
-
method_alias = :"__benchmark_#{method_name}"
|
|
65
|
-
|
|
66
|
-
return if alias_target.method_defined?(method_alias)
|
|
67
|
-
|
|
68
|
-
alias_target.alias_method(method_alias, method_name)
|
|
69
|
-
|
|
70
|
-
benchmark_name = build_benchmark_name
|
|
71
|
-
|
|
72
|
-
alias_target.define_method(method_name) do |*args, **kwargs, &block|
|
|
53
|
+
def build_collector_wrapper(benchmark_name)
|
|
54
|
+
Proc.new do |instance, next_layer, *args, **kwargs, &block|
|
|
73
55
|
Ree::BenchmarkTracer.collect(benchmark_name) do
|
|
74
|
-
|
|
56
|
+
next_layer.call(*args, **kwargs, &block)
|
|
75
57
|
end
|
|
76
58
|
end
|
|
77
59
|
end
|
|
@@ -99,8 +81,4 @@ class Ree::BenchmarkMethodPlugin
|
|
|
99
81
|
|
|
100
82
|
facade.get_object(pkg, obj).fn?
|
|
101
83
|
end
|
|
102
|
-
|
|
103
|
-
def eigenclass
|
|
104
|
-
class << @target; self; end
|
|
105
|
-
end
|
|
106
84
|
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ree
|
|
4
|
+
module CLI
|
|
5
|
+
module Licensing
|
|
6
|
+
class Obfuscate
|
|
7
|
+
class << self
|
|
8
|
+
def run(source_path:, target_path:, client_id:, expires_at:, exclude_files: "", clients_dir: Dir.pwd, stdout: $stdout)
|
|
9
|
+
exclude_list = exclude_files.to_s.split(',').map(&:strip).reject(&:empty?)
|
|
10
|
+
|
|
11
|
+
Ree::Licensing::Obfuscator.run(
|
|
12
|
+
source_path: source_path,
|
|
13
|
+
target_path: target_path,
|
|
14
|
+
client_id: client_id,
|
|
15
|
+
expires_at: expires_at,
|
|
16
|
+
clients_dir: clients_dir,
|
|
17
|
+
exclude_files: exclude_list,
|
|
18
|
+
stdout: stdout
|
|
19
|
+
)
|
|
20
|
+
rescue Ree::Error => e
|
|
21
|
+
stdout.puts "Error: #{e.message}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ree
|
|
4
|
+
module CLI
|
|
5
|
+
module Licensing
|
|
6
|
+
class RegisterClient
|
|
7
|
+
class << self
|
|
8
|
+
def run(name:, contact:, metadata_str: nil, clients_dir: Dir.pwd, stdout: $stdout)
|
|
9
|
+
metadata = parse_metadata(metadata_str)
|
|
10
|
+
|
|
11
|
+
store = Ree::Licensing::ClientStore.new(clients_dir)
|
|
12
|
+
client = store.register_client(
|
|
13
|
+
name: name,
|
|
14
|
+
contact: contact,
|
|
15
|
+
metadata: metadata
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
stdout.puts "Client registered successfully:"
|
|
19
|
+
stdout.puts " Client ID: #{client['client_id']}"
|
|
20
|
+
stdout.puts " Name: #{client['name']}"
|
|
21
|
+
stdout.puts " Contact: #{client['contact']}"
|
|
22
|
+
stdout.puts " Saved to: #{File.join(clients_dir, 'clients.json')}"
|
|
23
|
+
|
|
24
|
+
client
|
|
25
|
+
rescue Ree::Error => e
|
|
26
|
+
stdout.puts "Error: #{e.message}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def parse_metadata(metadata_str)
|
|
32
|
+
return {} if metadata_str.nil? || metadata_str.empty?
|
|
33
|
+
|
|
34
|
+
metadata_str.split(',').each_with_object({}) do |pair, hash|
|
|
35
|
+
key, value = pair.split('=', 2)
|
|
36
|
+
hash[key.strip] = value&.strip
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Ree
|
|
6
|
+
module CLI
|
|
7
|
+
module Licensing
|
|
8
|
+
class RenewLicense
|
|
9
|
+
class << self
|
|
10
|
+
def run(client_id:, expires_at:, output_path: nil, clients_dir: Dir.pwd, stdout: $stdout)
|
|
11
|
+
store = Ree::Licensing::ClientStore.new(clients_dir)
|
|
12
|
+
client = store.find_client(client_id)
|
|
13
|
+
raise Ree::Error.new("Client #{client_id} not found") unless client
|
|
14
|
+
|
|
15
|
+
last_license = store.last_license(client_id)
|
|
16
|
+
raise Ree::Error.new("No existing license found for #{client_id}") unless last_license
|
|
17
|
+
|
|
18
|
+
result = Ree::Licensing::LicenseGenerator.generate(
|
|
19
|
+
client_id: client_id,
|
|
20
|
+
private_key_pem: client['private_key_pem'],
|
|
21
|
+
public_key_pem: client['public_key_pem'],
|
|
22
|
+
aes_key_hex: last_license['aes_key_hex'],
|
|
23
|
+
iv_hex: last_license['iv_hex'],
|
|
24
|
+
expires_at: expires_at
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
store.add_license(client_id, result[:license_record])
|
|
28
|
+
|
|
29
|
+
output = output_path || File.join(clients_dir, "license_#{client_id}.json")
|
|
30
|
+
File.write(output, JSON.pretty_generate(result[:license_file]))
|
|
31
|
+
|
|
32
|
+
stdout.puts "License renewed successfully:"
|
|
33
|
+
stdout.puts " Client ID: #{client_id}"
|
|
34
|
+
stdout.puts " Expires at: #{expires_at}"
|
|
35
|
+
stdout.puts " License file: #{output}"
|
|
36
|
+
|
|
37
|
+
result
|
|
38
|
+
rescue Ree::Error => e
|
|
39
|
+
stdout.puts "Error: #{e.message}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/ree/cli.rb
CHANGED
|
@@ -8,5 +8,11 @@ module Ree
|
|
|
8
8
|
autoload :GenerateTemplate, 'ree/cli/generate_template'
|
|
9
9
|
autoload :Indexing, 'ree/cli/indexing'
|
|
10
10
|
autoload :SpecRunner, 'ree/cli/spec_runner'
|
|
11
|
+
|
|
12
|
+
module Licensing
|
|
13
|
+
autoload :RegisterClient, 'ree/cli/licensing/register_client'
|
|
14
|
+
autoload :Obfuscate, 'ree/cli/licensing/obfuscate'
|
|
15
|
+
autoload :RenewLicense, 'ree/cli/licensing/renew_license'
|
|
16
|
+
end
|
|
11
17
|
end
|
|
12
18
|
end
|
|
@@ -7,14 +7,14 @@ require_relative 'engine_proxy'
|
|
|
7
7
|
module Ree::Contracts
|
|
8
8
|
module Contractable
|
|
9
9
|
def method_added(name)
|
|
10
|
-
return if _ree_method_added_hook_active?
|
|
11
|
-
MethodDecorator.new(name, false, self).call
|
|
10
|
+
return super if _ree_method_added_hook_active?
|
|
11
|
+
MethodDecorator.new(name, false, self).call(plugin_mode: false)
|
|
12
12
|
super
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def singleton_method_added(name)
|
|
16
|
-
return if _ree_method_added_hook_active?
|
|
17
|
-
MethodDecorator.new(name, true, self).call
|
|
16
|
+
return super if _ree_method_added_hook_active?
|
|
17
|
+
MethodDecorator.new(name, true, self).call(plugin_mode: false)
|
|
18
18
|
super
|
|
19
19
|
end
|
|
20
20
|
|
|
@@ -51,21 +51,18 @@ module Ree::Contracts
|
|
|
51
51
|
@doc = engine.fetch_doc
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
-
def call
|
|
55
|
-
return if Ree::Contracts.no_contracts?
|
|
56
|
-
return unless contract_definition
|
|
54
|
+
def call(plugin_mode: true)
|
|
55
|
+
return nil if Ree::Contracts.no_contracts?
|
|
56
|
+
return nil unless contract_definition
|
|
57
57
|
|
|
58
|
+
# Store decorator for runtime lookups (still needed)
|
|
58
59
|
self.class.add_decorator(self)
|
|
59
60
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
else
|
|
64
|
-
method_name
|
|
65
|
-
end
|
|
66
|
-
|
|
61
|
+
# Get method parameters from the method (before aliasing)
|
|
62
|
+
# Note: We get params from the current method, not __ree_original_#{method_name}
|
|
63
|
+
# because the alias hasn't been created yet when plugins are called
|
|
67
64
|
@method_parameters = alias_target
|
|
68
|
-
.instance_method(
|
|
65
|
+
.instance_method(method_name)
|
|
69
66
|
.parameters
|
|
70
67
|
.freeze
|
|
71
68
|
|
|
@@ -77,21 +74,13 @@ module Ree::Contracts
|
|
|
77
74
|
|
|
78
75
|
@return_validator = Validators.fetch_for(contract_definition.return_contract)
|
|
79
76
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
result = target.send(method_alias, *args, **kwargs, &blk)
|
|
87
|
-
|
|
88
|
-
if !return_validator.call(result)
|
|
89
|
-
raise ReturnContractError, "Invalid return value for #{printed_name}\n #{
|
|
90
|
-
return_validator.message(result, 'returns', 0).strip
|
|
91
|
-
}"
|
|
77
|
+
if plugin_mode
|
|
78
|
+
# Plugin mode: Return wrapper lambda for composition
|
|
79
|
+
build_contract_wrapper
|
|
80
|
+
else
|
|
81
|
+
# Legacy mode: Apply wrapper directly (for Contractable standalone usage)
|
|
82
|
+
apply_contract_wrapper_directly
|
|
92
83
|
end
|
|
93
|
-
|
|
94
|
-
result
|
|
95
84
|
end
|
|
96
85
|
|
|
97
86
|
# Unique ID of this Method Decorator
|
|
@@ -99,11 +88,6 @@ module Ree::Contracts
|
|
|
99
88
|
@id ||= self.class.decorator_id(target, method_name, is_class_method)
|
|
100
89
|
end
|
|
101
90
|
|
|
102
|
-
# Alias name for original method
|
|
103
|
-
def method_alias
|
|
104
|
-
@method_alias ||= :"__original_#{method_name}_#{SecureRandom.hex}"
|
|
105
|
-
end
|
|
106
|
-
|
|
107
91
|
# Target class to be used for alias method definition
|
|
108
92
|
def alias_target
|
|
109
93
|
@alias_target ||= begin
|
|
@@ -112,54 +96,90 @@ module Ree::Contracts
|
|
|
112
96
|
end
|
|
113
97
|
end
|
|
114
98
|
|
|
99
|
+
# Public method used by legacy contract wrapper (called from class_eval'd method)
|
|
100
|
+
def validate_and_call(instance, method_alias, args, kwargs, &blk)
|
|
101
|
+
@args.call(args, kwargs, blk)
|
|
102
|
+
result = instance.send(method_alias, *args, **kwargs, &blk)
|
|
103
|
+
|
|
104
|
+
unless @return_validator.call(result)
|
|
105
|
+
raise ReturnContractError, "Invalid return value for #{printed_name}\n #{
|
|
106
|
+
@return_validator.message(result, 'returns', 0).strip
|
|
107
|
+
}"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
result
|
|
111
|
+
end
|
|
112
|
+
|
|
115
113
|
private
|
|
116
114
|
|
|
117
|
-
def
|
|
118
|
-
|
|
115
|
+
def build_contract_wrapper
|
|
116
|
+
args_validator = @args
|
|
117
|
+
return_validator = @return_validator
|
|
118
|
+
decorator_printed_name = printed_name
|
|
119
|
+
|
|
120
|
+
Proc.new do |instance, next_layer, *args, **kwargs, &block|
|
|
121
|
+
# Validate arguments
|
|
122
|
+
args_validator.call(args, kwargs, block)
|
|
123
|
+
|
|
124
|
+
# Call next layer
|
|
125
|
+
result = next_layer.call(*args, **kwargs, &block)
|
|
126
|
+
|
|
127
|
+
# Validate return value
|
|
128
|
+
unless return_validator.call(result)
|
|
129
|
+
raise ReturnContractError, "Invalid return value for #{decorator_printed_name}\n #{
|
|
130
|
+
return_validator.message(result, 'returns', 0).strip
|
|
131
|
+
}"
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
result
|
|
135
|
+
end
|
|
119
136
|
end
|
|
120
137
|
|
|
121
|
-
def
|
|
138
|
+
def apply_contract_wrapper_directly
|
|
139
|
+
# Legacy mode for Contractable standalone usage
|
|
140
|
+
# Detect visibility BEFORE creating alias
|
|
141
|
+
visibility = if alias_target.private_instance_methods.include?(method_name)
|
|
142
|
+
:private
|
|
143
|
+
elsif alias_target.protected_instance_methods.include?(method_name)
|
|
144
|
+
:protected
|
|
145
|
+
else
|
|
146
|
+
:public
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Create our own alias and wrapper
|
|
150
|
+
method_alias = :"__original_#{method_name}_#{SecureRandom.hex}"
|
|
151
|
+
alias_target.alias_method(method_alias, method_name)
|
|
152
|
+
|
|
153
|
+
args_validator = @args
|
|
154
|
+
return_validator = @return_validator
|
|
155
|
+
decorator_printed_name = printed_name
|
|
156
|
+
|
|
122
157
|
file, line = alias_target.instance_method(method_alias).source_location
|
|
123
158
|
|
|
124
159
|
alias_target.class_eval(%Q(
|
|
125
160
|
def #{method_name}(*args, **kwargs, &blk)
|
|
126
161
|
decorator = Ree::Contracts::MethodDecorator.get_decorator('#{id}')
|
|
127
|
-
decorator.
|
|
162
|
+
decorator.validate_and_call(self, #{method_alias.inspect}, args, kwargs, &blk)
|
|
128
163
|
end
|
|
129
164
|
), file, line - 3)
|
|
130
165
|
|
|
131
|
-
|
|
132
|
-
|
|
166
|
+
# Restore visibility
|
|
167
|
+
case visibility
|
|
168
|
+
when :private
|
|
169
|
+
alias_target.send(:private, method_name, method_alias)
|
|
170
|
+
when :protected
|
|
171
|
+
alias_target.send(:protected, method_name, method_alias)
|
|
172
|
+
end
|
|
133
173
|
end
|
|
134
174
|
|
|
135
175
|
def private_method?
|
|
136
|
-
return target.private_methods.include?(
|
|
137
|
-
target.private_instance_methods.include?(
|
|
176
|
+
return target.private_methods.include?(method_name) if is_class_method
|
|
177
|
+
target.private_instance_methods.include?(method_name)
|
|
138
178
|
end
|
|
139
179
|
|
|
140
180
|
def protected_method?
|
|
141
|
-
return target.protected_methods.include?(
|
|
142
|
-
target.protected_instance_methods.include?(
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
def make_private
|
|
146
|
-
_method_name = method_name
|
|
147
|
-
_method_alias = method_alias
|
|
148
|
-
|
|
149
|
-
alias_target.class_eval do
|
|
150
|
-
private _method_name
|
|
151
|
-
private _method_alias
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def make_protected
|
|
156
|
-
_method_name = method_name
|
|
157
|
-
_method_alias = method_alias
|
|
158
|
-
|
|
159
|
-
alias_target.class_eval do
|
|
160
|
-
protected _method_name
|
|
161
|
-
protected _method_alias
|
|
162
|
-
end
|
|
181
|
+
return target.protected_methods.include?(method_name) if is_class_method
|
|
182
|
+
target.protected_instance_methods.include?(method_name)
|
|
163
183
|
end
|
|
164
184
|
|
|
165
185
|
def printed_name
|
|
@@ -95,7 +95,22 @@ class Ree::PackageLoader
|
|
|
95
95
|
|
|
96
96
|
Ree.logger.debug("load_file(:#{package_name}, '#{path}')")
|
|
97
97
|
|
|
98
|
-
|
|
98
|
+
if Ree.obfuscated?
|
|
99
|
+
load_encrypted_file(path)
|
|
100
|
+
else
|
|
101
|
+
Kernel.require(path)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def load_encrypted_file(path)
|
|
108
|
+
license = Ree.license
|
|
109
|
+
full_path = path.end_with?('.rb') ? path : "#{path}.rb"
|
|
110
|
+
encrypted_data = File.binread(full_path)
|
|
111
|
+
bytecode = license.decrypt_file(encrypted_data)
|
|
112
|
+
iseq = RubyVM::InstructionSequence.load_from_binary(bytecode)
|
|
113
|
+
iseq.eval
|
|
99
114
|
end
|
|
100
115
|
end
|
|
101
116
|
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Ree
|
|
4
|
+
module Licensing
|
|
5
|
+
class BytecodeCompiler
|
|
6
|
+
def self.compile_file(path)
|
|
7
|
+
iseq = RubyVM::InstructionSequence.compile_file(path)
|
|
8
|
+
iseq.to_binary
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def self.compile_string(source, path = "(eval)")
|
|
12
|
+
iseq = RubyVM::InstructionSequence.compile(source, path)
|
|
13
|
+
iseq.to_binary
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.load_from_binary(binary)
|
|
17
|
+
RubyVM::InstructionSequence.load_from_binary(binary)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
require 'openssl'
|
|
6
|
+
|
|
7
|
+
module Ree
|
|
8
|
+
module Licensing
|
|
9
|
+
class ClientStore
|
|
10
|
+
CLIENTS_FILE = 'clients.json'
|
|
11
|
+
|
|
12
|
+
def initialize(dir)
|
|
13
|
+
@dir = dir
|
|
14
|
+
@file_path = File.join(dir, CLIENTS_FILE)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def register_client(name:, contact:, metadata: {})
|
|
18
|
+
data = load_data
|
|
19
|
+
client_id = "client_#{SecureRandom.hex(8)}"
|
|
20
|
+
rsa_key = OpenSSL::PKey::RSA.new(4096)
|
|
21
|
+
|
|
22
|
+
client = {
|
|
23
|
+
'client_id' => client_id,
|
|
24
|
+
'name' => name,
|
|
25
|
+
'contact' => contact,
|
|
26
|
+
'metadata' => metadata,
|
|
27
|
+
'created_at' => Date.today.to_s,
|
|
28
|
+
'private_key_pem' => rsa_key.to_pem,
|
|
29
|
+
'public_key_pem' => rsa_key.public_key.to_pem,
|
|
30
|
+
'licenses' => []
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
data['clients'] << client
|
|
34
|
+
save_data(data)
|
|
35
|
+
|
|
36
|
+
client
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def find_client(client_id)
|
|
40
|
+
data = load_data
|
|
41
|
+
data['clients'].detect { |c| c['client_id'] == client_id }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_license(client_id, license)
|
|
45
|
+
data = load_data
|
|
46
|
+
client = data['clients'].detect { |c| c['client_id'] == client_id }
|
|
47
|
+
raise Ree::Error.new("Client #{client_id} not found") unless client
|
|
48
|
+
|
|
49
|
+
client['licenses'] << license
|
|
50
|
+
save_data(data)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def last_license(client_id)
|
|
54
|
+
client = find_client(client_id)
|
|
55
|
+
raise Ree::Error.new("Client #{client_id} not found") unless client
|
|
56
|
+
|
|
57
|
+
client['licenses'].last
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def load_data
|
|
61
|
+
if File.exist?(@file_path)
|
|
62
|
+
JSON.parse(File.read(@file_path))
|
|
63
|
+
else
|
|
64
|
+
{ 'clients' => [] }
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def save_data(data)
|
|
71
|
+
File.write(@file_path, JSON.pretty_generate(data))
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'base64'
|
|
5
|
+
require 'openssl'
|
|
6
|
+
require 'date'
|
|
7
|
+
|
|
8
|
+
module Ree
|
|
9
|
+
module Licensing
|
|
10
|
+
class Decryptor
|
|
11
|
+
attr_reader :aes_key, :iv, :expires_at, :client_id
|
|
12
|
+
|
|
13
|
+
def initialize(aes_key:, iv:, expires_at:, client_id:)
|
|
14
|
+
@aes_key = aes_key
|
|
15
|
+
@iv = iv
|
|
16
|
+
@expires_at = expires_at
|
|
17
|
+
@client_id = client_id
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.load_license(license_path)
|
|
21
|
+
unless File.exist?(license_path)
|
|
22
|
+
raise Ree::Error.new("License file not found: #{license_path}")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
license_data = JSON.parse(File.read(license_path))
|
|
26
|
+
public_key_pem = license_data['public_key_pem']
|
|
27
|
+
encrypted_payload = Base64.strict_decode64(license_data['encrypted_payload'])
|
|
28
|
+
|
|
29
|
+
payload_json = Encryptor.rsa_public_decrypt(encrypted_payload, public_key_pem)
|
|
30
|
+
payload = JSON.parse(payload_json)
|
|
31
|
+
|
|
32
|
+
expires_at = Date.parse(payload['expires_at'])
|
|
33
|
+
|
|
34
|
+
if expires_at < Date.today
|
|
35
|
+
raise Ree::Error.new("License expired on #{payload['expires_at']}")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
aes_key = [payload['aes_key_hex']].pack('H*')
|
|
39
|
+
iv = [payload['iv_hex']].pack('H*')
|
|
40
|
+
|
|
41
|
+
new(
|
|
42
|
+
aes_key: aes_key,
|
|
43
|
+
iv: iv,
|
|
44
|
+
expires_at: expires_at,
|
|
45
|
+
client_id: payload['client_id']
|
|
46
|
+
)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def decrypt_file(encrypted_data)
|
|
50
|
+
Encryptor.aes_decrypt(encrypted_data, @aes_key, @iv)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'openssl'
|
|
4
|
+
require 'securerandom'
|
|
5
|
+
require 'json'
|
|
6
|
+
require 'base64'
|
|
7
|
+
|
|
8
|
+
module Ree
|
|
9
|
+
module Licensing
|
|
10
|
+
class Encryptor
|
|
11
|
+
def self.generate_aes_key
|
|
12
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
|
13
|
+
cipher.encrypt
|
|
14
|
+
{
|
|
15
|
+
key: cipher.random_key,
|
|
16
|
+
iv: cipher.random_iv
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.aes_encrypt(data, key, iv)
|
|
21
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
|
22
|
+
cipher.encrypt
|
|
23
|
+
cipher.key = key
|
|
24
|
+
cipher.iv = iv
|
|
25
|
+
cipher.update(data) + cipher.final
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.aes_decrypt(encrypted_data, key, iv)
|
|
29
|
+
cipher = OpenSSL::Cipher::AES256.new(:CBC)
|
|
30
|
+
cipher.decrypt
|
|
31
|
+
cipher.key = key
|
|
32
|
+
cipher.iv = iv
|
|
33
|
+
cipher.update(encrypted_data) + cipher.final
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.rsa_private_encrypt(payload_json, private_key_pem)
|
|
37
|
+
rsa = OpenSSL::PKey::RSA.new(private_key_pem)
|
|
38
|
+
rsa.private_encrypt(payload_json)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def self.rsa_public_decrypt(encrypted_payload, public_key_pem)
|
|
42
|
+
rsa = OpenSSL::PKey::RSA.new(public_key_pem)
|
|
43
|
+
rsa.public_decrypt(encrypted_payload)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'base64'
|
|
5
|
+
require 'securerandom'
|
|
6
|
+
|
|
7
|
+
module Ree
|
|
8
|
+
module Licensing
|
|
9
|
+
class LicenseGenerator
|
|
10
|
+
def self.generate(client_id:, private_key_pem:, public_key_pem:, aes_key_hex:, iv_hex:, expires_at:)
|
|
11
|
+
payload = {
|
|
12
|
+
'aes_key_hex' => aes_key_hex,
|
|
13
|
+
'iv_hex' => iv_hex,
|
|
14
|
+
'expires_at' => expires_at,
|
|
15
|
+
'client_id' => client_id
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
payload_json = JSON.generate(payload)
|
|
19
|
+
encrypted_payload = Encryptor.rsa_private_encrypt(payload_json, private_key_pem)
|
|
20
|
+
|
|
21
|
+
license_file = {
|
|
22
|
+
'version' => 1,
|
|
23
|
+
'client_id' => client_id,
|
|
24
|
+
'public_key_pem' => public_key_pem,
|
|
25
|
+
'encrypted_payload' => Base64.strict_encode64(encrypted_payload)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
license_record = {
|
|
29
|
+
'license_id' => "lic_#{SecureRandom.hex(6)}",
|
|
30
|
+
'expires_at' => expires_at,
|
|
31
|
+
'aes_key_hex' => aes_key_hex,
|
|
32
|
+
'iv_hex' => iv_hex,
|
|
33
|
+
'created_at' => Date.today.to_s
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
{ license_file: license_file, license_record: license_record }
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'date'
|
|
6
|
+
|
|
7
|
+
module Ree
|
|
8
|
+
module Licensing
|
|
9
|
+
class Obfuscator
|
|
10
|
+
def self.run(source_path:, target_path:, client_id:, expires_at:, clients_dir:, exclude_files: [], stdout: $stdout)
|
|
11
|
+
new(
|
|
12
|
+
source_path: source_path,
|
|
13
|
+
target_path: target_path,
|
|
14
|
+
client_id: client_id,
|
|
15
|
+
expires_at: expires_at,
|
|
16
|
+
clients_dir: clients_dir,
|
|
17
|
+
exclude_files: exclude_files,
|
|
18
|
+
stdout: stdout
|
|
19
|
+
).run
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def initialize(source_path:, target_path:, client_id:, expires_at:, clients_dir:, exclude_files: [], stdout: $stdout)
|
|
23
|
+
@source_path = File.expand_path(source_path)
|
|
24
|
+
@target_path = File.expand_path(target_path)
|
|
25
|
+
@client_id = client_id
|
|
26
|
+
@expires_at = expires_at
|
|
27
|
+
@clients_dir = clients_dir
|
|
28
|
+
@exclude_files = exclude_files
|
|
29
|
+
@stdout = stdout
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def run
|
|
33
|
+
start_time = Time.now
|
|
34
|
+
|
|
35
|
+
store = ClientStore.new(@clients_dir)
|
|
36
|
+
client = store.find_client(@client_id)
|
|
37
|
+
raise Ree::Error.new("Client #{@client_id} not found in clients.json") unless client
|
|
38
|
+
|
|
39
|
+
copy_project
|
|
40
|
+
remove_spec_dirs
|
|
41
|
+
|
|
42
|
+
aes_data = Encryptor.generate_aes_key
|
|
43
|
+
aes_key = aes_data[:key]
|
|
44
|
+
iv = aes_data[:iv]
|
|
45
|
+
aes_key_hex = aes_key.unpack1('H*')
|
|
46
|
+
iv_hex = iv.unpack1('H*')
|
|
47
|
+
|
|
48
|
+
encrypted_count = encrypt_ruby_files(aes_key, iv)
|
|
49
|
+
|
|
50
|
+
result = LicenseGenerator.generate(
|
|
51
|
+
client_id: @client_id,
|
|
52
|
+
private_key_pem: client['private_key_pem'],
|
|
53
|
+
public_key_pem: client['public_key_pem'],
|
|
54
|
+
aes_key_hex: aes_key_hex,
|
|
55
|
+
iv_hex: iv_hex,
|
|
56
|
+
expires_at: @expires_at
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
license_path = File.join(@target_path, 'license.json')
|
|
60
|
+
File.write(license_path, JSON.pretty_generate(result[:license_file]))
|
|
61
|
+
|
|
62
|
+
store.add_license(@client_id, result[:license_record])
|
|
63
|
+
|
|
64
|
+
elapsed = (Time.now - start_time).round(2)
|
|
65
|
+
@stdout.puts "Obfuscation complete:"
|
|
66
|
+
@stdout.puts " Files encrypted: #{encrypted_count}"
|
|
67
|
+
@stdout.puts " License file: #{license_path}"
|
|
68
|
+
@stdout.puts " Time: #{elapsed}s"
|
|
69
|
+
|
|
70
|
+
{ encrypted_count: encrypted_count, license_path: license_path }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def copy_project
|
|
76
|
+
FileUtils.rm_rf(@target_path) if Dir.exist?(@target_path)
|
|
77
|
+
FileUtils.cp_r(@source_path, @target_path)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def remove_spec_dirs
|
|
81
|
+
Dir.glob(File.join(@target_path, '**/spec')).each do |spec_dir|
|
|
82
|
+
FileUtils.rm_rf(spec_dir) if File.directory?(spec_dir)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def encrypt_ruby_files(aes_key, iv)
|
|
87
|
+
count = 0
|
|
88
|
+
pattern = File.join(@target_path, '**/package/**/*.rb')
|
|
89
|
+
|
|
90
|
+
Dir.glob(pattern).each do |file_path|
|
|
91
|
+
basename = File.basename(file_path)
|
|
92
|
+
next if @exclude_files.include?(basename)
|
|
93
|
+
|
|
94
|
+
source = File.read(file_path)
|
|
95
|
+
if source.include?('require_relative')
|
|
96
|
+
raise Ree::Error.new("File contains require_relative: #{file_path}")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
bytecode = BytecodeCompiler.compile_file(file_path)
|
|
100
|
+
encrypted = Encryptor.aes_encrypt(bytecode, aes_key, iv)
|
|
101
|
+
File.binwrite(file_path, encrypted)
|
|
102
|
+
count += 1
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
count
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -8,14 +8,22 @@ module Ree::MethodAddedHook
|
|
|
8
8
|
|
|
9
9
|
@__ree_plugin_running = true
|
|
10
10
|
|
|
11
|
-
original_alias = :"__ree_original_#{name}"
|
|
12
|
-
remove_method(original_alias) if method_defined?(original_alias)
|
|
13
|
-
alias_method(original_alias, name)
|
|
14
|
-
|
|
15
11
|
begin
|
|
16
|
-
plugins
|
|
17
|
-
|
|
12
|
+
# Collect wrapper lambdas from plugins (some may return nil)
|
|
13
|
+
wrappers = plugins.map { |plugin_class| plugin_class.new(name, false, self).call }.compact
|
|
14
|
+
|
|
15
|
+
if wrappers.empty?
|
|
16
|
+
@__ree_plugin_running = false
|
|
17
|
+
return super
|
|
18
18
|
end
|
|
19
|
+
|
|
20
|
+
# Create single alias for original implementation
|
|
21
|
+
original_alias = :"__ree_original_#{name}"
|
|
22
|
+
remove_method(original_alias) if method_defined?(original_alias)
|
|
23
|
+
alias_method(original_alias, name)
|
|
24
|
+
|
|
25
|
+
# Compose all wrappers into a single method
|
|
26
|
+
compose_method(name, original_alias, wrappers)
|
|
19
27
|
ensure
|
|
20
28
|
@__ree_plugin_running = false
|
|
21
29
|
end
|
|
@@ -30,22 +38,106 @@ module Ree::MethodAddedHook
|
|
|
30
38
|
|
|
31
39
|
@__ree_singleton_plugin_running = true
|
|
32
40
|
|
|
33
|
-
original_alias = :"__ree_original_#{name}"
|
|
34
41
|
eigenclass = class << self; self; end
|
|
35
42
|
|
|
36
|
-
if eigenclass.method_defined?(original_alias)
|
|
37
|
-
eigenclass.remove_method(original_alias)
|
|
38
|
-
end
|
|
39
|
-
eigenclass.alias_method(original_alias, name)
|
|
40
|
-
|
|
41
43
|
begin
|
|
42
|
-
plugins
|
|
43
|
-
|
|
44
|
+
# Collect wrapper lambdas from plugins (some may return nil)
|
|
45
|
+
wrappers = plugins.map { |plugin_class| plugin_class.new(name, true, self).call }.compact
|
|
46
|
+
|
|
47
|
+
if wrappers.empty?
|
|
48
|
+
@__ree_singleton_plugin_running = false
|
|
49
|
+
return super
|
|
44
50
|
end
|
|
51
|
+
|
|
52
|
+
# Create single alias for original implementation
|
|
53
|
+
original_alias = :"__ree_original_#{name}"
|
|
54
|
+
eigenclass.remove_method(original_alias) if eigenclass.method_defined?(original_alias)
|
|
55
|
+
eigenclass.alias_method(original_alias, name)
|
|
56
|
+
|
|
57
|
+
# Compose all wrappers into a single method
|
|
58
|
+
compose_singleton_method(eigenclass, name, original_alias, wrappers)
|
|
45
59
|
ensure
|
|
46
60
|
@__ree_singleton_plugin_running = false
|
|
47
61
|
end
|
|
48
62
|
|
|
49
63
|
super
|
|
50
64
|
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def compose_method(method_name, original_alias, wrappers)
|
|
69
|
+
# Detect original method visibility
|
|
70
|
+
visibility = if private_method_defined?(original_alias)
|
|
71
|
+
:private
|
|
72
|
+
elsif protected_method_defined?(original_alias)
|
|
73
|
+
:protected
|
|
74
|
+
else
|
|
75
|
+
:public
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Build executor chain from inside out
|
|
79
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
|
80
|
+
# Innermost layer: call the original method
|
|
81
|
+
# Note: next_layer signature is: ->(){ ... } to be called with .call(*args, **kwargs, &block)
|
|
82
|
+
executor = ->(*a, **kw, &b) { send(original_alias, *a, **kw, &b) }
|
|
83
|
+
|
|
84
|
+
# Wrap from inside out (reverse to make first plugin outermost)
|
|
85
|
+
wrappers.reverse_each do |wrapper|
|
|
86
|
+
current_executor = executor
|
|
87
|
+
# Wrapper receives: (instance, next_layer, *args, **kwargs, &block)
|
|
88
|
+
# We need to create a next_layer that calls current_executor
|
|
89
|
+
next_layer = ->(*a, **kw, &b) { current_executor.call(*a, **kw, &b) }
|
|
90
|
+
executor = ->(*a, **kw, &b) { wrapper.call(self, next_layer, *a, **kw, &b) }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Execute the composed chain
|
|
94
|
+
executor.call(*args, **kwargs, &block)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Restore original visibility
|
|
98
|
+
case visibility
|
|
99
|
+
when :private
|
|
100
|
+
private method_name
|
|
101
|
+
when :protected
|
|
102
|
+
protected method_name
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def compose_singleton_method(eigenclass, method_name, original_alias, wrappers)
|
|
107
|
+
# Detect original method visibility
|
|
108
|
+
visibility = if eigenclass.private_method_defined?(original_alias)
|
|
109
|
+
:private
|
|
110
|
+
elsif eigenclass.protected_method_defined?(original_alias)
|
|
111
|
+
:protected
|
|
112
|
+
else
|
|
113
|
+
:public
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Build executor chain from inside out
|
|
117
|
+
eigenclass.define_method(method_name) do |*args, **kwargs, &block|
|
|
118
|
+
# Innermost layer: call the original method
|
|
119
|
+
# Note: next_layer signature is: ->(){ ... } to be called with .call(*args, **kwargs, &block)
|
|
120
|
+
executor = ->(*a, **kw, &b) { send(original_alias, *a, **kw, &b) }
|
|
121
|
+
|
|
122
|
+
# Wrap from inside out (reverse to make first plugin outermost)
|
|
123
|
+
wrappers.reverse_each do |wrapper|
|
|
124
|
+
current_executor = executor
|
|
125
|
+
# Wrapper receives: (instance, next_layer, *args, **kwargs, &block)
|
|
126
|
+
# We need to create a next_layer that calls current_executor
|
|
127
|
+
next_layer = ->(*a, **kw, &b) { current_executor.call(*a, **kw, &b) }
|
|
128
|
+
executor = ->(*a, **kw, &b) { wrapper.call(self, next_layer, *a, **kw, &b) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Execute the composed chain
|
|
132
|
+
executor.call(*args, **kwargs, &block)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Restore original visibility
|
|
136
|
+
case visibility
|
|
137
|
+
when :private
|
|
138
|
+
eigenclass.send(:private, method_name)
|
|
139
|
+
when :protected
|
|
140
|
+
eigenclass.send(:protected, method_name)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
51
143
|
end
|
data/lib/ree/version.rb
CHANGED
data/lib/ree.rb
CHANGED
|
@@ -53,6 +53,15 @@ module Ree
|
|
|
53
53
|
autoload :TemplateHandler, 'ree/handlers/template_handler'
|
|
54
54
|
autoload :TemplateRenderer, 'ree/templates/template_renderer'
|
|
55
55
|
|
|
56
|
+
module Licensing
|
|
57
|
+
autoload :ClientStore, 'ree/licensing/client_store'
|
|
58
|
+
autoload :Encryptor, 'ree/licensing/encryptor'
|
|
59
|
+
autoload :Decryptor, 'ree/licensing/decryptor'
|
|
60
|
+
autoload :BytecodeCompiler, 'ree/licensing/bytecode_compiler'
|
|
61
|
+
autoload :LicenseGenerator, 'ree/licensing/license_generator'
|
|
62
|
+
autoload :Obfuscator, 'ree/licensing/obfuscator'
|
|
63
|
+
end
|
|
64
|
+
|
|
56
65
|
PACKAGE = 'package'
|
|
57
66
|
SCHEMAS = 'schemas'
|
|
58
67
|
SCHEMA = 'schema'
|
|
@@ -124,6 +133,20 @@ module Ree
|
|
|
124
133
|
!!@benchmark_mode
|
|
125
134
|
end
|
|
126
135
|
|
|
136
|
+
def obfuscated?
|
|
137
|
+
ENV.has_key?('REE_LICENSE_KEY')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def license
|
|
141
|
+
@license ||= if obfuscated?
|
|
142
|
+
Ree::Licensing::Decryptor.load_license(ENV['REE_LICENSE_KEY'])
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def reset_license
|
|
147
|
+
@license = nil
|
|
148
|
+
end
|
|
149
|
+
|
|
127
150
|
def method_added_plugins
|
|
128
151
|
@method_added_plugins ||= []
|
|
129
152
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ree
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.2.
|
|
4
|
+
version: 1.2.7
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ruslan Gatiyatov
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: 1.6.5
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: base64
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: debug
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -86,6 +100,9 @@ files:
|
|
|
86
100
|
- lib/ree/cli/indexing/index_package.rb
|
|
87
101
|
- lib/ree/cli/indexing/index_project.rb
|
|
88
102
|
- lib/ree/cli/init.rb
|
|
103
|
+
- lib/ree/cli/licensing/obfuscate.rb
|
|
104
|
+
- lib/ree/cli/licensing/register_client.rb
|
|
105
|
+
- lib/ree/cli/licensing/renew_license.rb
|
|
89
106
|
- lib/ree/cli/spec_runner.rb
|
|
90
107
|
- lib/ree/container.rb
|
|
91
108
|
- lib/ree/contracts.rb
|
|
@@ -166,6 +183,12 @@ files:
|
|
|
166
183
|
- lib/ree/gen/package.rb
|
|
167
184
|
- lib/ree/handlers/template_handler.rb
|
|
168
185
|
- lib/ree/inspectable.rb
|
|
186
|
+
- lib/ree/licensing/bytecode_compiler.rb
|
|
187
|
+
- lib/ree/licensing/client_store.rb
|
|
188
|
+
- lib/ree/licensing/decryptor.rb
|
|
189
|
+
- lib/ree/licensing/encryptor.rb
|
|
190
|
+
- lib/ree/licensing/license_generator.rb
|
|
191
|
+
- lib/ree/licensing/obfuscator.rb
|
|
169
192
|
- lib/ree/link_dsl.rb
|
|
170
193
|
- lib/ree/method_added_hook.rb
|
|
171
194
|
- lib/ree/object_compiler.rb
|