bluedoc-license 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/bluedoc-license.rb +131 -0
- data/lib/bluedoc/license/boundary.rb +42 -0
- data/lib/bluedoc/license/encryptor.rb +74 -0
- data/lib/bluedoc/license/version.rb +7 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4fc049f5457a7af6e3a05758bed8369d787d199c9ac2ef45a0e6e21350127c59
|
4
|
+
data.tar.gz: f2373552d83d44f35473b3c5319300b106b278bf23ac9bebe9eec6c21a7c1244
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '0269c8d3b4ef9b7138725982aa7ffe5f933f79027103e574b89949def4c2f65ff8b874e5a60fa813dfd1771e898ec36f53acd643ade77c1926f8590b513869cc'
|
7
|
+
data.tar.gz: a7cb8605a08201bee63fe23804be1a01f795b7145182d43f6dbee95b893e7ba87c8805630d7162d973c4a51fe553a11f598d78fbc706c235854187880acdb26b
|
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "openssl"
|
4
|
+
require "date"
|
5
|
+
require "json"
|
6
|
+
require "base64"
|
7
|
+
|
8
|
+
require "bluedoc/license/version"
|
9
|
+
require "bluedoc/license/encryptor"
|
10
|
+
require "bluedoc/license/boundary"
|
11
|
+
|
12
|
+
module BlueDoc
|
13
|
+
class License
|
14
|
+
class Error < StandardError; end
|
15
|
+
class ImportError < Error; end
|
16
|
+
class ValidationError < Error; end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_reader :encryption_key
|
20
|
+
@encryption_key = nil
|
21
|
+
|
22
|
+
def encryption_key=(key)
|
23
|
+
if key && !key.is_a?(OpenSSL::PKey::RSA)
|
24
|
+
raise ArgumentError, "No RSA encryption key provided."
|
25
|
+
end
|
26
|
+
|
27
|
+
@encryption_key = key
|
28
|
+
@encryptor = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def encryptor
|
32
|
+
@encryptor ||= Encryptor.new(self.encryption_key)
|
33
|
+
end
|
34
|
+
|
35
|
+
def import(data)
|
36
|
+
if data.nil?
|
37
|
+
raise ImportError, "No license data."
|
38
|
+
end
|
39
|
+
|
40
|
+
data = Boundary.remove_boundary(data)
|
41
|
+
|
42
|
+
begin
|
43
|
+
license_json = encryptor.decrypt(data)
|
44
|
+
rescue Encryptor::Error
|
45
|
+
raise ImportError, "License data could not be decrypted."
|
46
|
+
end
|
47
|
+
|
48
|
+
begin
|
49
|
+
attributes = JSON.parse(license_json)
|
50
|
+
rescue JSON::ParseError
|
51
|
+
raise ImportError, "License data is invalid JSON."
|
52
|
+
end
|
53
|
+
|
54
|
+
new(attributes)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_reader :version
|
59
|
+
attr_accessor :licensee, :starts_at, :expires_at
|
60
|
+
attr_accessor :restrictions, :features
|
61
|
+
|
62
|
+
def initialize(attributes = {})
|
63
|
+
load_attributes(attributes)
|
64
|
+
end
|
65
|
+
|
66
|
+
def valid?
|
67
|
+
return false if !licensee || !licensee.is_a?(Hash) || licensee.length == 0
|
68
|
+
return false if !starts_at || !starts_at.is_a?(Date)
|
69
|
+
return false if expires_at && !expires_at.is_a?(Date)
|
70
|
+
return false if restrictions && !restrictions.is_a?(Hash)
|
71
|
+
return false if features && !features.is_a?(Array)
|
72
|
+
|
73
|
+
true
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate!
|
77
|
+
raise ValidationError, "License is invalid" unless valid?
|
78
|
+
end
|
79
|
+
|
80
|
+
def will_expire?
|
81
|
+
self.expires_at == nil ? false : true
|
82
|
+
end
|
83
|
+
|
84
|
+
def expired?
|
85
|
+
will_expire? && Date.today >= self.expires_at
|
86
|
+
end
|
87
|
+
|
88
|
+
def restricted?(key = nil)
|
89
|
+
if key
|
90
|
+
restricted? && restrictions.has_key?(key)
|
91
|
+
else
|
92
|
+
restrictions && restrictions.length >= 1
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def allow_feature?(key)
|
97
|
+
self.features.include?(key.to_s)
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def load_attributes(attributes)
|
103
|
+
attributes = Hash[attributes.map { |k, v| [k.to_s, v] }]
|
104
|
+
|
105
|
+
version = attributes["version"] || 1
|
106
|
+
unless version && version == 1
|
107
|
+
raise ArgumentError, "Version is too new"
|
108
|
+
end
|
109
|
+
|
110
|
+
@version = version
|
111
|
+
|
112
|
+
@licensee = attributes["licensee"]
|
113
|
+
@features = attributes["features"] || []
|
114
|
+
|
115
|
+
%w(starts_at expires_at).each do |attr|
|
116
|
+
value = attributes[attr]
|
117
|
+
value = Date.parse(value) rescue nil if value.is_a?(String)
|
118
|
+
|
119
|
+
next unless value
|
120
|
+
|
121
|
+
send("#{attr}=", value)
|
122
|
+
end
|
123
|
+
|
124
|
+
restrictions = attributes["restrictions"]
|
125
|
+
if restrictions && restrictions.is_a?(Hash)
|
126
|
+
restrictions = Hash[restrictions.map { |k, v| [k.to_sym, v] }]
|
127
|
+
@restrictions = restrictions
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BlueDoc
|
4
|
+
class License
|
5
|
+
module Boundary
|
6
|
+
BOUNDARY_START = /(\A|\r?\n)-*BEGIN .+? LICENSE-*\r?\n/.freeze
|
7
|
+
BOUNDARY_END = /\r?\n-*END .+? LICENSE-*(\r?\n|\z)/.freeze
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def add_boundary(data, product_name)
|
11
|
+
data = remove_boundary(data)
|
12
|
+
|
13
|
+
product_name.upcase!
|
14
|
+
|
15
|
+
pad = lambda do |message, width|
|
16
|
+
total_padding = [width - message.length, 0].max
|
17
|
+
|
18
|
+
padding = total_padding / 2.0
|
19
|
+
[
|
20
|
+
"-" * padding.ceil,
|
21
|
+
message,
|
22
|
+
"-" * padding.floor
|
23
|
+
].join
|
24
|
+
end
|
25
|
+
|
26
|
+
[
|
27
|
+
pad.call("BEGIN #{product_name} LICENSE", 60),
|
28
|
+
data.strip,
|
29
|
+
pad.call("END #{product_name} LICENSE", 60)
|
30
|
+
].join("\n")
|
31
|
+
end
|
32
|
+
|
33
|
+
def remove_boundary(data)
|
34
|
+
after_boundary = data.split(BOUNDARY_START).last
|
35
|
+
in_boundary = after_boundary.split(BOUNDARY_END).first
|
36
|
+
|
37
|
+
in_boundary
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module BlueDoc
|
4
|
+
class License
|
5
|
+
class Encryptor
|
6
|
+
class Error < StandardError; end
|
7
|
+
class KeyError < Error; end
|
8
|
+
class DecryptionError < Error; end
|
9
|
+
|
10
|
+
attr_accessor :key
|
11
|
+
|
12
|
+
def initialize(key)
|
13
|
+
if key && !key.is_a?(OpenSSL::PKey::RSA)
|
14
|
+
raise KeyError, "No RSA encryption key provided."
|
15
|
+
end
|
16
|
+
|
17
|
+
@key = key
|
18
|
+
end
|
19
|
+
|
20
|
+
def decrypt(data)
|
21
|
+
unless key.public?
|
22
|
+
raise KeyError, "Provided key is not a public key."
|
23
|
+
end
|
24
|
+
|
25
|
+
json_data = Base64.decode64(data.chomp)
|
26
|
+
|
27
|
+
begin
|
28
|
+
encryption_data = JSON.parse(json_data)
|
29
|
+
rescue JSON::ParserError
|
30
|
+
raise DecryptionError, "Encryption data is invalid JSON."
|
31
|
+
end
|
32
|
+
|
33
|
+
unless %w(data key iv).all? { |key| encryption_data[key] }
|
34
|
+
raise DecryptionError, "Required field missing from encryption data."
|
35
|
+
end
|
36
|
+
|
37
|
+
encrypted_data = Base64.decode64(encryption_data["data"])
|
38
|
+
encrypted_key = Base64.decode64(encryption_data["key"])
|
39
|
+
aes_iv = Base64.decode64(encryption_data["iv"])
|
40
|
+
|
41
|
+
begin
|
42
|
+
# Decrypt the AES key using asymmetric RSA encryption.
|
43
|
+
aes_key = self.key.public_decrypt(encrypted_key)
|
44
|
+
rescue OpenSSL::PKey::RSAError
|
45
|
+
raise DecryptionError, "AES encryption key could not be decrypted."
|
46
|
+
end
|
47
|
+
|
48
|
+
# Decrypt the data using symmetric AES encryption.
|
49
|
+
cipher = OpenSSL::Cipher::AES128.new(:CBC)
|
50
|
+
cipher.decrypt
|
51
|
+
|
52
|
+
begin
|
53
|
+
cipher.key = aes_key
|
54
|
+
rescue OpenSSL::Cipher::CipherError
|
55
|
+
raise DecryptionError, "AES encryption key is invalid."
|
56
|
+
end
|
57
|
+
|
58
|
+
begin
|
59
|
+
cipher.iv = aes_iv
|
60
|
+
rescue OpenSSL::Cipher::CipherError
|
61
|
+
raise DecryptionError, "AES IV is invalid."
|
62
|
+
end
|
63
|
+
|
64
|
+
begin
|
65
|
+
data = cipher.update(encrypted_data) + cipher.final
|
66
|
+
rescue OpenSSL::Cipher::CipherError
|
67
|
+
raise DecryptionError, "Data could not be decrypted."
|
68
|
+
end
|
69
|
+
|
70
|
+
data
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bluedoc-license
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jason Lee
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-03-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: byebug
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description:
|
56
|
+
email:
|
57
|
+
- huacnlee@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- lib/bluedoc-license.rb
|
63
|
+
- lib/bluedoc/license/boundary.rb
|
64
|
+
- lib/bluedoc/license/encryptor.rb
|
65
|
+
- lib/bluedoc/license/version.rb
|
66
|
+
homepage: https://bluedoc.org
|
67
|
+
licenses:
|
68
|
+
- MIT
|
69
|
+
metadata: {}
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
requirements: []
|
85
|
+
rubygems_version: 3.0.1
|
86
|
+
signing_key:
|
87
|
+
specification_version: 4
|
88
|
+
summary: bluedoc-license helps you generate, verify and enforce software licenses.
|
89
|
+
test_files: []
|