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.
@@ -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 and validated on boot rather than on the
17
- # first request that needs them
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
@@ -1,3 +1,3 @@
1
1
  module Masks
2
- VERSION = "0.10.0".freeze
2
+ VERSION = "0.12.0".freeze
3
3
  end
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.10.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: