virgil-jwt 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,125 @@
1
+ # Copyright (C) 2015-2019 Virgil Security Inc.
2
+ #
3
+ # Lead Maintainer: Virgil Security Inc. <support@virgilsecurity.com>
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are
9
+ # met:
10
+ #
11
+ # (1) Redistributions of source code must retain the above copyright
12
+ # notice, this list of conditions and the following disclaimer.
13
+ #
14
+ # (2) Redistributions in binary form must reproduce the above copyright
15
+ # notice, this list of conditions and the following disclaimer in
16
+ # the documentation and/or other materials provided with the
17
+ # distribution.
18
+ #
19
+ # (3) Neither the name of the copyright holder nor the names of its
20
+ # contributors may be used to endorse or promote products derived from
21
+ # this software without specific prior written permission.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR
24
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
27
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+ # SERVICES; LOSS OF USE, bytes, OR PROFITS; OR BUSINESS INTERRUPTION)
30
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
32
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
+ # POSSIBILITY OF SUCH DAMAGE.
34
+
35
+ require 'base64'
36
+ require 'json'
37
+
38
+ module Virgil
39
+ module Jwt
40
+ class Bytes < Array
41
+ # Initializes a new array of bytes from specified string, which encodes binary data.
42
+ # @param str [String] String to decode.
43
+ # @param encoding [VirgilStringEncoding] The character encoding of string.
44
+ # @raise [ArgumentError] if encoding is undefined
45
+ def self.from_string(str, encoding = VirgilStringEncoding::UTF8)
46
+ case encoding
47
+ when VirgilStringEncoding::BASE64
48
+ from_base64(str)
49
+ when VirgilStringEncoding::HEX
50
+ from_hex(str)
51
+ when VirgilStringEncoding::UTF8
52
+ from_utf8(str)
53
+ else
54
+ raise ArgumentError, 'Encoding is undefined'
55
+ end
56
+ end
57
+
58
+ # Decodes the current bytes to a string according to the specified
59
+ # character encoding.
60
+ # @param encoding [VirgilStringEncoding] The character encoding to encode to.
61
+ # equivalent string representation if raw bytes in selected encoding.
62
+ # @return [String]
63
+ # @raise [ArgumentError] if encoding is undefined
64
+ def to_string(encoding = VirgilStringEncoding::UTF8)
65
+ case encoding
66
+ when VirgilStringEncoding::BASE64
67
+ to_base64
68
+ when VirgilStringEncoding::HEX
69
+ to_hex
70
+ when VirgilStringEncoding::UTF8
71
+ to_s
72
+ else
73
+ raise ArgumentError, 'Encoding is undefined'
74
+ end
75
+ end
76
+
77
+ # Converts all the bytes to its equivalent string representation in utf8.
78
+ def to_s
79
+ pack('c*')
80
+ end
81
+
82
+ # Initializes a new array of bytes from specified string,
83
+ # which encodes binary data as base-64 digits.
84
+ def self.from_base64(str)
85
+ new(Base64.decode64(str).bytes)
86
+ end
87
+
88
+ # Initializes a new array of bytes from specified string,
89
+ # which encodes binary data as utf8.
90
+ def self.from_utf8(str)
91
+ new(str.bytes)
92
+ end
93
+
94
+ # Initializes a new array of bytes from specified string,
95
+ # which encodes binary data as hexadecimal digits.
96
+ def self.from_hex(str)
97
+ new(str.scan(/../).map { |x| x.hex })
98
+ end
99
+
100
+ # Converts all the bytes to its equivalent string representation that
101
+ # is encoded with base-64 digits.
102
+ def to_base64
103
+ Base64.strict_encode64(to_s)
104
+ end
105
+
106
+ # Encodes all the bytes into a utf8 string.
107
+ def to_utf8
108
+ to_s
109
+ end
110
+
111
+ # Converts the numeric value of each element of a current array of bytes to its
112
+ # equivalent hexadecimal string representation.
113
+ def to_hex
114
+ to_s.each_byte.map { |b| b.to_s(16) }.join
115
+ end
116
+
117
+ end
118
+
119
+ module VirgilStringEncoding
120
+ BASE64 = 1
121
+ HEX = 2
122
+ UTF8 = 3
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,74 @@
1
+ # Copyright (C) 2015-2019 Virgil Security Inc.
2
+ #
3
+ # Lead Maintainer: Virgil Security Inc. <support@virgilsecurity.com>
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are
9
+ # met:
10
+ #
11
+ # (1) Redistributions of source code must retain the above copyright
12
+ # notice, this list of conditions and the following disclaimer.
13
+ #
14
+ # (2) Redistributions in binary form must reproduce the above copyright
15
+ # notice, this list of conditions and the following disclaimer in
16
+ # the documentation and/or other materials provided with the
17
+ # distribution.
18
+ #
19
+ # (3) Neither the name of the copyright holder nor the names of its
20
+ # contributors may be used to endorse or promote products derived from
21
+ # this software without specific prior written permission.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR
24
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
27
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+ # SERVICES; LOSS OF USE, bytes, OR PROFITS; OR BUSINESS INTERRUPTION)
30
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
32
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
+ # POSSIBILITY OF SUCH DAMAGE.
34
+ require 'thread'
35
+
36
+ module Virgil
37
+ module Jwt
38
+
39
+ # Provides an opportunity to
40
+ # get cached access token or renew it using callback mechanism.
41
+ class CachingJwtProvider < AccessTokenProvider
42
+
43
+ # Callback, that takes an instance of [TokenContext]
44
+ # and returns string representation of generated instance of [AccessToken]
45
+ # @return [Proc]
46
+ attr_reader :renew_access_token_proc
47
+
48
+ @@mutex = Mutex.new
49
+ def initialize(obtain_token_proc)
50
+ Validation.check_type_argument!(Proc, obtain_token_proc)
51
+ @renew_access_token_proc = obtain_token_proc
52
+ end
53
+
54
+ # Gets cached or renewed access token.
55
+ # @param token_context an instance of [TokenContext]
56
+ # @return The instance of [AccessToken]
57
+ def get_token(token_context)
58
+ Validation.check_type_argument!(TokenContext, token_context)
59
+
60
+ if !@jwt || (@jwt.body_content.expires_at <= Time.at(Time.now.utc.to_i + 5).utc)
61
+ @@mutex.synchronize {
62
+ jwt_str = @renew_access_token_proc.call(token_context)
63
+ @jwt = Jwt.from(jwt_str)
64
+ }
65
+ end
66
+ @jwt
67
+ end
68
+
69
+ private
70
+
71
+ attr_reader :jwt
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,60 @@
1
+ # Copyright (C) 2015-2019 Virgil Security Inc.
2
+ #
3
+ # Lead Maintainer: Virgil Security Inc. <support@virgilsecurity.com>
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are
9
+ # met:
10
+ #
11
+ # (1) Redistributions of source code must retain the above copyright
12
+ # notice, this list of conditions and the following disclaimer.
13
+ #
14
+ # (2) Redistributions in binary form must reproduce the above copyright
15
+ # notice, this list of conditions and the following disclaimer in
16
+ # the documentation and/or other materials provided with the
17
+ # distribution.
18
+ #
19
+ # (3) Neither the name of the copyright holder nor the names of its
20
+ # contributors may be used to endorse or promote products derived from
21
+ # this software without specific prior written permission.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR
24
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
27
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+ # SERVICES; LOSS OF USE, bytes, OR PROFITS; OR BUSINESS INTERRUPTION)
30
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
32
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
+ # POSSIBILITY OF SUCH DAMAGE.
34
+
35
+ module Virgil
36
+ module Jwt
37
+ # provides an opportunity to
38
+ # get access token using callback mechanism.
39
+ class CallbackJwtProvider < AccessTokenProvider
40
+
41
+ # Callback, that takes an instance of [TokenContext]
42
+ # and returns string representation of generated instance of [AccessToken]
43
+ attr_reader :obtain_access_token_proc
44
+
45
+ def initialize(obtain_token_proc)
46
+ Validation.check_type_argument!(Proc, obtain_token_proc)
47
+ @obtain_access_token_proc = obtain_token_proc
48
+ end
49
+
50
+ # Gets access token.
51
+ # @param token_context [TokenContext]
52
+ # @return [AccessToken] Access token
53
+ def get_token(token_context)
54
+ Validation.check_type_argument!(TokenContext, token_context)
55
+ jwt_str = @obtain_access_token_proc.call(token_context)
56
+ Jwt.from(jwt_str)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,56 @@
1
+ # Copyright (C) 2015-2019 Virgil Security Inc.
2
+ #
3
+ # Lead Maintainer: Virgil Security Inc. <support@virgilsecurity.com>
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are
9
+ # met:
10
+ #
11
+ # (1) Redistributions of source code must retain the above copyright
12
+ # notice, this list of conditions and the following disclaimer.
13
+ #
14
+ # (2) Redistributions in binary form must reproduce the above copyright
15
+ # notice, this list of conditions and the following disclaimer in
16
+ # the documentation and/or other materials provided with the
17
+ # distribution.
18
+ #
19
+ # (3) Neither the name of the copyright holder nor the names of its
20
+ # contributors may be used to endorse or promote products derived from
21
+ # this software without specific prior written permission.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR
24
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
27
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+ # SERVICES; LOSS OF USE, bytes, OR PROFITS; OR BUSINESS INTERRUPTION)
30
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
32
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
+ # POSSIBILITY OF SUCH DAMAGE.
34
+
35
+ module Virgil
36
+ module Jwt
37
+ # Provides an opportunity to use constant access token.
38
+ class ConstAccessTokenProvider < AccessTokenProvider
39
+ def initialize(token)
40
+ @access_token = token
41
+ end
42
+
43
+ # Gets access token.
44
+ # @param token_context [TokenContext] can be null as
45
+ # it does not affect the result.
46
+ # @return [AccessToken]
47
+ def get_token(token_context)
48
+ access_token
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :access_token
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,132 @@
1
+ # Copyright (C) 2015-2019 Virgil Security Inc.
2
+ #
3
+ # Lead Maintainer: Virgil Security Inc. <support@virgilsecurity.com>
4
+ #
5
+ # All rights reserved.
6
+ #
7
+ # Redistribution and use in source and binary forms, with or without
8
+ # modification, are permitted provided that the following conditions are
9
+ # met:
10
+ #
11
+ # (1) Redistributions of source code must retain the above copyright
12
+ # notice, this list of conditions and the following disclaimer.
13
+ #
14
+ # (2) Redistributions in binary form must reproduce the above copyright
15
+ # notice, this list of conditions and the following disclaimer in
16
+ # the documentation and/or other materials provided with the
17
+ # distribution.
18
+ #
19
+ # (3) Neither the name of the copyright holder nor the names of its
20
+ # contributors may be used to endorse or promote products derived from
21
+ # this software without specific prior written permission.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR
24
+ # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25
+ # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ # DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
27
+ # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28
+ # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29
+ # SERVICES; LOSS OF USE, bytes, OR PROFITS; OR BUSINESS INTERRUPTION)
30
+ # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31
+ # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
32
+ # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33
+ # POSSIBILITY OF SUCH DAMAGE.
34
+
35
+ module Virgil
36
+ module Jwt
37
+ # Implements [AccessToken] in terms of Virgil JWT.
38
+ class Jwt < AccessToken
39
+ # Gets a jwt body
40
+ # @return [JwtBodyContent]
41
+ attr_reader :body_content
42
+
43
+ # Gets a jwt header
44
+ # @return [JwtHeaderContent]
45
+ attr_reader :header_content
46
+
47
+ # Gets a digital signature of jwt
48
+ # @return [Bytes]
49
+ attr_reader :signature_data
50
+
51
+ # String representation of jwt without signature.
52
+ # It equals to:
53
+ # Base64.urlsafe_encode64(JWT Header) + "." + Base64.urlsafe_encode64(JWT Body)
54
+ # @return [String]
55
+ attr_reader :unsigned_data
56
+
57
+ # Initializes a new instance of the [Jwt] class using specified header,
58
+ # body and signature.
59
+ # @param header_content [JwtHeaderContext] jwt header
60
+ # @param body_content [JwtBodyContent] jwt body
61
+ # @param signature_data [Bytes] jwt signature data
62
+ def initialize(header_content:, body_content:, signature_data:)
63
+ @header_content = header_content
64
+ @body_content = body_content
65
+ @signature_data = signature_data
66
+ @string_representation = "#{header_base64}.#{body_base64}"
67
+ @unsigned_data = Bytes.from_string(@string_representation)
68
+ @string_representation += ".#{signature_base64}" unless @signature_data.nil?
69
+ end
70
+
71
+ # Initializes a new instance of the [Jwt] class using
72
+ # its string representation
73
+ # @param jwt_str [String] string representation of signed jwt.
74
+ # It must be equal to:
75
+ # Base64.urlsafe_encode64(jwt_header.to_base64) + "."
76
+ # + Base64.urlsafe_encode64(JWT Body) "."
77
+ # + Base64.urlsafe_encode64(Jwt Signature).
78
+ # @return [Jwt]
79
+ def self.from(jwt_str)
80
+ begin
81
+ parts = jwt_str.split('.')
82
+ raise ArgumentError unless parts.size == 3
83
+ signature_data = Bytes.new(Base64.urlsafe_decode64(parts[2]).bytes)
84
+ new(header_content: parse_header_content(parts[0]),
85
+ body_content: parse_body_content(parts[1]),
86
+ signature_data: signature_data)
87
+ rescue StandardError
88
+ raise ArgumentError, 'Wrong JWT format.'
89
+ end
90
+
91
+ end
92
+
93
+ # String representation of jwt.
94
+ # @return [String]
95
+ def to_s
96
+ @string_representation
97
+ end
98
+
99
+ # Whether or not token is expired.
100
+ # @return [TrueClass]
101
+ def expired?
102
+ Time.now.utc >= @body_content.expires_at
103
+ end
104
+
105
+ private
106
+
107
+ attr_reader :string_representation
108
+
109
+ def self.parse_body_content(str)
110
+ body_json = Base64.urlsafe_decode64(str)
111
+ JwtBodyContent.restore_from_json(body_json)
112
+ end
113
+
114
+ def self.parse_header_content(str)
115
+ header_json = Base64.urlsafe_decode64(str)
116
+ JwtHeaderContent.restore_from_json(header_json)
117
+ end
118
+
119
+ def header_base64
120
+ Base64.urlsafe_encode64(@header_content.to_json, padding: false)
121
+ end
122
+
123
+ def body_base64
124
+ Base64.urlsafe_encode64(@body_content.to_json, padding: false)
125
+ end
126
+
127
+ def signature_base64
128
+ Base64.urlsafe_encode64(@signature_data.to_s, padding: false)
129
+ end
130
+ end
131
+ end
132
+ end