openproject-token 4.0.0 → 5.1.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 +4 -4
- data/lib/open_project/token/armor.rb +9 -9
- data/lib/open_project/token/extractor.rb +5 -3
- data/lib/open_project/token/plans.rb +83 -0
- data/lib/open_project/token/version.rb +3 -1
- data/lib/open_project/token.rb +79 -52
- data/lib/openproject-token.rb +3 -1
- metadata +7 -51
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24a0ac03322b7a1471a9b638111f18d7a038e3171fb1e9156e6b7d04d0503053
|
4
|
+
data.tar.gz: 47d565cc5231417c67dc9db5639bdb8eecb8e12203c7f43cc9e789ff3c66df23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c50b946dd44364c974f326fd3c4987f0edd9ad1c04a123bf8242af1e2556923f68f12e90032a58376c2a3e867f1a73b421b8ea81f94d61afd7de67c0020a88d6
|
7
|
+
data.tar.gz: 1a8d2af2edaf6587af2791fd414b9446e41a2f9fb7b66a17b76c1a8b60956182dbe0da6c7c8d67f2a0753ffc22ed914e80f869a8272c31e3de96f9b0201377db
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OpenProject
|
2
4
|
class Token
|
3
5
|
module Armor
|
4
6
|
class ParseError < StandardError; end
|
5
7
|
|
6
|
-
MARKER =
|
8
|
+
MARKER = "OPENPROJECT-EE TOKEN"
|
7
9
|
|
8
10
|
class << self
|
9
11
|
def header
|
@@ -15,19 +17,17 @@ module OpenProject
|
|
15
17
|
end
|
16
18
|
|
17
19
|
def encode(data)
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
s << footer
|
24
|
-
end
|
20
|
+
<<~DATA
|
21
|
+
#{header}
|
22
|
+
#{data.strip}
|
23
|
+
#{footer}
|
24
|
+
DATA
|
25
25
|
end
|
26
26
|
|
27
27
|
def decode(data)
|
28
28
|
match = data.match /#{header}\r?\n(.+?)\r?\n#{footer}/m
|
29
29
|
if match.nil?
|
30
|
-
raise ParseError,
|
30
|
+
raise ParseError, "Failed to parse armored text."
|
31
31
|
end
|
32
32
|
|
33
33
|
match[1]
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module OpenProject
|
2
4
|
class Token
|
3
5
|
class Extractor
|
@@ -40,17 +42,17 @@ module OpenProject
|
|
40
42
|
end
|
41
43
|
|
42
44
|
# Decrypt the data using symmetric AES encryption.
|
43
|
-
cipher = OpenSSL::Cipher
|
45
|
+
cipher = OpenSSL::Cipher.new("aes-128-cbc")
|
44
46
|
cipher.decrypt
|
45
47
|
|
46
48
|
begin
|
47
|
-
cipher.key
|
49
|
+
cipher.key = aes_key
|
48
50
|
rescue OpenSSL::Cipher::CipherError
|
49
51
|
raise DecryptionError, "AES encryption key is invalid."
|
50
52
|
end
|
51
53
|
|
52
54
|
begin
|
53
|
-
cipher.iv
|
55
|
+
cipher.iv = aes_iv
|
54
56
|
rescue OpenSSL::Cipher::CipherError
|
55
57
|
raise DecryptionError, "AES IV is invalid."
|
56
58
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module OpenProject
|
4
|
+
class Token
|
5
|
+
LEGACY_ENTERPRISE_PLAN_FEATURES = %i[
|
6
|
+
baseline_comparison
|
7
|
+
board_view
|
8
|
+
conditional_highlighting
|
9
|
+
custom_actions
|
10
|
+
custom_field_hierarchies
|
11
|
+
customize_life_cycle
|
12
|
+
date_alerts
|
13
|
+
define_custom_style
|
14
|
+
edit_attribute_groups
|
15
|
+
gantt_pdf_export
|
16
|
+
grid_widget_wp_graph
|
17
|
+
ldap_groups
|
18
|
+
one_drive_sharepoint_file_storage
|
19
|
+
placeholder_users
|
20
|
+
project_list_sharing
|
21
|
+
readonly_work_packages
|
22
|
+
sso_auth_providers
|
23
|
+
team_planner_view
|
24
|
+
virus_scanning
|
25
|
+
work_package_query_relation_columns
|
26
|
+
work_package_sharing
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
BASIC_PLAN_FEATURES = %i[
|
30
|
+
baseline_comparison
|
31
|
+
board_view
|
32
|
+
conditional_highlighting
|
33
|
+
custom_actions
|
34
|
+
custom_field_hierarchies
|
35
|
+
date_alerts
|
36
|
+
define_custom_style
|
37
|
+
edit_attribute_groups
|
38
|
+
gantt_pdf_export
|
39
|
+
grid_widget_wp_graph
|
40
|
+
placeholder_users
|
41
|
+
readonly_work_packages
|
42
|
+
team_planner_view
|
43
|
+
work_package_query_relation_columns
|
44
|
+
].freeze
|
45
|
+
|
46
|
+
PROFESSIONAL_PLAN_FEATURES = %i[
|
47
|
+
one_drive_sharepoint_file_storage
|
48
|
+
work_package_sharing
|
49
|
+
work_package_subject_generation
|
50
|
+
].freeze
|
51
|
+
|
52
|
+
PREMIUM_PLAN_FEATURES = %i[
|
53
|
+
customize_life_cycle
|
54
|
+
ldap_groups
|
55
|
+
project_list_sharing
|
56
|
+
sso_auth_providers
|
57
|
+
].freeze
|
58
|
+
|
59
|
+
CORPORATE_PLAN_FEATURES = %i[
|
60
|
+
virus_scanning
|
61
|
+
].freeze
|
62
|
+
|
63
|
+
FEATURES_PER_PLAN = {
|
64
|
+
# old plan that is used for all plans that do not have a plan set
|
65
|
+
legacy_enterprise: LEGACY_ENTERPRISE_PLAN_FEATURES,
|
66
|
+
|
67
|
+
# new plans
|
68
|
+
basic: BASIC_PLAN_FEATURES,
|
69
|
+
professional: PROFESSIONAL_PLAN_FEATURES + BASIC_PLAN_FEATURES,
|
70
|
+
premium: PREMIUM_PLAN_FEATURES + PROFESSIONAL_PLAN_FEATURES + BASIC_PLAN_FEATURES,
|
71
|
+
corporate: CORPORATE_PLAN_FEATURES + PREMIUM_PLAN_FEATURES + PROFESSIONAL_PLAN_FEATURES + BASIC_PLAN_FEATURES
|
72
|
+
}.freeze
|
73
|
+
|
74
|
+
# Current plan names, sorted by inheritance
|
75
|
+
ACTIVE_PLAN_NAMES = %i[basic professional premium corporate].freeze
|
76
|
+
|
77
|
+
# All available plan names
|
78
|
+
AVAILABLE_PLAN_NAMES = %i[legacy_enterprise] + ACTIVE_PLAN_NAMES
|
79
|
+
|
80
|
+
# default plan that is assigned to a token if no plan is given (especially legacy tokens)
|
81
|
+
DEFAULT_PLAN = :legacy_enterprise
|
82
|
+
end
|
83
|
+
end
|
data/lib/open_project/token.rb
CHANGED
@@ -1,13 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "openssl"
|
2
4
|
require "date"
|
3
5
|
require "json"
|
4
6
|
require "base64"
|
5
7
|
|
6
|
-
require
|
8
|
+
require "active_model"
|
7
9
|
|
8
10
|
require "open_project/token/version"
|
9
11
|
require "open_project/token/extractor"
|
10
12
|
require "open_project/token/armor"
|
13
|
+
require "open_project/token/plans"
|
11
14
|
|
12
15
|
module OpenProject
|
13
16
|
class Token
|
@@ -44,28 +47,31 @@ module OpenProject
|
|
44
47
|
rescue Armor::ParseError
|
45
48
|
raise ImportError, "Token value could not be parsed."
|
46
49
|
end
|
50
|
+
|
51
|
+
def lowest_plan_for(feature)
|
52
|
+
OpenProject::Token::ACTIVE_PLAN_NAMES.detect do |plan|
|
53
|
+
OpenProject::Token::FEATURES_PER_PLAN[plan].include?(feature)
|
54
|
+
end
|
55
|
+
end
|
47
56
|
end
|
48
57
|
|
49
58
|
include ActiveModel::Validations
|
50
59
|
|
51
|
-
attr_reader :version
|
52
|
-
attr_accessor :subscriber, :mail, :company, :domain
|
53
|
-
|
54
|
-
attr_accessor :reprieve_days
|
55
|
-
attr_accessor :notify_admins_at, :notify_users_at, :block_changes_at
|
56
|
-
attr_accessor :restrictions
|
57
|
-
attr_accessor :features
|
60
|
+
attr_reader :version, :plan
|
61
|
+
attr_accessor :subscriber, :mail, :company, :domain, :starts_at, :issued_at, :expires_at, :reprieve_days, :notify_admins_at,
|
62
|
+
:notify_users_at, :block_changes_at, :restrictions, :features
|
58
63
|
|
59
64
|
validates_presence_of :subscriber
|
60
65
|
validates_presence_of :mail
|
61
66
|
validates_presence_of :company, allow_blank: true
|
62
67
|
validates_presence_of :domain, if: :validate_domain?
|
68
|
+
validates_inclusion_of :plan, in: -> { OpenProject::Token::AVAILABLE_PLAN_NAMES }, allow_blank: true
|
63
69
|
|
64
70
|
validates_each(
|
65
71
|
:starts_at, :issued_at, :expires_at, :notify_admins_at, :notify_users_at, :block_changes_at,
|
66
|
-
allow_blank: true
|
67
|
-
|
68
|
-
record.errors.add attr,
|
72
|
+
allow_blank: true
|
73
|
+
) do |record, attr, value|
|
74
|
+
record.errors.add attr, "is not a date" if !value.is_a?(Date)
|
69
75
|
end
|
70
76
|
|
71
77
|
validates_each :restrictions, allow_nil: true do |record, attr, value|
|
@@ -77,23 +83,36 @@ module OpenProject
|
|
77
83
|
end
|
78
84
|
|
79
85
|
def will_expire?
|
80
|
-
|
86
|
+
expires_at
|
81
87
|
end
|
82
88
|
|
83
89
|
def will_notify_admins?
|
84
|
-
|
90
|
+
notify_admins_at
|
85
91
|
end
|
86
92
|
|
87
93
|
def will_notify_users?
|
88
|
-
|
94
|
+
notify_users_at
|
89
95
|
end
|
90
96
|
|
91
97
|
def will_block_changes?
|
92
|
-
|
98
|
+
block_changes_at
|
93
99
|
end
|
94
100
|
|
95
101
|
def has_feature?(name)
|
96
|
-
|
102
|
+
available_features.include?(name.to_sym)
|
103
|
+
end
|
104
|
+
|
105
|
+
def plan=(value)
|
106
|
+
value.presence&.to_sym
|
107
|
+
end
|
108
|
+
|
109
|
+
def available_features
|
110
|
+
return @available_features if defined?(@available_features)
|
111
|
+
|
112
|
+
relevant_features = OpenProject::Token::FEATURES_PER_PLAN[plan] || []
|
113
|
+
additional_features = features || []
|
114
|
+
|
115
|
+
@available_features = (relevant_features + additional_features).uniq
|
97
116
|
end
|
98
117
|
|
99
118
|
##
|
@@ -108,7 +127,7 @@ module OpenProject
|
|
108
127
|
def expired?(reprieve: true)
|
109
128
|
offset = reprieve ? reprieve_days.to_i : 0
|
110
129
|
|
111
|
-
will_expire? && Date.today >=
|
130
|
+
will_expire? && Date.today >= expires_at.next_day(offset)
|
112
131
|
end
|
113
132
|
|
114
133
|
##
|
@@ -118,19 +137,19 @@ module OpenProject
|
|
118
137
|
def reprieve_days_left
|
119
138
|
return nil unless reprieve_days.to_i > 0 && expired?(reprieve: false)
|
120
139
|
|
121
|
-
(
|
140
|
+
(expires_at.next_day(reprieve_days.to_i) - Date.today).to_i
|
122
141
|
end
|
123
142
|
|
124
143
|
def notify_admins?
|
125
|
-
will_notify_admins? && Date.today >=
|
144
|
+
will_notify_admins? && Date.today >= notify_admins_at
|
126
145
|
end
|
127
146
|
|
128
147
|
def notify_users?
|
129
|
-
will_notify_users? && Date.today >=
|
148
|
+
will_notify_users? && Date.today >= notify_users_at
|
130
149
|
end
|
131
150
|
|
132
151
|
def block_changes?
|
133
|
-
will_block_changes? && Date.today >=
|
152
|
+
will_block_changes? && Date.today >= block_changes_at
|
134
153
|
end
|
135
154
|
|
136
155
|
# tokens with no version or a version lower than 2.0 don't have the attributes company or domain
|
@@ -157,34 +176,35 @@ module OpenProject
|
|
157
176
|
def attributes
|
158
177
|
hash = {}
|
159
178
|
|
160
|
-
hash["version"] =
|
161
|
-
hash["subscriber"] =
|
162
|
-
hash["mail"] =
|
163
|
-
hash["company"] =
|
164
|
-
hash["domain"] =
|
179
|
+
hash["version"] = version
|
180
|
+
hash["subscriber"] = subscriber
|
181
|
+
hash["mail"] = mail
|
182
|
+
hash["company"] = company
|
183
|
+
hash["domain"] = domain
|
184
|
+
hash["plan"] = plan
|
165
185
|
|
166
|
-
hash["issued_at"] =
|
167
|
-
hash["starts_at"] =
|
168
|
-
hash["expires_at"] =
|
169
|
-
hash["reprieve_days"] =
|
186
|
+
hash["issued_at"] = issued_at
|
187
|
+
hash["starts_at"] = starts_at
|
188
|
+
hash["expires_at"] = expires_at if will_expire?
|
189
|
+
hash["reprieve_days"] = reprieve_days if will_expire?
|
170
190
|
|
171
|
-
hash["notify_admins_at"] =
|
172
|
-
hash["notify_users_at"] =
|
173
|
-
hash["block_changes_at"] =
|
191
|
+
hash["notify_admins_at"] = notify_admins_at if will_notify_admins?
|
192
|
+
hash["notify_users_at"] = notify_users_at if will_notify_users?
|
193
|
+
hash["block_changes_at"] = block_changes_at if will_block_changes?
|
174
194
|
|
175
|
-
hash["restrictions"] =
|
176
|
-
hash["features"] =
|
195
|
+
hash["restrictions"] = restrictions if restricted?
|
196
|
+
hash["features"] = features
|
177
197
|
|
178
198
|
hash
|
179
199
|
end
|
180
200
|
|
181
|
-
def to_json
|
182
|
-
JSON.dump(
|
201
|
+
def to_json(*_args)
|
202
|
+
JSON.dump(attributes)
|
183
203
|
end
|
184
204
|
|
185
205
|
def from_json(json)
|
186
206
|
load_attributes(JSON.parse(json))
|
187
|
-
rescue => e
|
207
|
+
rescue StandardError => e
|
188
208
|
raise ParseError, "Failed to load from json: #{e}"
|
189
209
|
end
|
190
210
|
|
@@ -194,11 +214,10 @@ module OpenProject
|
|
194
214
|
@parsed_domain
|
195
215
|
end
|
196
216
|
|
197
|
-
|
198
217
|
private
|
199
218
|
|
200
219
|
def load_attributes(attributes)
|
201
|
-
attributes =
|
220
|
+
attributes = attributes.transform_keys(&:to_s)
|
202
221
|
|
203
222
|
@version = read_version attributes
|
204
223
|
@subscriber = attributes["subscriber"]
|
@@ -208,7 +227,13 @@ module OpenProject
|
|
208
227
|
|
209
228
|
date_attribute_keys.each do |attr|
|
210
229
|
value = attributes[attr]
|
211
|
-
|
230
|
+
if value.is_a?(String)
|
231
|
+
value = begin
|
232
|
+
Date.parse(value)
|
233
|
+
rescue StandardError
|
234
|
+
nil
|
235
|
+
end
|
236
|
+
end
|
212
237
|
|
213
238
|
next unless value
|
214
239
|
|
@@ -218,24 +243,26 @@ module OpenProject
|
|
218
243
|
reprieve_days = attributes["reprieve_days"]
|
219
244
|
|
220
245
|
if will_expire?
|
221
|
-
if reprieve_days.nil? && apply_default_reprieve?(version)
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
246
|
+
@reprieve_days = if reprieve_days.nil? && apply_default_reprieve?(version)
|
247
|
+
7
|
248
|
+
else
|
249
|
+
reprieve_days.to_i
|
250
|
+
end
|
226
251
|
end
|
227
252
|
|
228
253
|
restrictions = attributes["restrictions"]
|
229
254
|
|
230
|
-
if restrictions
|
231
|
-
restrictions =
|
255
|
+
if restrictions.is_a?(Hash)
|
256
|
+
restrictions = restrictions.transform_keys(&:to_sym)
|
232
257
|
@restrictions = restrictions
|
233
258
|
end
|
234
259
|
|
235
|
-
features = attributes[
|
236
|
-
if features
|
260
|
+
features = attributes["features"]
|
261
|
+
if features.is_a?(Array)
|
237
262
|
@features = features.map(&:to_sym)
|
238
263
|
end
|
264
|
+
|
265
|
+
@plan = attributes["plan"].presence&.to_sym || OpenProject::Token::DEFAULT_PLAN
|
239
266
|
end
|
240
267
|
|
241
268
|
##
|
@@ -272,7 +299,7 @@ module OpenProject
|
|
272
299
|
end
|
273
300
|
|
274
301
|
def read_domain!(input)
|
275
|
-
return input if input.nil? || !(input.start_with?(
|
302
|
+
return input if input.nil? || !(input.start_with?("/") && input.end_with?("/"))
|
276
303
|
|
277
304
|
# Omit the slashes of the input
|
278
305
|
Regexp.new input[1..-2]
|
@@ -287,7 +314,7 @@ module OpenProject
|
|
287
314
|
end
|
288
315
|
|
289
316
|
def domain_required_from_version
|
290
|
-
@domain_required_from_version ||= Gem::Version.new(
|
317
|
+
@domain_required_from_version ||= Gem::Version.new("2.0")
|
291
318
|
end
|
292
319
|
|
293
320
|
##
|
data/lib/openproject-token.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: openproject-token
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- OpenProject GmbH
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-03-25 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: activemodel
|
@@ -24,49 +23,6 @@ dependencies:
|
|
24
23
|
- - ">="
|
25
24
|
- !ruby/object:Gem::Version
|
26
25
|
version: '0'
|
27
|
-
- !ruby/object:Gem::Dependency
|
28
|
-
name: pry
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
30
|
-
requirements:
|
31
|
-
- - "~>"
|
32
|
-
- !ruby/object:Gem::Version
|
33
|
-
version: '0.10'
|
34
|
-
type: :development
|
35
|
-
prerelease: false
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
37
|
-
requirements:
|
38
|
-
- - "~>"
|
39
|
-
- !ruby/object:Gem::Version
|
40
|
-
version: '0.10'
|
41
|
-
- !ruby/object:Gem::Dependency
|
42
|
-
name: rspec
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
44
|
-
requirements:
|
45
|
-
- - "~>"
|
46
|
-
- !ruby/object:Gem::Version
|
47
|
-
version: '3.5'
|
48
|
-
type: :development
|
49
|
-
prerelease: false
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
51
|
-
requirements:
|
52
|
-
- - "~>"
|
53
|
-
- !ruby/object:Gem::Version
|
54
|
-
version: '3.5'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: debug
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ">="
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
69
|
-
description:
|
70
26
|
email: info@openproject.com
|
71
27
|
executables: []
|
72
28
|
extensions: []
|
@@ -77,13 +33,14 @@ files:
|
|
77
33
|
- lib/open_project/token.rb
|
78
34
|
- lib/open_project/token/armor.rb
|
79
35
|
- lib/open_project/token/extractor.rb
|
36
|
+
- lib/open_project/token/plans.rb
|
80
37
|
- lib/open_project/token/version.rb
|
81
38
|
- lib/openproject-token.rb
|
82
39
|
homepage: https://www.openproject.org
|
83
40
|
licenses:
|
84
41
|
- GPL-3.0
|
85
|
-
metadata:
|
86
|
-
|
42
|
+
metadata:
|
43
|
+
rubygems_mfa_required: 'true'
|
87
44
|
rdoc_options: []
|
88
45
|
require_paths:
|
89
46
|
- lib
|
@@ -91,15 +48,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
91
48
|
requirements:
|
92
49
|
- - ">="
|
93
50
|
- !ruby/object:Gem::Version
|
94
|
-
version:
|
51
|
+
version: 3.4.2
|
95
52
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
96
53
|
requirements:
|
97
54
|
- - ">="
|
98
55
|
- !ruby/object:Gem::Version
|
99
56
|
version: '0'
|
100
57
|
requirements: []
|
101
|
-
rubygems_version: 3.
|
102
|
-
signing_key:
|
58
|
+
rubygems_version: 3.6.5
|
103
59
|
specification_version: 4
|
104
60
|
summary: OpenProject EE token reader
|
105
61
|
test_files: []
|