gitlab-license 0.0.1 → 2.6.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 +5 -5
- data/.gitlab-ci.yml +25 -0
- data/.rubocop.yml +24 -0
- data/.rubocop_todo.yml +90 -0
- data/README.md +22 -5
- data/Rakefile +1 -1
- data/bin/console +3 -3
- data/gitlab-license.gemspec +17 -13
- data/lib/gitlab/license/boundary.rb +40 -0
- data/lib/gitlab/license/encryptor.rb +48 -19
- data/lib/gitlab/license/version.rb +1 -1
- data/lib/gitlab/license.rb +232 -64
- metadata +66 -17
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 22b0a5d5cec617462fad10a954e69a8ab0cbf2234c4c666b2bce3962387627b4
|
|
4
|
+
data.tar.gz: 5c74cb614cca7a0501188c219fa4dd716d6158fd79cae2a9aeef698d08d90ff9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8cbae440edf6fa10bb5944b63957e204e3fe6213d66d338b1cb7ce0428e2bdfc65a56b634a16c5832cd0039c568108a34c9f9c8b2da1f15e1427332355a2535a
|
|
7
|
+
data.tar.gz: ee7e2deda0d8f42d5453455a9d9d6d88f1b3a53d908f87323928151587a3640bdf3ad4fa7cb9d792639f88df00c0b934491d54e20233d0513b884135ea789ef1
|
data/.gitlab-ci.yml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
default:
|
|
2
|
+
image: "ruby:${RUBY_VERSION}"
|
|
3
|
+
|
|
4
|
+
cache:
|
|
5
|
+
key: "${CI_JOB_IMAGE}"
|
|
6
|
+
paths:
|
|
7
|
+
- vendor/ruby
|
|
8
|
+
|
|
9
|
+
before_script:
|
|
10
|
+
- ruby -v
|
|
11
|
+
- gem update --system --no-document
|
|
12
|
+
- bundle install -j $(nproc) --path vendor
|
|
13
|
+
|
|
14
|
+
test:
|
|
15
|
+
script:
|
|
16
|
+
- bundle exec rspec
|
|
17
|
+
parallel:
|
|
18
|
+
matrix:
|
|
19
|
+
- RUBY_VERSION: [ "3.0", "3.1", "3.2" ]
|
|
20
|
+
|
|
21
|
+
rubocop:
|
|
22
|
+
script:
|
|
23
|
+
- bundle exec rubocop
|
|
24
|
+
variables:
|
|
25
|
+
RUBY_VERSION: "3.0"
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
inherit_from: .rubocop_todo.yml
|
|
2
|
+
|
|
3
|
+
require: rubocop-rspec
|
|
4
|
+
AllCops:
|
|
5
|
+
TargetRubyVersion: 2.7
|
|
6
|
+
|
|
7
|
+
Metrics/BlockLength:
|
|
8
|
+
ExcludedMethods: ['describe', 'context']
|
|
9
|
+
|
|
10
|
+
Style/Alias:
|
|
11
|
+
EnforcedStyle: prefer_alias_method
|
|
12
|
+
Enabled: true
|
|
13
|
+
|
|
14
|
+
Style/HashEachMethods:
|
|
15
|
+
Enabled: true
|
|
16
|
+
|
|
17
|
+
Style/HashTransformKeys:
|
|
18
|
+
Enabled: true
|
|
19
|
+
|
|
20
|
+
Style/HashTransformValues:
|
|
21
|
+
Enabled: true
|
|
22
|
+
|
|
23
|
+
RSpec/MultipleExpectations:
|
|
24
|
+
Enabled: false
|
data/.rubocop_todo.yml
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# This configuration was generated by
|
|
2
|
+
# `rubocop --auto-gen-config`
|
|
3
|
+
# on 2023-12-21 18:05:17 -0500 using RuboCop version 0.80.1.
|
|
4
|
+
# The point is for the user to remove these configuration records
|
|
5
|
+
# one by one as the offenses are removed from the code base.
|
|
6
|
+
# Note that changes in the inspected code, or installation of new
|
|
7
|
+
# versions of RuboCop, may require this file to be generated again.
|
|
8
|
+
|
|
9
|
+
# Offense count: 6
|
|
10
|
+
Metrics/AbcSize:
|
|
11
|
+
Max: 60
|
|
12
|
+
|
|
13
|
+
# Offense count: 1
|
|
14
|
+
# Configuration parameters: CountComments, ExcludedMethods.
|
|
15
|
+
# ExcludedMethods: refine
|
|
16
|
+
Metrics/BlockLength:
|
|
17
|
+
Max: 26
|
|
18
|
+
|
|
19
|
+
# Offense count: 1
|
|
20
|
+
# Configuration parameters: CountComments.
|
|
21
|
+
Metrics/ClassLength:
|
|
22
|
+
Max: 223
|
|
23
|
+
|
|
24
|
+
# Offense count: 3
|
|
25
|
+
Metrics/CyclomaticComplexity:
|
|
26
|
+
Max: 27
|
|
27
|
+
|
|
28
|
+
# Offense count: 6
|
|
29
|
+
# Configuration parameters: CountComments, ExcludedMethods.
|
|
30
|
+
Metrics/MethodLength:
|
|
31
|
+
Max: 36
|
|
32
|
+
|
|
33
|
+
# Offense count: 3
|
|
34
|
+
Metrics/PerceivedComplexity:
|
|
35
|
+
Max: 28
|
|
36
|
+
|
|
37
|
+
# Offense count: 3
|
|
38
|
+
Style/Documentation:
|
|
39
|
+
Exclude:
|
|
40
|
+
- 'spec/**/*'
|
|
41
|
+
- 'test/**/*'
|
|
42
|
+
- 'lib/gitlab/license.rb'
|
|
43
|
+
- 'lib/gitlab/license/boundary.rb'
|
|
44
|
+
- 'lib/gitlab/license/encryptor.rb'
|
|
45
|
+
|
|
46
|
+
# Offense count: 12
|
|
47
|
+
# Cop supports --auto-correct.
|
|
48
|
+
# Configuration parameters: EnforcedStyle.
|
|
49
|
+
# SupportedStyles: always, always_true, never
|
|
50
|
+
Style/FrozenStringLiteralComment:
|
|
51
|
+
Exclude:
|
|
52
|
+
- 'Gemfile'
|
|
53
|
+
- 'Rakefile'
|
|
54
|
+
- 'bin/console'
|
|
55
|
+
- 'gitlab-license.gemspec'
|
|
56
|
+
- 'lib/gitlab/license.rb'
|
|
57
|
+
- 'lib/gitlab/license/boundary.rb'
|
|
58
|
+
- 'lib/gitlab/license/encryptor.rb'
|
|
59
|
+
- 'lib/gitlab/license/version.rb'
|
|
60
|
+
- 'spec/gitlab/license/boundary_spec.rb'
|
|
61
|
+
- 'spec/gitlab/license/encryptor_spec.rb'
|
|
62
|
+
- 'spec/gitlab/license_spec.rb'
|
|
63
|
+
- 'spec/spec_helper.rb'
|
|
64
|
+
|
|
65
|
+
# Offense count: 1
|
|
66
|
+
# Configuration parameters: MinBodyLength.
|
|
67
|
+
Style/GuardClause:
|
|
68
|
+
Exclude:
|
|
69
|
+
- 'lib/gitlab/license.rb'
|
|
70
|
+
|
|
71
|
+
# Offense count: 1
|
|
72
|
+
# Cop supports --auto-correct.
|
|
73
|
+
# Configuration parameters: EnforcedStyle.
|
|
74
|
+
# SupportedStyles: short, verbose
|
|
75
|
+
Style/PreferredHashMethods:
|
|
76
|
+
Exclude:
|
|
77
|
+
- 'lib/gitlab/license.rb'
|
|
78
|
+
|
|
79
|
+
# Offense count: 1
|
|
80
|
+
# Cop supports --auto-correct.
|
|
81
|
+
Style/RescueModifier:
|
|
82
|
+
Exclude:
|
|
83
|
+
- 'lib/gitlab/license.rb'
|
|
84
|
+
|
|
85
|
+
# Offense count: 28
|
|
86
|
+
# Cop supports --auto-correct.
|
|
87
|
+
# Configuration parameters: AutoCorrect, AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
|
|
88
|
+
# URISchemes: http, https
|
|
89
|
+
Layout/LineLength:
|
|
90
|
+
Max: 104
|
data/README.md
CHANGED
|
@@ -18,6 +18,19 @@ Or install it yourself as:
|
|
|
18
18
|
|
|
19
19
|
$ gem install gitlab-license
|
|
20
20
|
|
|
21
|
+
## Release
|
|
22
|
+
|
|
23
|
+
To release a new version of this gem, follow these steps:
|
|
24
|
+
|
|
25
|
+
1. Bump the `VERSION` constant in [`lib/gitlab/license/version.rb`](https://dev.gitlab.org/gitlab/gitlab-license/-/blob/master/lib/gitlab/license/version.rb#L3). This is typically merged as part of the feature MR.
|
|
26
|
+
1. Create a [new release](https://dev.gitlab.org/gitlab/gitlab-license/-/releases) in the project (e.g. `v2.1.0`). For more details on creating a new release, refer to the [documentation](https://dev.gitlab.org/help/user/project/releases/index.md#create-a-release).
|
|
27
|
+
1. [Build the gem](https://guides.rubygems.org/command-reference/#gem-build) to be published: `gem build gitlab-license.gemspec`
|
|
28
|
+
1. [Publish the gem](https://guides.rubygems.org/publishing) to Rubygems: `gem push gitlab-license-2.1.0.gem`
|
|
29
|
+
- Note you will need to be added as an owner in Rubygems in order to publish.
|
|
30
|
+
1. Once published, the new version of the gem can be installed in projects that depend on `gitlab-license` like [CustomersDot](https://gitlab.com/gitlab-org/customers-gitlab-com) and [GitLab](https://gitlab.com/gitlab-org/gitlab).
|
|
31
|
+
- It is recommended to install the new version in both GitLab and CustomersDot to keep the versions aligned.
|
|
32
|
+
- This can be installed via bundler or using `gem install` directly.
|
|
33
|
+
|
|
21
34
|
## Usage
|
|
22
35
|
|
|
23
36
|
```ruby
|
|
@@ -48,17 +61,17 @@ license.licensee = {
|
|
|
48
61
|
"Email" => "douwe@gitlab.com"
|
|
49
62
|
}
|
|
50
63
|
|
|
51
|
-
# The date the license
|
|
64
|
+
# The date the license starts.
|
|
52
65
|
# Required.
|
|
53
|
-
license.
|
|
54
|
-
# The date the license expires.
|
|
66
|
+
license.starts_at = Date.new(2015, 4, 24)
|
|
67
|
+
# The date the license expires.
|
|
55
68
|
# Not required, to allow lifetime licenses.
|
|
56
69
|
license.expires_at = Date.new(2016, 4, 23)
|
|
57
70
|
|
|
58
71
|
# The below dates are hardcoded in the license so that you can play with the
|
|
59
72
|
# period after which there are "repercussions" to license expiration.
|
|
60
73
|
|
|
61
|
-
# The date admins will be notified about the license's pending expiration.
|
|
74
|
+
# The date admins will be notified about the license's pending expiration.
|
|
62
75
|
# Not required.
|
|
63
76
|
license.notify_admins_at = Date.new(2016, 4, 19)
|
|
64
77
|
|
|
@@ -66,11 +79,15 @@ license.notify_admins_at = Date.new(2016, 4, 19)
|
|
|
66
79
|
# Not required.
|
|
67
80
|
license.notify_users_at = Date.new(2016, 4, 23)
|
|
68
81
|
|
|
69
|
-
# The date "changes" like code pushes, issue or merge request creation
|
|
82
|
+
# The date "changes" like code pushes, issue or merge request creation
|
|
70
83
|
# or modification and project creation will be blocked.
|
|
71
84
|
# Not required.
|
|
72
85
|
license.block_changes_at = Date.new(2016, 5, 7)
|
|
73
86
|
|
|
87
|
+
# If this license was created due to a subscription cancellation
|
|
88
|
+
# Not required.
|
|
89
|
+
license.generated_from_cancellation? = false
|
|
90
|
+
|
|
74
91
|
# Restrictions bundled with this license.
|
|
75
92
|
# Not required, to allow unlimited-user licenses for things like educational organizations.
|
|
76
93
|
license.restrictions = {
|
data/Rakefile
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
require
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/console
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
3
|
+
require 'bundler/setup'
|
|
4
|
+
require 'gitlab/license'
|
|
5
5
|
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
|
@@ -10,5 +10,5 @@ require "gitlab/license"
|
|
|
10
10
|
# require "pry"
|
|
11
11
|
# Pry.start
|
|
12
12
|
|
|
13
|
-
require
|
|
13
|
+
require 'irb'
|
|
14
14
|
IRB.start
|
data/gitlab-license.gemspec
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
1
|
-
|
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
|
1
|
+
lib = File.expand_path('lib', __dir__)
|
|
3
2
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
3
|
require 'gitlab/license/version'
|
|
5
4
|
|
|
6
5
|
Gem::Specification.new do |spec|
|
|
7
|
-
spec.name =
|
|
6
|
+
spec.name = 'gitlab-license'
|
|
8
7
|
spec.version = Gitlab::License::VERSION
|
|
9
|
-
spec.authors = [
|
|
10
|
-
spec.email = [
|
|
8
|
+
spec.authors = ['Douwe Maan', 'Stan Hu', 'Tyler Amos']
|
|
9
|
+
spec.email = ['douwe@gitlab.com', 'stanhu@gitlab.com', 'tamos@gitlab.com']
|
|
11
10
|
|
|
12
|
-
spec.summary =
|
|
13
|
-
spec.homepage =
|
|
14
|
-
spec.license =
|
|
11
|
+
spec.summary = 'gitlab-license helps you generate, verify and enforce software licenses.'
|
|
12
|
+
spec.homepage = 'https://dev.gitlab.org/gitlab/gitlab-license'
|
|
13
|
+
spec.license = 'MIT'
|
|
15
14
|
|
|
16
15
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
|
17
|
-
spec.bindir =
|
|
16
|
+
spec.bindir = 'exe'
|
|
18
17
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
19
|
-
spec.require_paths = [
|
|
18
|
+
spec.require_paths = ['lib']
|
|
20
19
|
|
|
21
|
-
spec.
|
|
22
|
-
|
|
23
|
-
spec.add_development_dependency
|
|
20
|
+
spec.required_ruby_version = '>= 2.7.0'
|
|
21
|
+
|
|
22
|
+
spec.add_development_dependency 'bundler', '~> 2.4'
|
|
23
|
+
spec.add_development_dependency 'byebug'
|
|
24
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.9'
|
|
26
|
+
spec.add_development_dependency 'rubocop', '~> 0.80.1'
|
|
27
|
+
spec.add_development_dependency 'rubocop-rspec', '~> 1.38.1'
|
|
24
28
|
end
|
|
@@ -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
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
module Gitlab
|
|
2
2
|
class License
|
|
3
3
|
class Encryptor
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
class KeyError < Error; end
|
|
6
|
+
class DecryptionError < Error; end
|
|
7
|
+
|
|
4
8
|
attr_accessor :key
|
|
5
9
|
|
|
6
10
|
def initialize(key)
|
|
11
|
+
raise KeyError, 'No RSA encryption key provided.' if key && !key.is_a?(OpenSSL::PKey::RSA)
|
|
12
|
+
|
|
7
13
|
@key = key
|
|
8
14
|
end
|
|
9
15
|
|
|
10
16
|
def encrypt(data)
|
|
11
|
-
unless key.private?
|
|
12
|
-
raise "Provided key is not a private key."
|
|
13
|
-
end
|
|
17
|
+
raise KeyError, 'Provided key is not a private key.' unless key.private?
|
|
14
18
|
|
|
15
19
|
# Encrypt the data using symmetric AES encryption.
|
|
16
20
|
cipher = OpenSSL::Cipher::AES128.new(:CBC)
|
|
@@ -21,12 +25,12 @@ module Gitlab
|
|
|
21
25
|
encrypted_data = cipher.update(data) + cipher.final
|
|
22
26
|
|
|
23
27
|
# Encrypt the AES key using asymmetric RSA encryption.
|
|
24
|
-
encrypted_key =
|
|
28
|
+
encrypted_key = key.private_encrypt(aes_key)
|
|
25
29
|
|
|
26
30
|
encryption_data = {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
'data' => Base64.encode64(encrypted_data),
|
|
32
|
+
'key' => Base64.encode64(encrypted_key),
|
|
33
|
+
'iv' => Base64.encode64(aes_iv)
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
json_data = JSON.dump(encryption_data)
|
|
@@ -34,27 +38,52 @@ module Gitlab
|
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
def decrypt(data)
|
|
37
|
-
unless key.public?
|
|
38
|
-
|
|
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.'
|
|
39
49
|
end
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
|
|
51
|
+
unless %w[data key iv].all? { |key| encryption_data[key] }
|
|
52
|
+
raise DecryptionError, 'Required field missing from encryption data.'
|
|
53
|
+
end
|
|
43
54
|
|
|
44
|
-
encrypted_data = Base64.decode64(encryption_data[
|
|
45
|
-
encrypted_key = Base64.decode64(encryption_data[
|
|
46
|
-
aes_iv = Base64.decode64(encryption_data[
|
|
55
|
+
encrypted_data = Base64.decode64(encryption_data['data'])
|
|
56
|
+
encrypted_key = Base64.decode64(encryption_data['key'])
|
|
57
|
+
aes_iv = Base64.decode64(encryption_data['iv'])
|
|
47
58
|
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
50
65
|
|
|
51
66
|
# Decrypt the data using symmetric AES encryption.
|
|
52
67
|
cipher = OpenSSL::Cipher::AES128.new(:CBC)
|
|
53
68
|
cipher.decrypt
|
|
54
|
-
cipher.key = aes_key
|
|
55
|
-
cipher.iv = aes_iv
|
|
56
69
|
|
|
57
|
-
|
|
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
|
|
58
87
|
|
|
59
88
|
data
|
|
60
89
|
end
|
data/lib/gitlab/license.rb
CHANGED
|
@@ -1,89 +1,209 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
3
|
-
require
|
|
4
|
-
require
|
|
1
|
+
require 'openssl'
|
|
2
|
+
require 'date'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'base64'
|
|
5
5
|
|
|
6
|
-
require
|
|
7
|
-
require
|
|
6
|
+
require 'gitlab/license/version'
|
|
7
|
+
require 'gitlab/license/encryptor'
|
|
8
|
+
require 'gitlab/license/boundary'
|
|
8
9
|
|
|
9
10
|
module Gitlab
|
|
10
|
-
class License
|
|
11
|
+
class License # rubocop:disable Metrics/ClassLength
|
|
12
|
+
class Error < StandardError; end
|
|
13
|
+
class ImportError < Error; end
|
|
14
|
+
class ValidationError < Error; end
|
|
15
|
+
|
|
11
16
|
class << self
|
|
12
|
-
|
|
17
|
+
attr_reader :encryption_key
|
|
18
|
+
attr_reader :fallback_decryption_keys
|
|
13
19
|
@encryption_key = nil
|
|
14
20
|
|
|
15
|
-
def
|
|
16
|
-
|
|
17
|
-
|
|
21
|
+
def encryption_key=(key)
|
|
22
|
+
raise ArgumentError, 'No RSA encryption key provided.' if key && !key.is_a?(OpenSSL::PKey::RSA)
|
|
23
|
+
|
|
24
|
+
@encryption_key = key
|
|
25
|
+
@encryptor = nil
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def fallback_decryption_keys=(keys)
|
|
29
|
+
unless keys
|
|
30
|
+
@fallback_decryption_keys = nil
|
|
31
|
+
return
|
|
18
32
|
end
|
|
19
33
|
|
|
20
|
-
|
|
34
|
+
unless keys.is_a?(Enumerable) && keys.all? { |key| key.is_a?(OpenSSL::PKey::RSA) }
|
|
35
|
+
raise ArgumentError, 'Invalid fallback RSA encryption keys provided.'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
@fallback_decryption_keys = Array(keys)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def encryptor
|
|
42
|
+
@encryptor ||= Encryptor.new(encryption_key)
|
|
21
43
|
end
|
|
22
44
|
|
|
23
45
|
def import(data)
|
|
24
|
-
|
|
46
|
+
raise ImportError, 'No license data.' if data.nil?
|
|
47
|
+
|
|
48
|
+
data = Boundary.remove_boundary(data)
|
|
49
|
+
|
|
50
|
+
license_json = decrypt_with_fallback_keys(data)
|
|
51
|
+
|
|
52
|
+
begin
|
|
53
|
+
attributes = JSON.parse(license_json)
|
|
54
|
+
rescue JSON::ParseError
|
|
55
|
+
raise ImportError, 'License data is invalid JSON.'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
new(attributes)
|
|
25
59
|
end
|
|
26
60
|
|
|
27
|
-
def
|
|
28
|
-
|
|
61
|
+
def decrypt_with_fallback_keys(data)
|
|
62
|
+
keys_to_try = Array(encryption_key)
|
|
63
|
+
keys_to_try += fallback_decryption_keys if fallback_decryption_keys
|
|
64
|
+
|
|
65
|
+
keys_to_try.each do |decryption_key|
|
|
66
|
+
decryptor = Encryptor.new(decryption_key)
|
|
67
|
+
return decryptor.decrypt(data)
|
|
68
|
+
rescue Encryptor::Error
|
|
69
|
+
next
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
raise ImportError, 'License data could not be decrypted.'
|
|
29
73
|
end
|
|
30
74
|
end
|
|
31
75
|
|
|
32
76
|
attr_reader :version
|
|
33
|
-
attr_accessor :licensee, :
|
|
34
|
-
|
|
35
|
-
|
|
77
|
+
attr_accessor :licensee, :starts_at, :expires_at, :notify_admins_at,
|
|
78
|
+
:notify_users_at, :block_changes_at, :last_synced_at, :next_sync_at,
|
|
79
|
+
:activated_at, :restrictions, :cloud_licensing_enabled,
|
|
80
|
+
:offline_cloud_licensing_enabled, :auto_renew_enabled, :seat_reconciliation_enabled,
|
|
81
|
+
:operational_metrics_enabled, :generated_from_customers_dot,
|
|
82
|
+
:generated_from_cancellation, :temporary_extension, :contract_overages_allowed
|
|
83
|
+
|
|
84
|
+
alias_method :issued_at, :starts_at
|
|
85
|
+
alias_method :issued_at=, :starts_at=
|
|
36
86
|
|
|
37
87
|
def initialize(attributes = {})
|
|
38
88
|
load_attributes(attributes)
|
|
39
89
|
end
|
|
40
90
|
|
|
41
91
|
def valid?
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
92
|
+
if !licensee || !licensee.is_a?(Hash) || licensee.empty?
|
|
93
|
+
false
|
|
94
|
+
elsif !starts_at || !starts_at.is_a?(Date)
|
|
95
|
+
false
|
|
96
|
+
elsif !expires_at && !gl_team_license? && !jh_team_license?
|
|
97
|
+
false
|
|
98
|
+
elsif expires_at && !expires_at.is_a?(Date)
|
|
99
|
+
false
|
|
100
|
+
elsif notify_admins_at && !notify_admins_at.is_a?(Date)
|
|
101
|
+
false
|
|
102
|
+
elsif notify_users_at && !notify_users_at.is_a?(Date)
|
|
103
|
+
false
|
|
104
|
+
elsif block_changes_at && !block_changes_at.is_a?(Date)
|
|
105
|
+
false
|
|
106
|
+
elsif last_synced_at && !last_synced_at.is_a?(DateTime)
|
|
107
|
+
false
|
|
108
|
+
elsif next_sync_at && !next_sync_at.is_a?(DateTime)
|
|
109
|
+
false
|
|
110
|
+
elsif activated_at && !activated_at.is_a?(DateTime)
|
|
111
|
+
false
|
|
112
|
+
elsif restrictions && !restrictions.is_a?(Hash)
|
|
113
|
+
false
|
|
114
|
+
elsif !cloud_licensing? && offline_cloud_licensing?
|
|
115
|
+
false
|
|
116
|
+
else
|
|
117
|
+
true
|
|
118
|
+
end
|
|
51
119
|
end
|
|
52
120
|
|
|
53
121
|
def validate!
|
|
54
|
-
raise
|
|
122
|
+
raise ValidationError, 'License is invalid' unless valid?
|
|
55
123
|
end
|
|
56
124
|
|
|
57
125
|
def will_expire?
|
|
58
|
-
|
|
126
|
+
expires_at
|
|
59
127
|
end
|
|
60
128
|
|
|
61
129
|
def will_notify_admins?
|
|
62
|
-
|
|
130
|
+
notify_admins_at
|
|
63
131
|
end
|
|
64
132
|
|
|
65
133
|
def will_notify_users?
|
|
66
|
-
|
|
134
|
+
notify_users_at
|
|
67
135
|
end
|
|
68
136
|
|
|
69
137
|
def will_block_changes?
|
|
70
|
-
|
|
138
|
+
block_changes_at
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def will_sync?
|
|
142
|
+
next_sync_at
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def activated?
|
|
146
|
+
activated_at
|
|
71
147
|
end
|
|
72
148
|
|
|
73
149
|
def expired?
|
|
74
|
-
will_expire? && Date.today >=
|
|
150
|
+
will_expire? && Date.today >= expires_at
|
|
75
151
|
end
|
|
76
152
|
|
|
77
153
|
def notify_admins?
|
|
78
|
-
will_notify_admins? && Date.today >=
|
|
154
|
+
will_notify_admins? && Date.today >= notify_admins_at
|
|
79
155
|
end
|
|
80
156
|
|
|
81
157
|
def notify_users?
|
|
82
|
-
will_notify_users? && Date.today >=
|
|
158
|
+
will_notify_users? && Date.today >= notify_users_at
|
|
83
159
|
end
|
|
84
160
|
|
|
85
161
|
def block_changes?
|
|
86
|
-
will_block_changes? && Date.today >=
|
|
162
|
+
will_block_changes? && Date.today >= block_changes_at
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def cloud_licensing?
|
|
166
|
+
cloud_licensing_enabled == true
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def offline_cloud_licensing?
|
|
170
|
+
offline_cloud_licensing_enabled == true
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def contract_overages_allowed?
|
|
174
|
+
contract_overages_allowed == true
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def auto_renew?
|
|
178
|
+
auto_renew_enabled == true
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def seat_reconciliation?
|
|
182
|
+
seat_reconciliation_enabled == true
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def operational_metrics?
|
|
186
|
+
operational_metrics_enabled == true
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def generated_from_customers_dot?
|
|
190
|
+
generated_from_customers_dot == true
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def generated_from_cancellation?
|
|
194
|
+
generated_from_cancellation == true
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def gl_team_license?
|
|
198
|
+
licensee['Company'].to_s.match?(/GitLab/i) && licensee['Email'].to_s.end_with?('@gitlab.com')
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def jh_team_license?
|
|
202
|
+
licensee['Company'].to_s.match?(/GitLab/i) && licensee['Email'].to_s.end_with?('@jihulab.com')
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def temporary_extension?
|
|
206
|
+
temporary_extension == true
|
|
87
207
|
end
|
|
88
208
|
|
|
89
209
|
def restricted?(key = nil)
|
|
@@ -94,62 +214,110 @@ module Gitlab
|
|
|
94
214
|
end
|
|
95
215
|
end
|
|
96
216
|
|
|
97
|
-
def attributes
|
|
217
|
+
def attributes
|
|
98
218
|
hash = {}
|
|
99
219
|
|
|
100
|
-
hash[
|
|
101
|
-
hash[
|
|
220
|
+
hash['version'] = version
|
|
221
|
+
hash['licensee'] = licensee
|
|
222
|
+
|
|
223
|
+
# `issued_at` is the legacy name for starts_at.
|
|
224
|
+
# TODO: Move to starts_at in a next version.
|
|
225
|
+
hash['issued_at'] = starts_at
|
|
226
|
+
hash['expires_at'] = expires_at if will_expire?
|
|
102
227
|
|
|
103
|
-
hash[
|
|
104
|
-
hash[
|
|
228
|
+
hash['notify_admins_at'] = notify_admins_at if will_notify_admins?
|
|
229
|
+
hash['notify_users_at'] = notify_users_at if will_notify_users?
|
|
230
|
+
hash['block_changes_at'] = block_changes_at if will_block_changes?
|
|
105
231
|
|
|
106
|
-
hash[
|
|
107
|
-
hash[
|
|
108
|
-
hash[
|
|
232
|
+
hash['next_sync_at'] = next_sync_at if will_sync?
|
|
233
|
+
hash['last_synced_at'] = last_synced_at if will_sync?
|
|
234
|
+
hash['activated_at'] = activated_at if activated?
|
|
109
235
|
|
|
110
|
-
hash[
|
|
236
|
+
hash['cloud_licensing_enabled'] = cloud_licensing?
|
|
237
|
+
hash['offline_cloud_licensing_enabled'] = offline_cloud_licensing?
|
|
238
|
+
hash['auto_renew_enabled'] = auto_renew?
|
|
239
|
+
hash['seat_reconciliation_enabled'] = seat_reconciliation?
|
|
240
|
+
hash['operational_metrics_enabled'] = operational_metrics?
|
|
241
|
+
hash['contract_overages_allowed'] = contract_overages_allowed?
|
|
242
|
+
|
|
243
|
+
hash['generated_from_customers_dot'] = generated_from_customers_dot?
|
|
244
|
+
hash['generated_from_cancellation'] = generated_from_cancellation?
|
|
245
|
+
|
|
246
|
+
hash['temporary_extension'] = temporary_extension?
|
|
247
|
+
|
|
248
|
+
hash['restrictions'] = restrictions if restricted?
|
|
111
249
|
|
|
112
250
|
hash
|
|
113
251
|
end
|
|
114
252
|
|
|
115
|
-
def to_json
|
|
116
|
-
JSON.dump(
|
|
253
|
+
def to_json(*_args)
|
|
254
|
+
JSON.dump(attributes)
|
|
117
255
|
end
|
|
118
256
|
|
|
119
|
-
def export
|
|
257
|
+
def export(boundary: nil)
|
|
120
258
|
validate!
|
|
121
259
|
|
|
122
|
-
self.class.encryptor.encrypt(
|
|
260
|
+
data = self.class.encryptor.encrypt(to_json)
|
|
261
|
+
|
|
262
|
+
data = Boundary.add_boundary(data, boundary) if boundary
|
|
263
|
+
|
|
264
|
+
data
|
|
123
265
|
end
|
|
124
266
|
|
|
125
267
|
private
|
|
126
268
|
|
|
127
269
|
def load_attributes(attributes)
|
|
128
|
-
attributes =
|
|
270
|
+
attributes = attributes.transform_keys(&:to_s)
|
|
129
271
|
|
|
130
|
-
version = attributes[
|
|
131
|
-
unless version && version == 1
|
|
132
|
-
raise ArgumentError, "Version is too new"
|
|
133
|
-
end
|
|
272
|
+
version = attributes['version'] || 1
|
|
273
|
+
raise ArgumentError, 'Version is too new' unless version && version == 1
|
|
134
274
|
|
|
135
275
|
@version = version
|
|
136
276
|
|
|
137
|
-
@licensee = attributes[
|
|
277
|
+
@licensee = attributes['licensee']
|
|
278
|
+
|
|
279
|
+
# `issued_at` is the legacy name for starts_at.
|
|
280
|
+
# TODO: Move to starts_at in a next version.
|
|
281
|
+
%w[issued_at expires_at notify_admins_at notify_users_at block_changes_at].each do |attr_name|
|
|
282
|
+
set_date_attribute(attr_name, attributes[attr_name])
|
|
283
|
+
end
|
|
138
284
|
|
|
139
|
-
%w
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
next unless value
|
|
285
|
+
%w[last_synced_at next_sync_at activated_at].each do |attr_name|
|
|
286
|
+
set_datetime_attribute(attr_name, attributes[attr_name])
|
|
287
|
+
end
|
|
144
288
|
|
|
145
|
-
|
|
289
|
+
%w[
|
|
290
|
+
cloud_licensing_enabled
|
|
291
|
+
offline_cloud_licensing_enabled
|
|
292
|
+
auto_renew_enabled
|
|
293
|
+
seat_reconciliation_enabled
|
|
294
|
+
operational_metrics_enabled
|
|
295
|
+
generated_from_customers_dot
|
|
296
|
+
generated_from_cancellation
|
|
297
|
+
temporary_extension
|
|
298
|
+
].each do |attr_name|
|
|
299
|
+
public_send("#{attr_name}=", attributes[attr_name] == true)
|
|
146
300
|
end
|
|
147
301
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
302
|
+
@contract_overages_allowed = attributes['contract_overages_allowed'] != false
|
|
303
|
+
|
|
304
|
+
restrictions = attributes['restrictions']
|
|
305
|
+
if restrictions&.is_a?(Hash)
|
|
306
|
+
restrictions = restrictions.transform_keys(&:to_sym)
|
|
151
307
|
@restrictions = restrictions
|
|
152
308
|
end
|
|
153
309
|
end
|
|
310
|
+
|
|
311
|
+
def set_date_attribute(attr_name, value, date_class = Date)
|
|
312
|
+
value = date_class.parse(value) rescue nil if value.is_a?(String)
|
|
313
|
+
|
|
314
|
+
return unless value
|
|
315
|
+
|
|
316
|
+
public_send("#{attr_name}=", value)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def set_datetime_attribute(attr_name, value)
|
|
320
|
+
set_date_attribute(attr_name, value, DateTime)
|
|
321
|
+
end
|
|
154
322
|
end
|
|
155
323
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gitlab-license
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Douwe Maan
|
|
8
|
-
|
|
8
|
+
- Stan Hu
|
|
9
|
+
- Tyler Amos
|
|
10
|
+
autorequire:
|
|
9
11
|
bindir: exe
|
|
10
12
|
cert_chain: []
|
|
11
|
-
date:
|
|
13
|
+
date: 2024-11-14 00:00:00.000000000 Z
|
|
12
14
|
dependencies:
|
|
13
15
|
- !ruby/object:Gem::Dependency
|
|
14
16
|
name: bundler
|
|
@@ -16,14 +18,28 @@ dependencies:
|
|
|
16
18
|
requirements:
|
|
17
19
|
- - "~>"
|
|
18
20
|
- !ruby/object:Gem::Version
|
|
19
|
-
version: '
|
|
21
|
+
version: '2.4'
|
|
20
22
|
type: :development
|
|
21
23
|
prerelease: false
|
|
22
24
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
25
|
requirements:
|
|
24
26
|
- - "~>"
|
|
25
27
|
- !ruby/object:Gem::Version
|
|
26
|
-
version: '
|
|
28
|
+
version: '2.4'
|
|
29
|
+
- !ruby/object:Gem::Dependency
|
|
30
|
+
name: byebug
|
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
|
32
|
+
requirements:
|
|
33
|
+
- - ">="
|
|
34
|
+
- !ruby/object:Gem::Version
|
|
35
|
+
version: '0'
|
|
36
|
+
type: :development
|
|
37
|
+
prerelease: false
|
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '0'
|
|
27
43
|
- !ruby/object:Gem::Dependency
|
|
28
44
|
name: rake
|
|
29
45
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -39,28 +55,61 @@ dependencies:
|
|
|
39
55
|
- !ruby/object:Gem::Version
|
|
40
56
|
version: '10.0'
|
|
41
57
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name:
|
|
58
|
+
name: rspec
|
|
43
59
|
requirement: !ruby/object:Gem::Requirement
|
|
44
60
|
requirements:
|
|
45
|
-
- - "
|
|
61
|
+
- - "~>"
|
|
46
62
|
- !ruby/object:Gem::Version
|
|
47
|
-
version: '
|
|
63
|
+
version: '3.9'
|
|
48
64
|
type: :development
|
|
49
65
|
prerelease: false
|
|
50
66
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
67
|
requirements:
|
|
52
|
-
- - "
|
|
68
|
+
- - "~>"
|
|
53
69
|
- !ruby/object:Gem::Version
|
|
54
|
-
version: '
|
|
55
|
-
|
|
70
|
+
version: '3.9'
|
|
71
|
+
- !ruby/object:Gem::Dependency
|
|
72
|
+
name: rubocop
|
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - "~>"
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: 0.80.1
|
|
78
|
+
type: :development
|
|
79
|
+
prerelease: false
|
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - "~>"
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: 0.80.1
|
|
85
|
+
- !ruby/object:Gem::Dependency
|
|
86
|
+
name: rubocop-rspec
|
|
87
|
+
requirement: !ruby/object:Gem::Requirement
|
|
88
|
+
requirements:
|
|
89
|
+
- - "~>"
|
|
90
|
+
- !ruby/object:Gem::Version
|
|
91
|
+
version: 1.38.1
|
|
92
|
+
type: :development
|
|
93
|
+
prerelease: false
|
|
94
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
95
|
+
requirements:
|
|
96
|
+
- - "~>"
|
|
97
|
+
- !ruby/object:Gem::Version
|
|
98
|
+
version: 1.38.1
|
|
99
|
+
description:
|
|
56
100
|
email:
|
|
57
101
|
- douwe@gitlab.com
|
|
102
|
+
- stanhu@gitlab.com
|
|
103
|
+
- tamos@gitlab.com
|
|
58
104
|
executables: []
|
|
59
105
|
extensions: []
|
|
60
106
|
extra_rdoc_files: []
|
|
61
107
|
files:
|
|
62
108
|
- ".gitignore"
|
|
109
|
+
- ".gitlab-ci.yml"
|
|
63
110
|
- ".rspec"
|
|
111
|
+
- ".rubocop.yml"
|
|
112
|
+
- ".rubocop_todo.yml"
|
|
64
113
|
- ".travis.yml"
|
|
65
114
|
- Gemfile
|
|
66
115
|
- LICENSE.txt
|
|
@@ -70,13 +119,14 @@ files:
|
|
|
70
119
|
- bin/setup
|
|
71
120
|
- gitlab-license.gemspec
|
|
72
121
|
- lib/gitlab/license.rb
|
|
122
|
+
- lib/gitlab/license/boundary.rb
|
|
73
123
|
- lib/gitlab/license/encryptor.rb
|
|
74
124
|
- lib/gitlab/license/version.rb
|
|
75
|
-
homepage: https://gitlab.
|
|
125
|
+
homepage: https://dev.gitlab.org/gitlab/gitlab-license
|
|
76
126
|
licenses:
|
|
77
127
|
- MIT
|
|
78
128
|
metadata: {}
|
|
79
|
-
post_install_message:
|
|
129
|
+
post_install_message:
|
|
80
130
|
rdoc_options: []
|
|
81
131
|
require_paths:
|
|
82
132
|
- lib
|
|
@@ -84,16 +134,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
84
134
|
requirements:
|
|
85
135
|
- - ">="
|
|
86
136
|
- !ruby/object:Gem::Version
|
|
87
|
-
version:
|
|
137
|
+
version: 2.7.0
|
|
88
138
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
89
139
|
requirements:
|
|
90
140
|
- - ">="
|
|
91
141
|
- !ruby/object:Gem::Version
|
|
92
142
|
version: '0'
|
|
93
143
|
requirements: []
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
signing_key:
|
|
144
|
+
rubygems_version: 3.1.6
|
|
145
|
+
signing_key:
|
|
97
146
|
specification_version: 4
|
|
98
147
|
summary: gitlab-license helps you generate, verify and enforce software licenses.
|
|
99
148
|
test_files: []
|