masks 0.10.0 → 0.12.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/CHANGELOG.md +63 -0
- data/README.md +28 -341
- data/app/controllers/masks/rails/handshakes_controller.rb +2 -2
- data/app/controllers/masks/rails/logouts_controller.rb +11 -8
- data/app/controllers/masks/rails/sessions_controller.rb +48 -6
- data/config/routes.rb +1 -0
- data/lib/masks/client/device.rb +47 -0
- data/lib/masks/client/embedded.rb +125 -0
- data/lib/masks/client/enrollment.rb +33 -0
- data/lib/masks/client/errors.rb +6 -0
- data/lib/masks/client/handshake.rb +12 -4
- data/lib/masks/client/http.rb +87 -24
- data/lib/masks/client/issuer.rb +2 -1
- data/lib/masks/client/key.rb +100 -0
- data/lib/masks/client/logout.rb +5 -1
- data/lib/masks/client/nonces.rb +31 -0
- data/lib/masks/client/proof.rb +7 -1
- data/lib/masks/client/rack.rb +8 -0
- data/lib/masks/client/registration.rb +32 -15
- data/lib/masks/client/resource.rb +1 -1
- data/lib/masks/client/session.rb +120 -11
- data/lib/masks/client/tokens.rb +4 -0
- data/lib/masks/client/transport.rb +114 -0
- data/lib/masks/client/verifier.rb +10 -2
- data/lib/masks/client.rb +21 -4
- data/lib/masks/rails/authentication.rb +115 -35
- data/lib/masks/rails/cache.rb +173 -0
- data/lib/masks/rails/configuration.rb +27 -12
- data/lib/masks/rails/protected_resource.rb +13 -0
- data/lib/masks/rails/renewal.rb +54 -0
- data/lib/masks/rails/session_cookies.rb +145 -0
- data/lib/masks/rails.rb +5 -4
- data/lib/masks/version.rb +1 -1
- data/lib/masks.rb +0 -1
- metadata +10 -1
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
require "zlib"
|
|
2
|
+
|
|
3
|
+
module Masks
|
|
4
|
+
module Rails
|
|
5
|
+
class SessionCookies
|
|
6
|
+
class Overflow < Masks::Client::Error; end
|
|
7
|
+
|
|
8
|
+
VERSION = "v1".freeze
|
|
9
|
+
PIECE = 3800
|
|
10
|
+
PIECES = 8
|
|
11
|
+
CIPHER = "aes-256-gcm".freeze
|
|
12
|
+
NONCE = 12
|
|
13
|
+
TAG = 16
|
|
14
|
+
PURPOSE = "masks session cookie".freeze
|
|
15
|
+
UNPACKED = PIECE * PIECES * 8
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def seal(held, bound)
|
|
19
|
+
sealing = OpenSSL::Cipher.new(CIPHER).encrypt
|
|
20
|
+
sealing.key = key
|
|
21
|
+
nonce = SecureRandom.random_bytes(NONCE)
|
|
22
|
+
sealing.iv = nonce
|
|
23
|
+
sealing.auth_data = bound.to_s
|
|
24
|
+
body = sealing.update(Zlib::Deflate.deflate(JSON.generate(held))) + sealing.final
|
|
25
|
+
|
|
26
|
+
"#{VERSION}.#{Base64.urlsafe_encode64(nonce + sealing.auth_tag(TAG) + body, padding: false)}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def open(value, bound)
|
|
30
|
+
version, sealed = value.to_s.split(".", 2)
|
|
31
|
+
|
|
32
|
+
return nil unless version == VERSION && sealed.present?
|
|
33
|
+
|
|
34
|
+
raw = Base64.urlsafe_decode64(sealed)
|
|
35
|
+
|
|
36
|
+
return nil if raw.bytesize < NONCE + TAG
|
|
37
|
+
|
|
38
|
+
opening = OpenSSL::Cipher.new(CIPHER).decrypt
|
|
39
|
+
opening.key = key
|
|
40
|
+
opening.iv = raw.byteslice(0, NONCE)
|
|
41
|
+
opening.auth_tag = raw.byteslice(NONCE, TAG)
|
|
42
|
+
opening.auth_data = bound.to_s
|
|
43
|
+
packed = opening.update(raw.byteslice((NONCE + TAG)..)) + opening.final
|
|
44
|
+
held = JSON.parse(inflated(packed))
|
|
45
|
+
|
|
46
|
+
held.is_a?(Hash) ? held : nil
|
|
47
|
+
rescue ArgumentError, OpenSSL::Cipher::CipherError, Zlib::Error, JSON::ParserError
|
|
48
|
+
nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def key
|
|
52
|
+
::Rails.application.key_generator.generate_key(PURPOSE, 32)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def inflated(packed)
|
|
58
|
+
inflater = Zlib::Inflate.new
|
|
59
|
+
held = +""
|
|
60
|
+
|
|
61
|
+
inflater.inflate(packed) do |chunk|
|
|
62
|
+
held << chunk
|
|
63
|
+
raise Zlib::DataError, "the session cookie unpacks to more than #{UNPACKED} bytes" if held.bytesize > UNPACKED
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
inflater.finish
|
|
67
|
+
held
|
|
68
|
+
ensure
|
|
69
|
+
inflater&.close
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def initialize(request, key)
|
|
74
|
+
@request = request
|
|
75
|
+
@key = key
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def read
|
|
79
|
+
bound = session[@key]
|
|
80
|
+
value = pieces.join
|
|
81
|
+
|
|
82
|
+
return nil if value.empty?
|
|
83
|
+
|
|
84
|
+
held = self.class.open(value, bound) if bound.is_a?(String)
|
|
85
|
+
|
|
86
|
+
clear_pieces if held.nil?
|
|
87
|
+
held
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def write(held)
|
|
91
|
+
bound = session[@key]
|
|
92
|
+
bound = session[@key] = SecureRandom.hex(32) unless bound.is_a?(String)
|
|
93
|
+
value = self.class.seal(held, bound)
|
|
94
|
+
written = value.scan(/.{1,#{PIECE}}/o)
|
|
95
|
+
|
|
96
|
+
raise Overflow, "this session is #{value.bytesize} bytes, more than #{PIECES} cookies can hold" if written.size > PIECES
|
|
97
|
+
|
|
98
|
+
written.each_with_index { |piece, index| jar[name(index)] = options.merge(value: piece) }
|
|
99
|
+
(written.size...PIECES).each { |index| drop(name(index)) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def clear
|
|
103
|
+
session.delete(@key)
|
|
104
|
+
clear_pieces
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def name(index)
|
|
108
|
+
base = @request.ssl? ? "__Host-#{@key}" : @key.to_s
|
|
109
|
+
|
|
110
|
+
index.zero? ? base : "#{base}-#{index}"
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
private
|
|
114
|
+
|
|
115
|
+
def session
|
|
116
|
+
@request.session
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def jar
|
|
120
|
+
@request.cookie_jar
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def pieces
|
|
124
|
+
(0...PIECES).map { |index| jar[name(index)] }.take_while(&:present?)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def clear_pieces
|
|
128
|
+
(0...PIECES).each { |index| drop(name(index)) }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def drop(named)
|
|
132
|
+
jar.delete(named, path: "/", secure: @request.ssl?, same_site: :lax) if jar.key?(named)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def options
|
|
136
|
+
lifetime = @request.session_options[:expire_after] if @request.respond_to?(:session_options)
|
|
137
|
+
|
|
138
|
+
{
|
|
139
|
+
path: "/", httponly: true, secure: @request.ssl?, same_site: :lax,
|
|
140
|
+
expires: lifetime ? Time.now + lifetime : nil
|
|
141
|
+
}.compact
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
data/lib/masks/rails.rb
CHANGED
|
@@ -13,10 +13,8 @@ module Masks
|
|
|
13
13
|
# Three pieces do the work:
|
|
14
14
|
#
|
|
15
15
|
# [Configuration] the issuer, credentials and routes, set once in an
|
|
16
|
-
# initializer
|
|
17
|
-
#
|
|
18
|
-
# [Authentication] +masks_login_url+, the callback, and the session the
|
|
19
|
-
# app reads +current_actor+ from
|
|
16
|
+
# initializer
|
|
17
|
+
# [Authentication] +masks_login_url+, the callback, and the session
|
|
20
18
|
# [ProtectedResource] the other direction — checking a bearer this app was
|
|
21
19
|
# handed, for an API rather than a browser
|
|
22
20
|
#
|
|
@@ -29,6 +27,9 @@ end
|
|
|
29
27
|
require_relative "version"
|
|
30
28
|
require_relative "client"
|
|
31
29
|
require_relative "rails/credentials"
|
|
30
|
+
require_relative "rails/renewal"
|
|
31
|
+
require_relative "rails/cache"
|
|
32
|
+
require_relative "rails/session_cookies"
|
|
32
33
|
require_relative "rails/configuration"
|
|
33
34
|
require_relative "rails/configurable"
|
|
34
35
|
require_relative "rails/masked"
|
data/lib/masks/version.rb
CHANGED
data/lib/masks.rb
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
# defined, so requiring this gem outside \Rails costs nothing.
|
|
9
9
|
#
|
|
10
10
|
# issuer = Masks::Client.issuer("https://auth.example")
|
|
11
|
-
# issuer.authorization_url(client_id: id, redirect_uri: uri)
|
|
12
11
|
#
|
|
13
12
|
# The provider itself is not in here. It is the masks-server gem, which runs
|
|
14
13
|
# as an app of its own or mounted inside another Rails app.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: masks
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- the masks authors
|
|
@@ -50,12 +50,17 @@ files:
|
|
|
50
50
|
- lib/masks/client/claims.rb
|
|
51
51
|
- lib/masks/client/delegations.rb
|
|
52
52
|
- lib/masks/client/delegations/fake.rb
|
|
53
|
+
- lib/masks/client/device.rb
|
|
54
|
+
- lib/masks/client/embedded.rb
|
|
55
|
+
- lib/masks/client/enrollment.rb
|
|
53
56
|
- lib/masks/client/errors.rb
|
|
54
57
|
- lib/masks/client/handshake.rb
|
|
55
58
|
- lib/masks/client/http.rb
|
|
56
59
|
- lib/masks/client/introspection.rb
|
|
57
60
|
- lib/masks/client/issuer.rb
|
|
61
|
+
- lib/masks/client/key.rb
|
|
58
62
|
- lib/masks/client/logout.rb
|
|
63
|
+
- lib/masks/client/nonces.rb
|
|
59
64
|
- lib/masks/client/pkce.rb
|
|
60
65
|
- lib/masks/client/proof.rb
|
|
61
66
|
- lib/masks/client/rack.rb
|
|
@@ -66,9 +71,11 @@ files:
|
|
|
66
71
|
- lib/masks/client/stores.rb
|
|
67
72
|
- lib/masks/client/tokens.rb
|
|
68
73
|
- lib/masks/client/tracker.rb
|
|
74
|
+
- lib/masks/client/transport.rb
|
|
69
75
|
- lib/masks/client/verifier.rb
|
|
70
76
|
- lib/masks/rails.rb
|
|
71
77
|
- lib/masks/rails/authentication.rb
|
|
78
|
+
- lib/masks/rails/cache.rb
|
|
72
79
|
- lib/masks/rails/configurable.rb
|
|
73
80
|
- lib/masks/rails/configuration.rb
|
|
74
81
|
- lib/masks/rails/credentials.rb
|
|
@@ -76,6 +83,8 @@ files:
|
|
|
76
83
|
- lib/masks/rails/masked.rb
|
|
77
84
|
- lib/masks/rails/privileged.rb
|
|
78
85
|
- lib/masks/rails/protected_resource.rb
|
|
86
|
+
- lib/masks/rails/renewal.rb
|
|
87
|
+
- lib/masks/rails/session_cookies.rb
|
|
79
88
|
- lib/masks/version.rb
|
|
80
89
|
homepage: https://github.com/masksrb/masks
|
|
81
90
|
licenses:
|