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
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedToken
|
|
4
|
+
module Encoder
|
|
5
|
+
##
|
|
6
|
+
# EncodedToken::Encode::LegacyEncoder
|
|
7
|
+
#
|
|
8
|
+
# This module represents the encoder used by EncodedToken::Encryptor
|
|
9
|
+
# to encode legacy tokens.
|
|
10
|
+
#
|
|
11
|
+
module LegacyEncoder
|
|
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
|
+
# Encodes the given ID into a legacy token.
|
|
28
|
+
#
|
|
29
|
+
# @param [Integer, String, *.to_s] id
|
|
30
|
+
# the ID to encode.
|
|
31
|
+
#
|
|
32
|
+
# @return [String]
|
|
33
|
+
# the encoded legacy token.
|
|
34
|
+
#
|
|
35
|
+
# @raise [ArgumentError]
|
|
36
|
+
# if the ID is invalid.
|
|
37
|
+
#
|
|
38
|
+
def encode(id)
|
|
39
|
+
assert_valid_id!(id)
|
|
40
|
+
generate_token(id)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# = Private Singleton Methods
|
|
44
|
+
# ======================================================================
|
|
45
|
+
#
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def assert_valid_id!(id)
|
|
49
|
+
sid = id.to_s
|
|
50
|
+
fail if sid.empty? || sid.size > 255
|
|
51
|
+
fail unless valid_hex_text?(sid)
|
|
52
|
+
fail unless valid_integer?(sid) || valid_uuid_format?(sid)
|
|
53
|
+
|
|
54
|
+
true
|
|
55
|
+
rescue StandardError
|
|
56
|
+
fail_with(:invalid_id)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# rubocop:disable Metrics/MethodLength
|
|
61
|
+
#
|
|
62
|
+
def generate_token(id, user_key = nil)
|
|
63
|
+
encryptor = EncodedToken::Encryptor.new(Encoder.encoding_cipher, user_key)
|
|
64
|
+
padding_size = encryptor.padding
|
|
65
|
+
sid = id.to_s
|
|
66
|
+
hex_sid_size = hex_id_size(sid)
|
|
67
|
+
|
|
68
|
+
token = [
|
|
69
|
+
encryptor.key,
|
|
70
|
+
encryptor.encrypt(hex_sid_size),
|
|
71
|
+
random_characters(padding_size),
|
|
72
|
+
encryptor.encrypt(sid)
|
|
73
|
+
].join
|
|
74
|
+
|
|
75
|
+
pad_to_min_token_size(token)
|
|
76
|
+
end
|
|
77
|
+
# rubocop:enable Metrics/MethodLength
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def hex_id_size(sid)
|
|
81
|
+
sid.size.to_s(16).rjust(2, '0')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def pad_to_min_token_size(token)
|
|
86
|
+
return token if token.size >= __min_token_size
|
|
87
|
+
|
|
88
|
+
padding = random_characters(__min_token_size)
|
|
89
|
+
(token + padding).slice(0, __min_token_size)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end # class << self
|
|
93
|
+
|
|
94
|
+
end # module LegacyEncoder
|
|
95
|
+
end # module encoder
|
|
96
|
+
end # module
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedToken
|
|
4
|
+
module Encoder
|
|
5
|
+
##
|
|
6
|
+
# EncodedToken::Encode::Utf8Decoder
|
|
7
|
+
#
|
|
8
|
+
# This module represents the decoder used by EncodedToken::Encryptor
|
|
9
|
+
# to decode UTF-8 tokens.
|
|
10
|
+
#
|
|
11
|
+
module Utf8Decoder
|
|
12
|
+
|
|
13
|
+
# ======================================================================
|
|
14
|
+
# = Macros
|
|
15
|
+
# ======================================================================
|
|
16
|
+
|
|
17
|
+
extend EncodedToken::Utils
|
|
18
|
+
extend EncodedToken::ErrorMessages
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ======================================================================
|
|
22
|
+
# = Singleton Methods
|
|
23
|
+
# ======================================================================
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Decodes the given UTF-8 token.
|
|
29
|
+
#
|
|
30
|
+
# @param [String] token
|
|
31
|
+
# the token to decode.
|
|
32
|
+
#
|
|
33
|
+
# @return [String, nil]
|
|
34
|
+
# the decoded value, or nil if unsuccessful.
|
|
35
|
+
#
|
|
36
|
+
# @raise [ArgumentError]
|
|
37
|
+
# if the token is invalid.
|
|
38
|
+
#
|
|
39
|
+
def decode(token)
|
|
40
|
+
assert_valid_token!(token)
|
|
41
|
+
parse_token(token)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ======================================================================
|
|
46
|
+
# = Private Singleton Methods
|
|
47
|
+
# ======================================================================
|
|
48
|
+
#
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def assert_valid_token!(token)
|
|
52
|
+
fail_with(:non_string_token) unless token.is_a?(String)
|
|
53
|
+
fail_with(:invalid_token) unless token[-1] == '_'
|
|
54
|
+
fail_with(:invalid_token_characters) unless valid_token_text?(token[0..-2])
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# rubocop:disable Metrics
|
|
58
|
+
#
|
|
59
|
+
def parse_token(original_token)
|
|
60
|
+
Encoder.decoding_ciphers.each do |cipher|
|
|
61
|
+
begin
|
|
62
|
+
token = original_token[0..-2]
|
|
63
|
+
key = token.slice!(0)
|
|
64
|
+
encryptor = EncodedToken::Encryptor.new(cipher, key) rescue next
|
|
65
|
+
|
|
66
|
+
hex_token = encryptor.decrypt(token)
|
|
67
|
+
padding_size = encryptor.padding
|
|
68
|
+
hex_token.slice!(0, padding_size)
|
|
69
|
+
|
|
70
|
+
data_size = hex_token.slice!(0, 3).to_i(16)
|
|
71
|
+
hex_data = hex_token.slice!(0, data_size)
|
|
72
|
+
crc_checksum = hex_token.slice!(0, 2).to_i(16)
|
|
73
|
+
|
|
74
|
+
next unless crc_checksum == __crc8_checksum(hex_data)
|
|
75
|
+
|
|
76
|
+
[hex_data].pack("H*").force_encoding("UTF-8")
|
|
77
|
+
rescue
|
|
78
|
+
next
|
|
79
|
+
end.tap { |result| return result if result }
|
|
80
|
+
end # each cipher
|
|
81
|
+
nil
|
|
82
|
+
end
|
|
83
|
+
# rubocop:enable Metrics
|
|
84
|
+
|
|
85
|
+
end # class << self
|
|
86
|
+
|
|
87
|
+
end # module Utf8Decoder
|
|
88
|
+
end # module encoder
|
|
89
|
+
end # module
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module EncodedToken
|
|
4
|
+
module Encoder
|
|
5
|
+
##
|
|
6
|
+
# EncodedToken::Encode::Utf8Encoder
|
|
7
|
+
#
|
|
8
|
+
# This module represents a encoder used by EncodedToken::Encryptor
|
|
9
|
+
# to encode UTF-8 tokens.
|
|
10
|
+
#
|
|
11
|
+
module Utf8Encoder
|
|
12
|
+
|
|
13
|
+
# ======================================================================
|
|
14
|
+
# = Mcros
|
|
15
|
+
# ======================================================================
|
|
16
|
+
|
|
17
|
+
extend EncodedToken::Utils
|
|
18
|
+
extend EncodedToken::ErrorMessages
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# ======================================================================
|
|
22
|
+
# = Singleton methods
|
|
23
|
+
# ======================================================================
|
|
24
|
+
|
|
25
|
+
class << self
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Encodes the given input into a UTF-8 token.
|
|
29
|
+
#
|
|
30
|
+
# @param [Integer, String, *.to_s] input
|
|
31
|
+
# the input to encode.
|
|
32
|
+
#
|
|
33
|
+
# @return [String]
|
|
34
|
+
# the encoded UTF-8 token.
|
|
35
|
+
#
|
|
36
|
+
# @raise [ArgumentError]
|
|
37
|
+
# if the input is invalid.
|
|
38
|
+
#
|
|
39
|
+
def encode(input)
|
|
40
|
+
assert_valid_input!(input)
|
|
41
|
+
generate_token(input)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# = Private Singleton Methods
|
|
46
|
+
# ======================================================================
|
|
47
|
+
#
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def assert_valid_input!(input)
|
|
51
|
+
utf8 = input.to_s.force_encoding("UTF-8")
|
|
52
|
+
fail if utf8.empty? || utf8.size > __max_input_size
|
|
53
|
+
|
|
54
|
+
true
|
|
55
|
+
rescue StandardError
|
|
56
|
+
fail_with(:invalid_utf8_input)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# rubocop:disable Metrics
|
|
60
|
+
#
|
|
61
|
+
def generate_token(input, user_key = nil)
|
|
62
|
+
encryptor = EncodedToken::Encryptor.new(Encoder.encoding_cipher, user_key)
|
|
63
|
+
padding_size = encryptor.padding
|
|
64
|
+
|
|
65
|
+
token = [
|
|
66
|
+
random_hex(padding_size),
|
|
67
|
+
hex_input_size(input),
|
|
68
|
+
hex_input(input),
|
|
69
|
+
crc_checksum(input),
|
|
70
|
+
random_hex(padding_size),
|
|
71
|
+
random_hex(rand(20).to_i)
|
|
72
|
+
].join
|
|
73
|
+
|
|
74
|
+
token = pad_to_min_token_size(token)
|
|
75
|
+
|
|
76
|
+
[encryptor.key, encryptor.encrypt(token), '_'].join
|
|
77
|
+
end
|
|
78
|
+
# rubocop:enable Metrics
|
|
79
|
+
|
|
80
|
+
def hex_input(input)
|
|
81
|
+
input.to_s.force_encoding("UTF-8").unpack1('H*')
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def hex_input_size(input)
|
|
85
|
+
hex_input(input).size.to_s(16).rjust(3, '0')
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def crc_checksum(input)
|
|
89
|
+
__crc8_checksum(hex_input(input)).to_s(16).rjust(2, '0')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def pad_to_min_token_size(token)
|
|
93
|
+
return token if token.size >= __min_token_size
|
|
94
|
+
|
|
95
|
+
padding = random_hex(__min_token_size)
|
|
96
|
+
(token + padding).slice(0, __min_token_size)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
end # class << self
|
|
100
|
+
|
|
101
|
+
end # class Utf8Encoder
|
|
102
|
+
end # module encoder
|
|
103
|
+
end # module
|
|
@@ -1,196 +1,183 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
|
|
3
|
+
module EncodedToken
|
|
4
|
+
##
|
|
5
|
+
# EncodedToken::Encoder
|
|
6
|
+
#
|
|
7
|
+
# This module handles the encoding and decoding of tokens.
|
|
8
|
+
#
|
|
9
9
|
module Encoder
|
|
10
10
|
|
|
11
|
-
#
|
|
11
|
+
# ======================================================================
|
|
12
|
+
# = Macros
|
|
12
13
|
# ======================================================================
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
# Generates a Secure Token from the given ID
|
|
16
|
-
#
|
|
17
|
-
# [args:]
|
|
18
|
-
# - *id* [Integer, String] - the ID or UUID to encode
|
|
19
|
-
# - eg. 12345, "12345", "468a5eeb-0cda-4c99-8dba-6a96c33003e0"
|
|
20
|
-
#
|
|
21
|
-
# [returns:]
|
|
22
|
-
# - a web-safe, variable length String of alphanumeric characters
|
|
23
|
-
#
|
|
24
|
-
# [on error:]
|
|
25
|
-
# - raises an exception based on the error
|
|
26
|
-
#
|
|
27
|
-
# *examples:*
|
|
28
|
-
#
|
|
29
|
-
# EncodedToken.encode!(12345)
|
|
30
|
-
# # => "KY3bnaRGmyy6yJS3imWr1dcWtzDYvZjpIAYyCUo5PEKPFvQgtTTed"
|
|
31
|
-
#
|
|
32
|
-
# EncodedToken.encode!("12345")
|
|
33
|
-
# # => "3gDwO7r4UJYeBYDBLU94MqjZQm0SToSE29ACDNcw0xf4QusZKxQHJ"
|
|
34
|
-
#
|
|
35
|
-
# EncodedToken.encode!("468a5eeb-0cda-4c99-8dba-6a96c33003e0")
|
|
36
|
-
# # => "pAi1SmpKgFAchh76EoLbYLeXVQmLwmMlH2v1zDVeufioKGr0709Qw"
|
|
37
|
-
#
|
|
38
|
-
# EncodedToken.encode!(:test)
|
|
39
|
-
# # => EncodedToken: :id must be an Integer, a String integer or a String UUID. (RuntimeError)
|
|
40
|
-
#
|
|
41
|
-
def encode!(id)
|
|
42
|
-
assert_valid_seed!
|
|
43
|
-
assert_valid_id!(id)
|
|
44
|
-
generate_token(id)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
##
|
|
50
|
-
# Generates a Secure Token from the given ID
|
|
51
|
-
#
|
|
52
|
-
# [args:]
|
|
53
|
-
# - *id* [Integer, String] - the ID or UUID to encode
|
|
54
|
-
# - eg. 12345, "12345", "468a5eeb-0cda-4c99-8dba-6a96c33003e0"
|
|
55
|
-
#
|
|
56
|
-
# [returns:]
|
|
57
|
-
# - a web-safe, variable length String of alphanumeric characters
|
|
58
|
-
#
|
|
59
|
-
# [on error:]
|
|
60
|
-
# - raises an ArgumentError
|
|
61
|
-
#
|
|
62
|
-
# *examples:*
|
|
63
|
-
#
|
|
64
|
-
# EncodedToken.encode(12345)
|
|
65
|
-
# # => "KY3bnaRGmyy6yJS3imWr1dcWtzDYvZjpIAYyCUo5PEKPFvQgtTTed"
|
|
66
|
-
#
|
|
67
|
-
# EncodedToken.encode("12345")
|
|
68
|
-
# # => "3gDwO7r4UJYeBYDBLU94MqjZQm0SToSE29ACDNcw0xf4QusZKxQHJ"
|
|
69
|
-
#
|
|
70
|
-
# EncodedToken.encode("468a5eeb-0cda-4c99-8dba-6a96c33003e0")
|
|
71
|
-
# # => "pAi1SmpKgFAchh76EoLbYLeXVQmLwmMlH2v1zDVeufioKGr0709Qw"
|
|
72
|
-
#
|
|
73
|
-
# EncodedToken.encode(:test)
|
|
74
|
-
# # => EncodedToken: :id must be an Integer, a String integer or a String UUID. (RuntimeError)
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
def encode(id)
|
|
78
|
-
encode!(id)
|
|
79
|
-
rescue ArgumentError
|
|
80
|
-
fail_with_invalid_id_argument
|
|
81
|
-
end
|
|
82
|
-
|
|
15
|
+
extend EncodedToken::ErrorMessages
|
|
83
16
|
|
|
84
17
|
|
|
85
18
|
# ======================================================================
|
|
86
|
-
#
|
|
19
|
+
# = Singleton Methods
|
|
87
20
|
# ======================================================================
|
|
88
|
-
#
|
|
89
|
-
private
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
# ensures the given ID is valid to encode
|
|
94
|
-
#
|
|
95
|
-
# id - an Integer, numerical String integer or UUID
|
|
96
|
-
# - max size of 255 characters
|
|
97
|
-
# - contain only hex charatacters + '-'
|
|
98
|
-
#
|
|
99
|
-
# returns - true if valid
|
|
100
|
-
#
|
|
101
|
-
# on error: - an ArgumentError is raised
|
|
102
|
-
#
|
|
103
|
-
def assert_valid_id!(id)
|
|
104
|
-
sid = id.to_s
|
|
105
|
-
|
|
106
|
-
fail if sid.size < 1
|
|
107
|
-
fail if sid.size > 255
|
|
108
|
-
fail unless valid_hex_text?(sid)
|
|
109
|
-
fail unless valid_integer?(id) || valid_uuid_format?(id)
|
|
110
|
-
|
|
111
|
-
return true
|
|
112
|
-
|
|
113
|
-
rescue
|
|
114
|
-
fail_with_invalid_id_argument
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
# generates the token
|
|
120
|
-
#
|
|
121
|
-
# id - Integer, String integer or String UUID
|
|
122
|
-
#
|
|
123
|
-
# returns - an alphanumeric String token
|
|
124
|
-
#
|
|
125
|
-
# Note - token comprises [key, id_size, left_padding, enc_id, right_padding]
|
|
126
|
-
#
|
|
127
|
-
def generate_token(id)
|
|
128
|
-
# stringify the id
|
|
129
|
-
sid = id.to_s
|
|
130
|
-
|
|
131
|
-
# select a random cipher key
|
|
132
|
-
token = key = __keylist.sample
|
|
133
|
-
|
|
134
|
-
# encrypt the id size
|
|
135
|
-
token += encrypt_size(sid, key)
|
|
136
|
-
|
|
137
|
-
# generate the left padding
|
|
138
|
-
token += random_characters(__ciphers[key][:padding])
|
|
139
|
-
|
|
140
|
-
# encrypt the id
|
|
141
|
-
token += encrypt(sid, key)
|
|
142
21
|
|
|
143
|
-
|
|
144
|
-
count = (__target_size - token.size).clamp(0, __target_size)
|
|
145
|
-
token += random_characters(count)
|
|
22
|
+
class << self
|
|
146
23
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
#
|
|
157
|
-
# note - we convert to hex to allow for strings up to 255 chars
|
|
158
|
-
#
|
|
159
|
-
def encrypt_size(id, key)
|
|
160
|
-
hex_size = id.size.to_s(16).rjust(2, '0')
|
|
24
|
+
##
|
|
25
|
+
# Checks if the ciphers have been built.
|
|
26
|
+
#
|
|
27
|
+
# @return [Boolean]
|
|
28
|
+
# true if the encoding cipher is set, false otherwise.
|
|
29
|
+
#
|
|
30
|
+
def ciphers_built?
|
|
31
|
+
!!encoding_cipher
|
|
32
|
+
end
|
|
161
33
|
|
|
162
|
-
|
|
163
|
-
|
|
34
|
+
##
|
|
35
|
+
# Returns a duplicate of the current encoding cipher.
|
|
36
|
+
#
|
|
37
|
+
# @return [Cipher]
|
|
38
|
+
# the encoding cipher instance.
|
|
39
|
+
#
|
|
40
|
+
def encoding_cipher
|
|
41
|
+
@encoding_cipher.dup
|
|
42
|
+
end
|
|
164
43
|
|
|
44
|
+
##
|
|
45
|
+
# Returns a list of duplicates of the current decoding ciphers.
|
|
46
|
+
#
|
|
47
|
+
# @return [Array<Cipher>]
|
|
48
|
+
# an array of decoding cipher instances.
|
|
49
|
+
#
|
|
50
|
+
def decoding_ciphers
|
|
51
|
+
@decoding_ciphers ||= []
|
|
52
|
+
@decoding_ciphers.map{ |cipher| cipher.dup }
|
|
53
|
+
end
|
|
165
54
|
|
|
55
|
+
##
|
|
56
|
+
# Returns the minimum token size from configuration.
|
|
57
|
+
#
|
|
58
|
+
# @return [Integer]
|
|
59
|
+
# the minimum size for an encoded token.
|
|
60
|
+
#
|
|
61
|
+
def min_token_size
|
|
62
|
+
EncodedToken.configuration.min_token_size
|
|
63
|
+
end
|
|
166
64
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
65
|
+
##
|
|
66
|
+
# Returns the maximum input size from configuration.
|
|
67
|
+
#
|
|
68
|
+
# @return [Integer]
|
|
69
|
+
# the maximum size allowed for input.
|
|
70
|
+
#
|
|
71
|
+
def max_input_size
|
|
72
|
+
EncodedToken.configuration.max_input_size
|
|
73
|
+
end
|
|
173
74
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
75
|
+
##
|
|
76
|
+
# Encodes a given input to an encoded token.
|
|
77
|
+
#
|
|
78
|
+
# @param [Integer, String, *.to_s] input
|
|
79
|
+
# any object that responds to '.to_s'
|
|
80
|
+
# @param [:utf8, :legacy] encoder
|
|
81
|
+
# the encoder to use.
|
|
82
|
+
#
|
|
83
|
+
# @return [String] if successful
|
|
84
|
+
# @return [nil] if unsuccessful
|
|
85
|
+
#
|
|
86
|
+
def encode(input, encoder = :utf8)
|
|
87
|
+
encode!(input, encoder)
|
|
88
|
+
rescue StandardError
|
|
89
|
+
nil
|
|
90
|
+
end
|
|
177
91
|
|
|
178
|
-
|
|
92
|
+
##
|
|
93
|
+
# Encodes a given input to an encoded token, raising an exception
|
|
94
|
+
# if encountered.
|
|
95
|
+
#
|
|
96
|
+
# @param [Integer, String, *.to_s] input
|
|
97
|
+
# any object that responds to '.to_s'
|
|
98
|
+
# @param [:utf8, :legacy] encoder
|
|
99
|
+
# the encoder to use.
|
|
100
|
+
#
|
|
101
|
+
# @return [String] the encoded token
|
|
102
|
+
#
|
|
103
|
+
# @raise [ArgumentError, RuntimeError]
|
|
104
|
+
# if the encoding fails or if the seed is not set.
|
|
105
|
+
#
|
|
106
|
+
def encode!(input, encoder = :utf8)
|
|
107
|
+
fail_with(:seed_not_set) unless encoding_cipher
|
|
108
|
+
|
|
109
|
+
case encoder
|
|
110
|
+
when :utf8
|
|
111
|
+
Encoder::Utf8Encoder.encode(input)
|
|
112
|
+
when :legacy
|
|
113
|
+
Encoder::LegacyEncoder.encode(input)
|
|
114
|
+
else
|
|
115
|
+
fail_with(:invalid_encoder)
|
|
116
|
+
end
|
|
179
117
|
end
|
|
180
118
|
|
|
181
|
-
|
|
182
|
-
|
|
119
|
+
##
|
|
120
|
+
# Decodes a given token to the original string.
|
|
121
|
+
#
|
|
122
|
+
# @param [String] token
|
|
123
|
+
# an encoded-token string
|
|
124
|
+
#
|
|
125
|
+
# @return [String]
|
|
126
|
+
# the decoded value if successful
|
|
127
|
+
# @return [nil]
|
|
128
|
+
# if unsuccessful
|
|
129
|
+
#
|
|
130
|
+
def decode(token)
|
|
131
|
+
decode!(token)
|
|
132
|
+
rescue
|
|
133
|
+
nil
|
|
134
|
+
end
|
|
183
135
|
|
|
136
|
+
##
|
|
137
|
+
# Decodes a given token to the original string, raising an exception
|
|
138
|
+
# if encountered.
|
|
139
|
+
#
|
|
140
|
+
# @param [String] token
|
|
141
|
+
# an encoded-token string
|
|
142
|
+
#
|
|
143
|
+
# @return [String]
|
|
144
|
+
# the decoded value
|
|
145
|
+
#
|
|
146
|
+
# @raise [ArgumentError, RuntimeError]
|
|
147
|
+
# if the decoding fails, if the token is not a string, or if the seed is not set.
|
|
148
|
+
#
|
|
149
|
+
def decode!(token)
|
|
150
|
+
fail_with(:seed_not_set) unless encoding_cipher
|
|
151
|
+
fail_with(:non_string_token) unless token.is_a?(String)
|
|
152
|
+
|
|
153
|
+
if token[-1] == '_'
|
|
154
|
+
Utf8Decoder.decode(token)
|
|
155
|
+
else
|
|
156
|
+
LegacyDecoder.decode(token)
|
|
157
|
+
end
|
|
158
|
+
end
|
|
184
159
|
|
|
160
|
+
##
|
|
161
|
+
# Builds the encoding and decoding ciphers based on the current configuration.
|
|
162
|
+
#
|
|
163
|
+
# @return [void]
|
|
164
|
+
#
|
|
165
|
+
# @raise [RuntimeError]
|
|
166
|
+
# if the seed is not set in the configuration.
|
|
167
|
+
#
|
|
168
|
+
def build_ciphers!
|
|
169
|
+
default_seed = EncodedToken.configuration.seed || fail_with(:seed_not_set)
|
|
170
|
+
|
|
171
|
+
@encoding_cipher = Cipher.new(default_seed)
|
|
172
|
+
@decoding_ciphers = [@encoding_cipher]
|
|
173
|
+
|
|
174
|
+
EncodedToken.configuration.expiring_seeds.each do |exp_seed, details|
|
|
175
|
+
cipher = Cipher.new(exp_seed, details[:expires_on], details[:cipher_count])
|
|
176
|
+
@decoding_ciphers << cipher unless cipher.expired?
|
|
177
|
+
end
|
|
178
|
+
end
|
|
185
179
|
|
|
186
|
-
#
|
|
187
|
-
#
|
|
188
|
-
def random_characters(size)
|
|
189
|
-
SecureRandom.alphanumeric(size)
|
|
190
|
-
end
|
|
180
|
+
end # class << self
|
|
191
181
|
|
|
192
182
|
end #module
|
|
193
|
-
end #
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
183
|
+
end #EncodedToken
|