encoded_token 1.0.2 → 2.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 +4 -4
- data/lib/encoded_token/cipher.rb +283 -0
- data/lib/encoded_token/configuration.rb +289 -0
- data/lib/encoded_token/encoder/legacy_decoder.rb +86 -0
- data/lib/encoded_token/encoder/legacy_encoder.rb +96 -0
- data/lib/encoded_token/encoder/utf8_decoder.rb +89 -0
- data/lib/encoded_token/encoder/utf8_encoder.rb +103 -0
- data/lib/encoded_token/encoder.rb +158 -171
- data/lib/encoded_token/encryptor.rb +134 -0
- data/lib/encoded_token/error_messages.rb +101 -0
- data/lib/encoded_token/utils.rb +131 -0
- data/lib/encoded_token/version.rb +11 -13
- data/lib/encoded_token.rb +120 -22
- metadata +17 -14
- data/lib/encoded_token/base.rb +0 -280
- data/lib/encoded_token/decoder.rb +0 -178
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 50fba0475e4de9c89299be7eec8a694dd602561b001d42c77d36cb16d654cfd6
|
|
4
|
+
data.tar.gz: 443f35ddbdc837ae6dd851ae426342fb4a3c305f51cda9fdb2dfa0dc1652d25b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1dfad2067b675873e7c2c786180e9641c7172202a5ff3d282b7cb111756e5e2eea1e59a43d94cb29c1b701e1260ddd773c43d82e0fc0601e9128753707b64b16
|
|
7
|
+
data.tar.gz: ae1f469da08ca88d1d1490877a735d12b1e12ac2e754993ae069ff730afa778cd6abd2639651008ca038be5f5171cfe23e706071a4c751ad11f03455090ddaa4
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedToken
|
|
4
|
+
##
|
|
5
|
+
# EncodedToken::Cipher
|
|
6
|
+
#
|
|
7
|
+
# This class represents a cipher used by EncodedToken::Encryptor
|
|
8
|
+
# for encryption.
|
|
9
|
+
#
|
|
10
|
+
class Cipher
|
|
11
|
+
|
|
12
|
+
# ======================================================================
|
|
13
|
+
# = Macros
|
|
14
|
+
# ======================================================================
|
|
15
|
+
|
|
16
|
+
include EncodedToken::ErrorMessages
|
|
17
|
+
|
|
18
|
+
# ======================================================================
|
|
19
|
+
# = Constants
|
|
20
|
+
# ======================================================================
|
|
21
|
+
|
|
22
|
+
CIPHER_COUNT = 16
|
|
23
|
+
CIPHER_CHARS = (('0'..'9').to_a + ('a'..'z').to_a + ('A'..'Z').to_a).freeze
|
|
24
|
+
CIPHER_TEXT = CIPHER_CHARS.join.freeze
|
|
25
|
+
CIPHER_INDEX_MAP = CIPHER_TEXT.each_char.with_index.to_h.freeze
|
|
26
|
+
HEX_CHARS = (('a'..'f').to_a + ('A'..'F').to_a).freeze
|
|
27
|
+
HEX_NUMS = ('0'..'9').to_a.freeze
|
|
28
|
+
SPECIAL_CHARS = ['-'].freeze
|
|
29
|
+
HEX_TEXT = (HEX_NUMS + HEX_CHARS + SPECIAL_CHARS).join.freeze
|
|
30
|
+
HEX_INDEX_MAP = HEX_TEXT.each_char.with_index.to_h.freeze
|
|
31
|
+
MAX_CIPHER_COUNT = 60
|
|
32
|
+
|
|
33
|
+
# ======================================================================
|
|
34
|
+
# = Instance Methods
|
|
35
|
+
# ======================================================================
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Initializes a new Cipher instance.
|
|
39
|
+
#
|
|
40
|
+
# @param [Integer] seed
|
|
41
|
+
# the seed for randomization.
|
|
42
|
+
# @param [Date, Time, nil] expiry_date
|
|
43
|
+
# the date when this cipher expires.
|
|
44
|
+
# @param [Integer] cipher_count
|
|
45
|
+
# the number of keys to generate.
|
|
46
|
+
#
|
|
47
|
+
def initialize(seed, expiry_date = nil, cipher_count = EncodedToken.configuration.cipher_count)
|
|
48
|
+
@seed = seed
|
|
49
|
+
@expiry_date = expiry_date
|
|
50
|
+
@cipher_count = cipher_count
|
|
51
|
+
|
|
52
|
+
build_cipher!
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# Returns the seed used for this cipher.
|
|
57
|
+
#
|
|
58
|
+
# @return [Integer]
|
|
59
|
+
#
|
|
60
|
+
def seed
|
|
61
|
+
@seed
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# Returns a duplicate of the root key.
|
|
66
|
+
#
|
|
67
|
+
# @return [String]
|
|
68
|
+
#
|
|
69
|
+
def root_key
|
|
70
|
+
@root_key.dup
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# Sets the root key and resets the rotation.
|
|
75
|
+
#
|
|
76
|
+
# @param [String] new_key
|
|
77
|
+
# the new root key.
|
|
78
|
+
#
|
|
79
|
+
# @return [String]
|
|
80
|
+
# the new root key.
|
|
81
|
+
#
|
|
82
|
+
# @raise [ArgumentError]
|
|
83
|
+
# if the key is invalid.
|
|
84
|
+
#
|
|
85
|
+
def root_key=(new_key)
|
|
86
|
+
fail_with(:invalid_root_key) unless valid_key?(new_key)
|
|
87
|
+
@root_key = new_key
|
|
88
|
+
reset!
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# Resets the key rotation to the root key.
|
|
93
|
+
#
|
|
94
|
+
# @return [nil]
|
|
95
|
+
#
|
|
96
|
+
def reset!
|
|
97
|
+
@rotation_key = root_key
|
|
98
|
+
@rotation_keys = keys.dup
|
|
99
|
+
|
|
100
|
+
rotate_keys!(target_key: root_key)
|
|
101
|
+
nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# Rotates the current key.
|
|
106
|
+
#
|
|
107
|
+
# @param [String, nil] target_key
|
|
108
|
+
# the key to rotate to.
|
|
109
|
+
#
|
|
110
|
+
# @return [String]
|
|
111
|
+
# the new current rotation key.
|
|
112
|
+
#
|
|
113
|
+
# @raise [ArgumentError]
|
|
114
|
+
# if the target key is invalid.
|
|
115
|
+
#
|
|
116
|
+
def rotate_keys!(target_key: nil)
|
|
117
|
+
if target_key
|
|
118
|
+
# noinspection RubyMismatchedArgumentType
|
|
119
|
+
unless valid_key?(target_key)
|
|
120
|
+
fail_with(:invalid_root_key)
|
|
121
|
+
end
|
|
122
|
+
rotation_keys.rotate! until rotation_keys.first == target_key
|
|
123
|
+
else
|
|
124
|
+
rotation_keys.rotate!
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
@rotation_key = rotation_keys.first
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
##
|
|
131
|
+
# Checks if the given key is valid for this cipher.
|
|
132
|
+
#
|
|
133
|
+
# @param [String] key
|
|
134
|
+
#
|
|
135
|
+
# @return [Boolean]
|
|
136
|
+
#
|
|
137
|
+
def valid_key?(key)
|
|
138
|
+
keys.include?(key)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
##
|
|
142
|
+
# Returns the cipher text for the current rotation key.
|
|
143
|
+
#
|
|
144
|
+
# @return [String]
|
|
145
|
+
#
|
|
146
|
+
def text
|
|
147
|
+
key_texts[@rotation_key]
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
##
|
|
151
|
+
# Returns the index map for the current rotation key.
|
|
152
|
+
#
|
|
153
|
+
# @return [Hash<String, Integer>]
|
|
154
|
+
#
|
|
155
|
+
def text_map
|
|
156
|
+
key_maps[@rotation_key]
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
##
|
|
160
|
+
# Returns the padding size for the root key.
|
|
161
|
+
#
|
|
162
|
+
# @return [Integer]
|
|
163
|
+
#
|
|
164
|
+
def padding
|
|
165
|
+
@key_paddings[@root_key]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
##
|
|
169
|
+
# Returns the hex text used for encryption mapping.
|
|
170
|
+
#
|
|
171
|
+
# @return [String]
|
|
172
|
+
#
|
|
173
|
+
def hex_text
|
|
174
|
+
HEX_TEXT
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
##
|
|
178
|
+
# Returns the index map for the hex text.
|
|
179
|
+
#
|
|
180
|
+
# @return [Hash<String, Integer>]
|
|
181
|
+
#
|
|
182
|
+
def hex_map
|
|
183
|
+
HEX_INDEX_MAP
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
##
|
|
187
|
+
# Checks if the cipher has expired.
|
|
188
|
+
#
|
|
189
|
+
# @return [Boolean]
|
|
190
|
+
#
|
|
191
|
+
def expired?
|
|
192
|
+
@expiry_date ? Time.now > @expiry_date : false
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
##
|
|
196
|
+
# Returns the number of keys in the cipher.
|
|
197
|
+
#
|
|
198
|
+
# @return [Integer]
|
|
199
|
+
#
|
|
200
|
+
def cipher_count
|
|
201
|
+
@cipher_count
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
##
|
|
206
|
+
# Returns the current rotation key.
|
|
207
|
+
#
|
|
208
|
+
# @return [String]
|
|
209
|
+
#
|
|
210
|
+
def rotation_key
|
|
211
|
+
@rotation_key
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
##
|
|
215
|
+
# Returns the map of keys to their respective cipher texts.
|
|
216
|
+
#
|
|
217
|
+
# @return [Hash<String, String>]
|
|
218
|
+
#
|
|
219
|
+
def key_texts
|
|
220
|
+
@key_texts ||= {}
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
##
|
|
224
|
+
# Returns the map of keys to their respective index maps.
|
|
225
|
+
#
|
|
226
|
+
# @return [Hash<String, Hash>]
|
|
227
|
+
#
|
|
228
|
+
def key_maps
|
|
229
|
+
@key_maps ||= {}
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
##
|
|
233
|
+
# Returns the map of keys to their respective padding sizes.
|
|
234
|
+
#
|
|
235
|
+
# @return [Hash<String, Integer>]
|
|
236
|
+
#
|
|
237
|
+
def key_paddings
|
|
238
|
+
@key_paddings ||= {}
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
##
|
|
242
|
+
# Returns the list of keys.
|
|
243
|
+
#
|
|
244
|
+
# @return [Array<String>]
|
|
245
|
+
#
|
|
246
|
+
def keys
|
|
247
|
+
@keys ||= []
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
##
|
|
251
|
+
# Returns the list of keys in their current rotation order.
|
|
252
|
+
#
|
|
253
|
+
# @return [Array<String>]
|
|
254
|
+
#
|
|
255
|
+
def rotation_keys
|
|
256
|
+
@rotation_keys ||= []
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
# = Private Instance Methods
|
|
260
|
+
# ======================================================================
|
|
261
|
+
#
|
|
262
|
+
private
|
|
263
|
+
|
|
264
|
+
def build_cipher!
|
|
265
|
+
return if keys.any?
|
|
266
|
+
|
|
267
|
+
randomizer = Random.new(seed)
|
|
268
|
+
@keys = CIPHER_CHARS.sample(cipher_count, random: randomizer)
|
|
269
|
+
@root_key = keys.sample(random: randomizer)
|
|
270
|
+
@rotation_key = @root_key
|
|
271
|
+
@rotation_keys = keys.dup
|
|
272
|
+
|
|
273
|
+
rotate_keys! target_key: @rotation_key
|
|
274
|
+
|
|
275
|
+
keys.each do |key|
|
|
276
|
+
key_paddings[key] = randomizer.rand(0..10)
|
|
277
|
+
key_texts[key] = CIPHER_CHARS.sample(HEX_TEXT.size, random: randomizer).join
|
|
278
|
+
key_maps[key] = key_texts[key].each_char.with_index.to_h
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
end # CipherPack
|
|
283
|
+
end
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedToken
|
|
4
|
+
##
|
|
5
|
+
# EncodedToken::Configuration
|
|
6
|
+
#
|
|
7
|
+
# This class manages the single configuration instance for EncodedToken.
|
|
8
|
+
#
|
|
9
|
+
class Configuration
|
|
10
|
+
|
|
11
|
+
# ======================================================================
|
|
12
|
+
# = Macros
|
|
13
|
+
# ======================================================================
|
|
14
|
+
|
|
15
|
+
include EncodedToken::ErrorMessages
|
|
16
|
+
include EncodedToken::Utils
|
|
17
|
+
|
|
18
|
+
# ======================================================================
|
|
19
|
+
# = Constants
|
|
20
|
+
# ======================================================================
|
|
21
|
+
|
|
22
|
+
DEFAULT_CIPHER_COUNT = 16
|
|
23
|
+
DEFAULT_MIN_TOKEN_SIZE = 55
|
|
24
|
+
DEFAULT_MAX_INPUT_SIZE = 1000
|
|
25
|
+
|
|
26
|
+
# ======================================================================
|
|
27
|
+
# = Class methods
|
|
28
|
+
# ======================================================================
|
|
29
|
+
|
|
30
|
+
private_class_method :new
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Returns the singleton instance of the Configuration.
|
|
36
|
+
#
|
|
37
|
+
# @return [Configuration]
|
|
38
|
+
#
|
|
39
|
+
def instance
|
|
40
|
+
@instance ||= new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# Checks if the configuration is locked.
|
|
45
|
+
#
|
|
46
|
+
# @return [Boolean]
|
|
47
|
+
#
|
|
48
|
+
def locked?
|
|
49
|
+
instance.locked?
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Checks if the configuration is unlocked.
|
|
54
|
+
#
|
|
55
|
+
# @return [Boolean]
|
|
56
|
+
#
|
|
57
|
+
def unlocked?
|
|
58
|
+
instance.unlocked?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Locks the configuration to prevent further changes.
|
|
63
|
+
#
|
|
64
|
+
# @return [true]
|
|
65
|
+
# the lock status
|
|
66
|
+
#
|
|
67
|
+
def lock!
|
|
68
|
+
instance.lock!
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Unlocks the configuration.
|
|
73
|
+
#
|
|
74
|
+
# @return [false]
|
|
75
|
+
# the lock status
|
|
76
|
+
#
|
|
77
|
+
def unlock!
|
|
78
|
+
instance.unlock!
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end # class << self
|
|
82
|
+
|
|
83
|
+
# ======================================================================
|
|
84
|
+
# = Instance Methods
|
|
85
|
+
# ======================================================================
|
|
86
|
+
|
|
87
|
+
def initialize
|
|
88
|
+
@cipher_count = DEFAULT_CIPHER_COUNT
|
|
89
|
+
@expiring_seeds = {}
|
|
90
|
+
@locked = false
|
|
91
|
+
@max_input_size = DEFAULT_MAX_INPUT_SIZE
|
|
92
|
+
@min_token_size = DEFAULT_MIN_TOKEN_SIZE
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
# Checks if the configuration is locked.
|
|
97
|
+
#
|
|
98
|
+
# @return [Boolean]
|
|
99
|
+
#
|
|
100
|
+
def locked?
|
|
101
|
+
!!@locked
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
##
|
|
105
|
+
# Checks if the configuration is unlocked.
|
|
106
|
+
#
|
|
107
|
+
# @return [Boolean]
|
|
108
|
+
#
|
|
109
|
+
def unlocked?
|
|
110
|
+
!locked?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
##
|
|
114
|
+
# Locks the configuration.
|
|
115
|
+
#
|
|
116
|
+
# @return [true] the lock status
|
|
117
|
+
#
|
|
118
|
+
def lock!
|
|
119
|
+
@locked = true
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
##
|
|
123
|
+
# Unlocks the configuration.
|
|
124
|
+
#
|
|
125
|
+
# @return [false] the lock status
|
|
126
|
+
#
|
|
127
|
+
def unlock!
|
|
128
|
+
@locked = false
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
##
|
|
132
|
+
# Adds an expiring seed to the configuration.
|
|
133
|
+
#
|
|
134
|
+
# @param [Integer, String] expiring_seed
|
|
135
|
+
# the seed for the expiring cipher.
|
|
136
|
+
# @param [Date, Time] expiry_date
|
|
137
|
+
# the date when the seed expires.
|
|
138
|
+
# @param [Integer] cipher_count
|
|
139
|
+
# the number of keys for the expiring cipher.
|
|
140
|
+
#
|
|
141
|
+
# @return [Hash]
|
|
142
|
+
# the details of the added expiring seed.
|
|
143
|
+
#
|
|
144
|
+
# @raise [ArgumentError]
|
|
145
|
+
# if the arguments are invalid.
|
|
146
|
+
#
|
|
147
|
+
def add_expiring_seed(expiring_seed, expiry_date, cipher_count = DEFAULT_CIPHER_COUNT)
|
|
148
|
+
assert_configuration_can_change!
|
|
149
|
+
|
|
150
|
+
key = Integer(expiring_seed).abs rescue nil
|
|
151
|
+
c_count = Integer(cipher_count).abs rescue nil
|
|
152
|
+
|
|
153
|
+
if key && valid_date?(expiry_date) && valid_cipher_count?(c_count)
|
|
154
|
+
@expiring_seeds[key] = {expires_on: expiry_date, cipher_count: c_count}
|
|
155
|
+
else
|
|
156
|
+
fail_with(:invalid_expiry_argument)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
##
|
|
161
|
+
# Returns the default cipher count.
|
|
162
|
+
#
|
|
163
|
+
# @return [Integer]
|
|
164
|
+
#
|
|
165
|
+
def cipher_count
|
|
166
|
+
@cipher_count
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
##
|
|
170
|
+
# Sets the default cipher count.
|
|
171
|
+
#
|
|
172
|
+
# @param [Integer] new_count
|
|
173
|
+
# the new cipher count.
|
|
174
|
+
#
|
|
175
|
+
# @raise [ArgumentError, RuntimeError]
|
|
176
|
+
# if the count is invalid or configuration is locked.
|
|
177
|
+
#
|
|
178
|
+
def cipher_count=(new_count)
|
|
179
|
+
assert_configuration_can_change!
|
|
180
|
+
|
|
181
|
+
value = Integer(new_count).abs rescue nil
|
|
182
|
+
fail_with(:invalid_cipher_count) unless valid_cipher_count?(value)
|
|
183
|
+
|
|
184
|
+
@cipher_count = value
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
##
|
|
188
|
+
# Returns the map of expiring seeds.
|
|
189
|
+
#
|
|
190
|
+
# @return [Hash<Integer, Hash>]
|
|
191
|
+
#
|
|
192
|
+
def expiring_seeds
|
|
193
|
+
@expiring_seeds || {}
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
##
|
|
197
|
+
# Returns the maximum input size.
|
|
198
|
+
#
|
|
199
|
+
# @return [Integer]
|
|
200
|
+
#
|
|
201
|
+
def max_input_size
|
|
202
|
+
@max_input_size
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
##
|
|
206
|
+
# Sets the maximum input size.
|
|
207
|
+
#
|
|
208
|
+
# @param [Integer] max_size
|
|
209
|
+
#
|
|
210
|
+
# @raise [ArgumentError, RuntimeError]
|
|
211
|
+
# if the size is invalid or configuration is locked.
|
|
212
|
+
#
|
|
213
|
+
def max_input_size=(max_size)
|
|
214
|
+
assert_configuration_can_change!
|
|
215
|
+
|
|
216
|
+
value = Integer(max_size).abs rescue nil
|
|
217
|
+
fail_with(:invalid_max_input_size) if value.nil? || !value.positive?
|
|
218
|
+
|
|
219
|
+
@max_input_size = value
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
##
|
|
223
|
+
# Returns the minimum token size.
|
|
224
|
+
#
|
|
225
|
+
# @return [Integer]
|
|
226
|
+
#
|
|
227
|
+
def min_token_size
|
|
228
|
+
@min_token_size
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
##
|
|
232
|
+
# Sets the minimum token size.
|
|
233
|
+
#
|
|
234
|
+
# @param [Integer] min_size
|
|
235
|
+
#
|
|
236
|
+
# @raise [ArgumentError, RuntimeError]
|
|
237
|
+
# if the size is invalid or configuration is locked.
|
|
238
|
+
#
|
|
239
|
+
def min_token_size=(min_size)
|
|
240
|
+
assert_configuration_can_change!
|
|
241
|
+
|
|
242
|
+
value = Integer(min_size).abs rescue nil
|
|
243
|
+
fail_with(:invalid_min_token_size) if value.nil? || !value.positive?
|
|
244
|
+
|
|
245
|
+
@min_token_size = value
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
##
|
|
249
|
+
# Returns the master seed.
|
|
250
|
+
#
|
|
251
|
+
# @return [Integer]
|
|
252
|
+
#
|
|
253
|
+
def seed
|
|
254
|
+
@seed
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
##
|
|
258
|
+
# Sets the master seed.
|
|
259
|
+
#
|
|
260
|
+
# @param [Integer] new_seed
|
|
261
|
+
#
|
|
262
|
+
# @raise [ArgumentError, RuntimeError]
|
|
263
|
+
# if the seed is invalid or configuration is locked.
|
|
264
|
+
#
|
|
265
|
+
def seed=(new_seed)
|
|
266
|
+
assert_configuration_can_change!
|
|
267
|
+
|
|
268
|
+
value = Integer(new_seed).abs rescue nil
|
|
269
|
+
fail_with(:invalid_seed) if value.nil?
|
|
270
|
+
|
|
271
|
+
@seed = value
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# = Private Instance Methods
|
|
275
|
+
# ======================================================================
|
|
276
|
+
#
|
|
277
|
+
private
|
|
278
|
+
|
|
279
|
+
def assert_configuration_can_change!
|
|
280
|
+
fail_with(:no_config_change_allowed) if locked?
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def valid_cipher_count?(count)
|
|
284
|
+
return true if count.is_a?(Integer) && count >= 8 && count <= Cipher::MAX_CIPHER_COUNT
|
|
285
|
+
fail_with(:invalid_expiry_argument)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
end # class configuration
|
|
289
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedToken
|
|
4
|
+
module Encoder
|
|
5
|
+
##
|
|
6
|
+
# EncodedToken::Encode::LegacyDecoder
|
|
7
|
+
#
|
|
8
|
+
# This module represents a decoder used by EncodedToken::Encryptor
|
|
9
|
+
# to decode legacy tokens.
|
|
10
|
+
#
|
|
11
|
+
module LegacyDecoder
|
|
12
|
+
|
|
13
|
+
# ======================================================================
|
|
14
|
+
# = Macros
|
|
15
|
+
# ======================================================================
|
|
16
|
+
|
|
17
|
+
extend EncodedToken::Utils
|
|
18
|
+
extend EncodedToken::ErrorMessages
|
|
19
|
+
|
|
20
|
+
# ======================================================================
|
|
21
|
+
# = Singleton Methods
|
|
22
|
+
# ======================================================================
|
|
23
|
+
|
|
24
|
+
class << self
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Decodes the given legacy token.
|
|
28
|
+
#
|
|
29
|
+
# @param [String] token
|
|
30
|
+
# the token to decode.
|
|
31
|
+
#
|
|
32
|
+
# @return [String, nil]
|
|
33
|
+
# the decoded value, or nil if unsuccessful.
|
|
34
|
+
#
|
|
35
|
+
# @raise [ArgumentError]
|
|
36
|
+
# if the token is invalid.
|
|
37
|
+
#
|
|
38
|
+
def decode(token)
|
|
39
|
+
assert_valid_token!(token)
|
|
40
|
+
parse_token(token)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# = Private Singleton Methods
|
|
44
|
+
# ======================================================================
|
|
45
|
+
#
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def assert_valid_token!(token)
|
|
49
|
+
fail_with(:non_string_token) unless token.is_a?(String)
|
|
50
|
+
fail_with(:invalid_token_characters) unless valid_token_text?(token)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# rubocop:disable Metrics/MethodLength
|
|
54
|
+
#
|
|
55
|
+
def parse_token(original_token)
|
|
56
|
+
Encoder.decoding_ciphers.each do |cipher|
|
|
57
|
+
begin
|
|
58
|
+
token = original_token.dup
|
|
59
|
+
key = token.slice!(0)
|
|
60
|
+
encryptor = EncodedToken::Encryptor.new(cipher, key) rescue next
|
|
61
|
+
|
|
62
|
+
# get the id size * padding
|
|
63
|
+
id_size = encryptor.decrypt(token.slice!(0, 2)).to_i(16)
|
|
64
|
+
padding = encryptor.padding
|
|
65
|
+
|
|
66
|
+
# remove the padding, get and decrypt the id
|
|
67
|
+
token.slice!(0, padding)
|
|
68
|
+
enc_id = token.slice!(0, id_size)
|
|
69
|
+
id = encryptor.decrypt(enc_id)
|
|
70
|
+
|
|
71
|
+
# next cipher if it's a UUID or numeric ID
|
|
72
|
+
return id if valid_integer?(id) || valid_uuid_format?(id)
|
|
73
|
+
|
|
74
|
+
rescue
|
|
75
|
+
next
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
nil
|
|
80
|
+
end
|
|
81
|
+
# rubocop:enable Metrics/MethodLength
|
|
82
|
+
|
|
83
|
+
end # class << self
|
|
84
|
+
end # module LegacyDecoder
|
|
85
|
+
end # module encoder
|
|
86
|
+
end # module
|