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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26f34040f3ddbd6546761e8e6e33f5081b95550d3795dccccdf86f3610952b52
4
- data.tar.gz: d387c8130062a25183db07cbb3f43d7319836d143e3857d5271e24d99ef208eb
3
+ metadata.gz: 24a0ac03322b7a1471a9b638111f18d7a038e3171fb1e9156e6b7d04d0503053
4
+ data.tar.gz: 47d565cc5231417c67dc9db5639bdb8eecb8e12203c7f43cc9e789ff3c66df23
5
5
  SHA512:
6
- metadata.gz: 7cfc8e13d23e2d50c5f769ea7482f526b92ecf6a83075ca03790c3415ecf959e073f60f611c217f7546f02b080242d3075cfc32a9185d5e69749c895aa398659
7
- data.tar.gz: bf8dd5ea2f830eeca4854560f560bcee3ccbbb6993e1d5868bf7caf40c24e0547b454e82b5aa9fde7384ac38314426cb9cac28f838197312596f6dfcf54ccc78
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 = 'OPENPROJECT-EE TOKEN'
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
- ''.tap do |s|
19
- s << header << "\n"
20
-
21
- s << data.strip << "\n"
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, 'Failed to parse armored text.'
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::AES128.new(:CBC)
45
+ cipher = OpenSSL::Cipher.new("aes-128-cbc")
44
46
  cipher.decrypt
45
47
 
46
48
  begin
47
- cipher.key = aes_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 = aes_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
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module OpenProject
2
4
  class Token
3
- VERSION = "4.0.0"
5
+ VERSION = "5.1.0"
4
6
  end
5
7
  end
@@ -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 'active_model'
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
- attr_accessor :starts_at, :issued_at, :expires_at
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) do |record, attr, value|
67
-
68
- record.errors.add attr, 'is not a date' if !value.is_a?(Date)
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
- self.expires_at
86
+ expires_at
81
87
  end
82
88
 
83
89
  def will_notify_admins?
84
- self.notify_admins_at
90
+ notify_admins_at
85
91
  end
86
92
 
87
93
  def will_notify_users?
88
- self.notify_users_at
94
+ notify_users_at
89
95
  end
90
96
 
91
97
  def will_block_changes?
92
- self.block_changes_at
98
+ block_changes_at
93
99
  end
94
100
 
95
101
  def has_feature?(name)
96
- features && features.include?(name.to_sym)
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 >= self.expires_at.next_day(offset)
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
- (self.expires_at.next_day(reprieve_days.to_i) - Date.today).to_i
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 >= self.notify_admins_at
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 >= self.notify_users_at
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 >= self.block_changes_at
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"] = self.version
161
- hash["subscriber"] = self.subscriber
162
- hash["mail"] = self.mail
163
- hash["company"] = self.company
164
- hash["domain"] = self.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"] = self.issued_at
167
- hash["starts_at"] = self.starts_at
168
- hash["expires_at"] = self.expires_at if self.will_expire?
169
- hash["reprieve_days"] = self.reprieve_days if self.will_expire?
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"] = self.notify_admins_at if self.will_notify_admins?
172
- hash["notify_users_at"] = self.notify_users_at if self.will_notify_users?
173
- hash["block_changes_at"] = self.block_changes_at if self.will_block_changes?
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"] = self.restrictions if self.restricted?
176
- hash["features"] = self.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(self.attributes)
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 = Hash[attributes.map { |k, v| [k.to_s, v] }]
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
- value = Date.parse(value) rescue nil if value.is_a?(String)
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
- @reprieve_days = 7
223
- else
224
- @reprieve_days = reprieve_days.to_i
225
- end
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 && restrictions.is_a?(Hash)
231
- restrictions = Hash[restrictions.map { |k, v| [k.to_sym, v] }]
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['features']
236
- if features && features.is_a?(Array)
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?('/') && input.end_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('2.0')
317
+ @domain_required_from_version ||= Gem::Version.new("2.0")
291
318
  end
292
319
 
293
320
  ##
@@ -1 +1,3 @@
1
- require 'open_project/token'
1
+ # frozen_string_literal: true
2
+
3
+ require "open_project/token"
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.0.0
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: 2023-11-08 00:00:00.000000000 Z
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
- post_install_message:
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: '0'
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.4.6
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: []