gitlab-license-plain 1.0.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/gitlab/license/boundary.rb +40 -0
- data/lib/gitlab/license/encryptor.rb +92 -0
- data/lib/gitlab/license/version.rb +5 -0
- data/lib/gitlab/license.rb +260 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5b42bef9bfea6e3ae767ae96c24ee93c2bf67a05252afd7a7e935800b6bfbaa9
|
4
|
+
data.tar.gz: d22767967ed75302255cbfd29e1839217135163e2193ca3ae3fa92cda1ca1128
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c55b971d50997b23048ef582ebbe725b039bacf160a54e68f0b93bdcbe91b2091c0e9239a9344def9ea89ff458280900d9b249452f35779ea5bc8b2ee0665651
|
7
|
+
data.tar.gz: 5db2778d58dd8c440163da50cb0b95a2a448ebb87380abf39f4d7eda912a48b3ba416e8240e86210ede6ccd94198556017e9f7cc126c4c06d525e38a28a3cf74
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Gitlab
|
2
|
+
class License
|
3
|
+
module Boundary
|
4
|
+
BOUNDARY_START = /(\A|\r?\n)-*BEGIN .+? LICENSE-*\r?\n/.freeze
|
5
|
+
BOUNDARY_END = /\r?\n-*END .+? LICENSE-*(\r?\n|\z)/.freeze
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def add_boundary(data, product_name)
|
9
|
+
data = remove_boundary(data)
|
10
|
+
|
11
|
+
product_name.upcase!
|
12
|
+
|
13
|
+
pad = lambda do |message, width|
|
14
|
+
total_padding = [width - message.length, 0].max
|
15
|
+
|
16
|
+
padding = total_padding / 2.0
|
17
|
+
[
|
18
|
+
'-' * padding.ceil,
|
19
|
+
message,
|
20
|
+
'-' * padding.floor
|
21
|
+
].join
|
22
|
+
end
|
23
|
+
|
24
|
+
[
|
25
|
+
pad.call("BEGIN #{product_name} LICENSE", 60),
|
26
|
+
data.strip,
|
27
|
+
pad.call("END #{product_name} LICENSE", 60)
|
28
|
+
].join("\n")
|
29
|
+
end
|
30
|
+
|
31
|
+
def remove_boundary(data)
|
32
|
+
after_boundary = data.split(BOUNDARY_START).last
|
33
|
+
in_boundary = after_boundary.split(BOUNDARY_END).first
|
34
|
+
|
35
|
+
in_boundary
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Gitlab
|
2
|
+
class License
|
3
|
+
class Encryptor
|
4
|
+
class Error < StandardError; end
|
5
|
+
class KeyError < Error; end
|
6
|
+
class DecryptionError < Error; end
|
7
|
+
|
8
|
+
attr_accessor :key
|
9
|
+
|
10
|
+
def initialize(key)
|
11
|
+
raise KeyError, 'No RSA encryption key provided.' if key && !key.is_a?(OpenSSL::PKey::RSA)
|
12
|
+
|
13
|
+
@key = key
|
14
|
+
end
|
15
|
+
|
16
|
+
def encrypt(data)
|
17
|
+
raise KeyError, 'Provided key is not a private key.' unless key.private?
|
18
|
+
|
19
|
+
# Encrypt the data using symmetric AES encryption.
|
20
|
+
cipher = OpenSSL::Cipher::AES128.new(:CBC)
|
21
|
+
cipher.encrypt
|
22
|
+
aes_key = cipher.random_key
|
23
|
+
aes_iv = cipher.random_iv
|
24
|
+
|
25
|
+
encrypted_data = cipher.update(data) + cipher.final
|
26
|
+
|
27
|
+
# Encrypt the AES key using asymmetric RSA encryption.
|
28
|
+
encrypted_key = key.private_encrypt(aes_key)
|
29
|
+
|
30
|
+
encryption_data = {
|
31
|
+
'data' => Base64.encode64(encrypted_data),
|
32
|
+
'key' => Base64.encode64(encrypted_key),
|
33
|
+
'iv' => Base64.encode64(aes_iv)
|
34
|
+
}
|
35
|
+
|
36
|
+
json_data = JSON.dump(encryption_data)
|
37
|
+
Base64.encode64(json_data)
|
38
|
+
end
|
39
|
+
|
40
|
+
def decrypt(data)
|
41
|
+
raise KeyError, 'Provided key is not a public key.' unless key.public?
|
42
|
+
|
43
|
+
json_data = Base64.decode64(data.chomp)
|
44
|
+
|
45
|
+
begin
|
46
|
+
encryption_data = JSON.parse(json_data)
|
47
|
+
rescue JSON::ParserError
|
48
|
+
raise DecryptionError, 'Encryption data is invalid JSON.'
|
49
|
+
end
|
50
|
+
|
51
|
+
unless %w[data key iv].all? { |key| encryption_data[key] }
|
52
|
+
raise DecryptionError, 'Required field missing from encryption data.'
|
53
|
+
end
|
54
|
+
|
55
|
+
encrypted_data = Base64.decode64(encryption_data['data'])
|
56
|
+
encrypted_key = Base64.decode64(encryption_data['key'])
|
57
|
+
aes_iv = Base64.decode64(encryption_data['iv'])
|
58
|
+
|
59
|
+
begin
|
60
|
+
# Decrypt the AES key using asymmetric RSA encryption.
|
61
|
+
aes_key = self.key.public_decrypt(encrypted_key)
|
62
|
+
rescue OpenSSL::PKey::RSAError
|
63
|
+
raise DecryptionError, 'AES encryption key could not be decrypted.'
|
64
|
+
end
|
65
|
+
|
66
|
+
# Decrypt the data using symmetric AES encryption.
|
67
|
+
cipher = OpenSSL::Cipher::AES128.new(:CBC)
|
68
|
+
cipher.decrypt
|
69
|
+
|
70
|
+
begin
|
71
|
+
cipher.key = aes_key
|
72
|
+
rescue OpenSSL::Cipher::CipherError
|
73
|
+
raise DecryptionError, 'AES encryption key is invalid.'
|
74
|
+
end
|
75
|
+
|
76
|
+
begin
|
77
|
+
cipher.iv = aes_iv
|
78
|
+
rescue OpenSSL::Cipher::CipherError
|
79
|
+
raise DecryptionError, 'AES IV is invalid.'
|
80
|
+
end
|
81
|
+
|
82
|
+
begin
|
83
|
+
data = cipher.update(encrypted_data) + cipher.final
|
84
|
+
rescue OpenSSL::Cipher::CipherError
|
85
|
+
raise DecryptionError, 'Data could not be decrypted.'
|
86
|
+
end
|
87
|
+
|
88
|
+
data
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,260 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'date'
|
3
|
+
require 'json'
|
4
|
+
require 'base64'
|
5
|
+
|
6
|
+
require 'gitlab/license/version'
|
7
|
+
require 'gitlab/license/encryptor'
|
8
|
+
require 'gitlab/license/boundary'
|
9
|
+
|
10
|
+
module Gitlab
|
11
|
+
class License
|
12
|
+
class Error < StandardError; end
|
13
|
+
class ImportError < Error; end
|
14
|
+
class ValidationError < Error; end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
attr_reader :encryption_key
|
18
|
+
@encryption_key = nil
|
19
|
+
|
20
|
+
def encryption_key=(key)
|
21
|
+
raise ArgumentError, 'No RSA encryption key provided.' if key && !key.is_a?(OpenSSL::PKey::RSA)
|
22
|
+
|
23
|
+
@encryption_key = key
|
24
|
+
@encryptor = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def encryptor
|
28
|
+
@encryptor ||= Encryptor.new(encryption_key)
|
29
|
+
end
|
30
|
+
|
31
|
+
def import(data)
|
32
|
+
raise ImportError, 'No license data.' if data.nil?
|
33
|
+
|
34
|
+
begin
|
35
|
+
attributes = JSON.parse(data)
|
36
|
+
rescue JSON::ParseError
|
37
|
+
raise ImportError, 'License data is invalid JSON.'
|
38
|
+
end
|
39
|
+
|
40
|
+
new(attributes)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
attr_reader :version
|
45
|
+
attr_accessor :licensee, :starts_at, :expires_at, :notify_admins_at,
|
46
|
+
:notify_users_at, :block_changes_at, :last_synced_at, :next_sync_at,
|
47
|
+
:activated_at, :restrictions, :cloud_licensing_enabled,
|
48
|
+
:offline_cloud_licensing_enabled, :auto_renew_enabled, :seat_reconciliation_enabled,
|
49
|
+
:operational_metrics_enabled, :generated_from_customers_dot
|
50
|
+
|
51
|
+
alias_method :issued_at, :starts_at
|
52
|
+
alias_method :issued_at=, :starts_at=
|
53
|
+
|
54
|
+
def initialize(attributes = {})
|
55
|
+
load_attributes(attributes)
|
56
|
+
end
|
57
|
+
|
58
|
+
def valid?
|
59
|
+
if !licensee || !licensee.is_a?(Hash) || licensee.empty?
|
60
|
+
false
|
61
|
+
elsif !starts_at || !starts_at.is_a?(Date)
|
62
|
+
false
|
63
|
+
elsif expires_at && !expires_at.is_a?(Date)
|
64
|
+
false
|
65
|
+
elsif notify_admins_at && !notify_admins_at.is_a?(Date)
|
66
|
+
false
|
67
|
+
elsif notify_users_at && !notify_users_at.is_a?(Date)
|
68
|
+
false
|
69
|
+
elsif block_changes_at && !block_changes_at.is_a?(Date)
|
70
|
+
false
|
71
|
+
elsif last_synced_at && !last_synced_at.is_a?(DateTime)
|
72
|
+
false
|
73
|
+
elsif next_sync_at && !next_sync_at.is_a?(DateTime)
|
74
|
+
false
|
75
|
+
elsif activated_at && !activated_at.is_a?(DateTime)
|
76
|
+
false
|
77
|
+
elsif restrictions && !restrictions.is_a?(Hash)
|
78
|
+
false
|
79
|
+
elsif !cloud_licensing? && offline_cloud_licensing?
|
80
|
+
false
|
81
|
+
else
|
82
|
+
true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def validate!
|
87
|
+
raise ValidationError, 'License is invalid' unless valid?
|
88
|
+
end
|
89
|
+
|
90
|
+
def will_expire?
|
91
|
+
expires_at
|
92
|
+
end
|
93
|
+
|
94
|
+
def will_notify_admins?
|
95
|
+
notify_admins_at
|
96
|
+
end
|
97
|
+
|
98
|
+
def will_notify_users?
|
99
|
+
notify_users_at
|
100
|
+
end
|
101
|
+
|
102
|
+
def will_block_changes?
|
103
|
+
block_changes_at
|
104
|
+
end
|
105
|
+
|
106
|
+
def will_sync?
|
107
|
+
next_sync_at
|
108
|
+
end
|
109
|
+
|
110
|
+
def activated?
|
111
|
+
activated_at
|
112
|
+
end
|
113
|
+
|
114
|
+
def expired?
|
115
|
+
will_expire? && Date.today >= expires_at
|
116
|
+
end
|
117
|
+
|
118
|
+
def notify_admins?
|
119
|
+
will_notify_admins? && Date.today >= notify_admins_at
|
120
|
+
end
|
121
|
+
|
122
|
+
def notify_users?
|
123
|
+
will_notify_users? && Date.today >= notify_users_at
|
124
|
+
end
|
125
|
+
|
126
|
+
def block_changes?
|
127
|
+
will_block_changes? && Date.today >= block_changes_at
|
128
|
+
end
|
129
|
+
|
130
|
+
def cloud_licensing?
|
131
|
+
cloud_licensing_enabled == true
|
132
|
+
end
|
133
|
+
|
134
|
+
def offline_cloud_licensing?
|
135
|
+
offline_cloud_licensing_enabled == true
|
136
|
+
end
|
137
|
+
|
138
|
+
def auto_renew?
|
139
|
+
auto_renew_enabled == true
|
140
|
+
end
|
141
|
+
|
142
|
+
def seat_reconciliation?
|
143
|
+
seat_reconciliation_enabled == true
|
144
|
+
end
|
145
|
+
|
146
|
+
def operational_metrics?
|
147
|
+
operational_metrics_enabled == true
|
148
|
+
end
|
149
|
+
|
150
|
+
def generated_from_customers_dot?
|
151
|
+
generated_from_customers_dot == true
|
152
|
+
end
|
153
|
+
|
154
|
+
def restricted?(key = nil)
|
155
|
+
if key
|
156
|
+
restricted? && restrictions.has_key?(key)
|
157
|
+
else
|
158
|
+
restrictions && restrictions.length >= 1
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def attributes
|
163
|
+
hash = {}
|
164
|
+
|
165
|
+
hash['version'] = version
|
166
|
+
hash['licensee'] = licensee
|
167
|
+
|
168
|
+
# `issued_at` is the legacy name for starts_at.
|
169
|
+
# TODO: Move to starts_at in a next version.
|
170
|
+
hash['issued_at'] = starts_at
|
171
|
+
hash['expires_at'] = expires_at if will_expire?
|
172
|
+
|
173
|
+
hash['notify_admins_at'] = notify_admins_at if will_notify_admins?
|
174
|
+
hash['notify_users_at'] = notify_users_at if will_notify_users?
|
175
|
+
hash['block_changes_at'] = block_changes_at if will_block_changes?
|
176
|
+
|
177
|
+
hash['next_sync_at'] = next_sync_at if will_sync?
|
178
|
+
hash['last_synced_at'] = last_synced_at if will_sync?
|
179
|
+
hash['activated_at'] = activated_at if activated?
|
180
|
+
|
181
|
+
hash['cloud_licensing_enabled'] = cloud_licensing?
|
182
|
+
hash['offline_cloud_licensing_enabled'] = offline_cloud_licensing?
|
183
|
+
hash['auto_renew_enabled'] = auto_renew?
|
184
|
+
hash['seat_reconciliation_enabled'] = seat_reconciliation?
|
185
|
+
hash['operational_metrics_enabled'] = operational_metrics?
|
186
|
+
|
187
|
+
hash['generated_from_customers_dot'] = generated_from_customers_dot?
|
188
|
+
|
189
|
+
hash['restrictions'] = restrictions if restricted?
|
190
|
+
|
191
|
+
hash
|
192
|
+
end
|
193
|
+
|
194
|
+
def to_json(*_args)
|
195
|
+
JSON.dump(attributes)
|
196
|
+
end
|
197
|
+
|
198
|
+
def export(boundary: nil)
|
199
|
+
validate!
|
200
|
+
|
201
|
+
data = self.class.encryptor.encrypt(to_json)
|
202
|
+
|
203
|
+
data = Boundary.add_boundary(data, boundary) if boundary
|
204
|
+
|
205
|
+
data
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
|
210
|
+
def load_attributes(attributes)
|
211
|
+
attributes = attributes.transform_keys(&:to_s)
|
212
|
+
|
213
|
+
version = attributes['version'] || 1
|
214
|
+
raise ArgumentError, 'Version is too new' unless version && version == 1
|
215
|
+
|
216
|
+
@version = version
|
217
|
+
|
218
|
+
@licensee = attributes['licensee']
|
219
|
+
|
220
|
+
# `issued_at` is the legacy name for starts_at.
|
221
|
+
# TODO: Move to starts_at in a next version.
|
222
|
+
%w[issued_at expires_at notify_admins_at notify_users_at block_changes_at].each do |attr_name|
|
223
|
+
set_date_attribute(attr_name, attributes[attr_name])
|
224
|
+
end
|
225
|
+
|
226
|
+
%w[last_synced_at next_sync_at activated_at].each do |attr_name|
|
227
|
+
set_datetime_attribute(attr_name, attributes[attr_name])
|
228
|
+
end
|
229
|
+
|
230
|
+
%w[
|
231
|
+
cloud_licensing_enabled
|
232
|
+
offline_cloud_licensing_enabled
|
233
|
+
auto_renew_enabled
|
234
|
+
seat_reconciliation_enabled
|
235
|
+
operational_metrics_enabled
|
236
|
+
generated_from_customers_dot
|
237
|
+
].each do |attr_name|
|
238
|
+
public_send("#{attr_name}=", attributes[attr_name] == true)
|
239
|
+
end
|
240
|
+
|
241
|
+
restrictions = attributes['restrictions']
|
242
|
+
if restrictions&.is_a?(Hash)
|
243
|
+
restrictions = restrictions.transform_keys(&:to_sym)
|
244
|
+
@restrictions = restrictions
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def set_date_attribute(attr_name, value, date_class = Date)
|
249
|
+
value = date_class.parse(value) rescue nil if value.is_a?(String)
|
250
|
+
|
251
|
+
return unless value
|
252
|
+
|
253
|
+
public_send("#{attr_name}=", value)
|
254
|
+
end
|
255
|
+
|
256
|
+
def set_datetime_attribute(attr_name, value)
|
257
|
+
set_date_attribute(attr_name, value, DateTime)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gitlab-license-plain
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Satoshi Nakamoto
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-01-30 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: '2.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: byebug
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '10.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '10.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.9'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.9'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubocop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.80.1
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.80.1
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop-rspec
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.38.1
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.38.1
|
97
|
+
description:
|
98
|
+
email:
|
99
|
+
- satoshi@nakamoto.com
|
100
|
+
executables: []
|
101
|
+
extensions: []
|
102
|
+
extra_rdoc_files: []
|
103
|
+
files:
|
104
|
+
- lib/gitlab/license.rb
|
105
|
+
- lib/gitlab/license/boundary.rb
|
106
|
+
- lib/gitlab/license/encryptor.rb
|
107
|
+
- lib/gitlab/license/version.rb
|
108
|
+
homepage: https://github.com/andreimerfu/gitlab-license-plain
|
109
|
+
licenses:
|
110
|
+
- MIT
|
111
|
+
metadata: {}
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - ">="
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: 2.3.0
|
121
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - ">="
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
requirements: []
|
127
|
+
rubygems_version: 3.1.6
|
128
|
+
signing_key:
|
129
|
+
specification_version: 4
|
130
|
+
summary: Gitlab Enterprise license
|
131
|
+
test_files: []
|