ree 1.2.3 → 1.2.6
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 +23 -8
- data/lib/ree/benchmark_tracer.rb +5 -0
- 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/core/package_loader.rb +16 -1
- data/lib/ree/dsl/object_dsl.rb +5 -0
- data/lib/ree/fn_dsl.rb +0 -3
- 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/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: c4b0c989272201bf193600af16a5d4010842b6d169c4cd97afc9af4bea38d4cd
|
|
4
|
+
data.tar.gz: 0723ffdd0c7fdfb7220348d7bad7d0af8691f66330c24d64ff9e4d52300802dc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ca5e021ac6507f3679d44fd7fd48068f8918a5990fbd2b993f19e55db06cc1e80c42a23796a9962cdb29b1e20a2e979d7d7765030ca58997979fc7a8bf5cac1d
|
|
7
|
+
data.tar.gz: d12e9983fea63b43385e686072922b3f8f7bc5dc0e5864a276c3d80d7fcd31e9021ed6c8337814ada8c75cd76add04eab81a903df747833b0bcc69ea30a22e78
|
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,14 +12,14 @@ class Ree::BenchmarkMethodPlugin
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def call
|
|
15
|
-
|
|
15
|
+
return unless @method_name == :call && ree_fn?
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
17
|
+
config = @target.instance_variable_get(:@__ree_benchmark_config)
|
|
18
|
+
|
|
19
|
+
if config
|
|
20
|
+
wrap_as_entry_point(config)
|
|
21
|
+
else
|
|
22
|
+
wrap_as_collector
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
|
|
@@ -41,7 +41,11 @@ class Ree::BenchmarkMethodPlugin
|
|
|
41
41
|
benchmark_done = false
|
|
42
42
|
|
|
43
43
|
alias_target.define_method(method_name) do |*args, **kwargs, &block|
|
|
44
|
-
if
|
|
44
|
+
if Ree::BenchmarkTracer.active?
|
|
45
|
+
Ree::BenchmarkTracer.collect(benchmark_name) do
|
|
46
|
+
send(method_alias, *args, **kwargs, &block)
|
|
47
|
+
end
|
|
48
|
+
elsif once && benchmark_done
|
|
45
49
|
Ree::BenchmarkTracer.collect(benchmark_name) do
|
|
46
50
|
send(method_alias, *args, **kwargs, &block)
|
|
47
51
|
end
|
|
@@ -85,6 +89,17 @@ class Ree::BenchmarkMethodPlugin
|
|
|
85
89
|
@method_name == :call ? base : "#{base}##{@method_name}"
|
|
86
90
|
end
|
|
87
91
|
|
|
92
|
+
def ree_fn?
|
|
93
|
+
pkg = @target.instance_variable_get(:@__ree_package_name)
|
|
94
|
+
obj = @target.instance_variable_get(:@__ree_object_name)
|
|
95
|
+
return false unless pkg && obj
|
|
96
|
+
|
|
97
|
+
facade = Ree.container.packages_facade
|
|
98
|
+
return false unless facade.has_object?(pkg, obj)
|
|
99
|
+
|
|
100
|
+
facade.get_object(pkg, obj).fn?
|
|
101
|
+
end
|
|
102
|
+
|
|
88
103
|
def eigenclass
|
|
89
104
|
class << @target; self; end
|
|
90
105
|
end
|
data/lib/ree/benchmark_tracer.rb
CHANGED
|
@@ -34,6 +34,11 @@ class Ree::BenchmarkTracer
|
|
|
34
34
|
result
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
+
def active?
|
|
38
|
+
stack = Thread.current[THREAD_KEY]
|
|
39
|
+
stack && !stack.empty?
|
|
40
|
+
end
|
|
41
|
+
|
|
37
42
|
# Collector trace — only participates if a trace is already active
|
|
38
43
|
def collect(name)
|
|
39
44
|
stack = Thread.current[THREAD_KEY]
|
|
@@ -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
|
|
@@ -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
|
|
data/lib/ree/dsl/object_dsl.rb
CHANGED
|
@@ -333,6 +333,11 @@ class Ree::ObjectDsl
|
|
|
333
333
|
.set_package(package.name)
|
|
334
334
|
.set_rpath(object_rpath)
|
|
335
335
|
|
|
336
|
+
if mount_as == :fn
|
|
337
|
+
klass.instance_variable_set(:@__ree_package_name, package.name)
|
|
338
|
+
klass.instance_variable_set(:@__ree_object_name, object_name)
|
|
339
|
+
end
|
|
340
|
+
|
|
336
341
|
package.set_object(object)
|
|
337
342
|
object.set_loaded
|
|
338
343
|
|
data/lib/ree/fn_dsl.rb
CHANGED
|
@@ -21,9 +21,6 @@ module Ree::FnDSL
|
|
|
21
21
|
dsl.tags(["fn"])
|
|
22
22
|
dsl.object.set_as_compiled(false)
|
|
23
23
|
|
|
24
|
-
self.instance_variable_set(:@__ree_package_name, dsl.package.name)
|
|
25
|
-
self.instance_variable_set(:@__ree_object_name, name)
|
|
26
|
-
|
|
27
24
|
Ree.container.compile(dsl.package, name)
|
|
28
25
|
end
|
|
29
26
|
end
|
|
@@ -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
|
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.6
|
|
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
|