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 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,5 @@
1
+ module Gitlab
2
+ class License
3
+ VERSION = '1.0.0'.freeze
4
+ end
5
+ 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: []