sinatra-portier 1.4.0 → 2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: fbc4d5679b36bfe8c83d6657e0835d7b4747f76b
4
- data.tar.gz: bbe2ce4265fc758c9206f4e7986876e2477f72a5
2
+ SHA256:
3
+ metadata.gz: 7cd3476f061fb1de32d057c4f8a6161fb62e0321f47f0c6d264a31d3c16755f7
4
+ data.tar.gz: 1773f716e2a04c6e2b6de9c3fe221b4352004a3b1de09afb763846ff4ea34d17
5
5
  SHA512:
6
- metadata.gz: cdc7a80021f5577be656d395f0a809e4f9e3e7b937545e295ec4e5cf98a881701266e0d504395855ae77815047ca0b53aea09d511ab22b921ffef3bf320f4352
7
- data.tar.gz: a55f87fec6301cc3d3fa1727bd66d3b2d96a062c7a7190082fe640c7352f2f6547b66b0481d91f945bd74a9332b106913c85800b1dc777385ec0683d7f3a2b94
6
+ metadata.gz: 974c8c6c0464f36a73d93bde35a29545874da0ae0c1832d5cac4f0588e4e774fcaf365201e403936a1138c51c39efd07ebd14fb4fd857a72348d3fbf19779ba3
7
+ data.tar.gz: 4b305bc259bad7d833a74d3d27dfbc8922b83a5bb2b6dce48b3077f433b89de7d2635f5506e8c084647fef60b29b8b1973e1329e3390959af80565053b819600
data/README.md CHANGED
@@ -53,7 +53,13 @@ end
53
53
  ```
54
54
 
55
55
  See the rdoc for more details on the helper functions. For a functioning
56
- example app, run <tt>rackup -p $PORT</tt> in the example directory.
56
+ example app, start the app in the example directory:
57
+
58
+ ```
59
+ bundle install
60
+ bundle exec rackup -p PORT
61
+
62
+ ```
57
63
 
58
64
  Available sinatra settings:
59
65
 
data/example/app.rb CHANGED
@@ -1,28 +1,29 @@
1
- #!/usr/bin/env ruby
2
-
3
- $: << File.join(File.dirname(__FILE__), "..", "lib")
4
-
5
- require "sinatra/base"
6
- require "sinatra/browserid"
7
-
8
- class TestApp < Sinatra::Base
9
- register Sinatra::BrowserID
1
+ require 'sinatra'
2
+ require 'sinatra/browserid'
3
+
4
+
5
+ register Sinatra::BrowserID
6
+
7
+ set :sessions, true
8
+ # Disabling origin-check is needed to make webkit-browsers like Chrome work.
9
+ # Behind a proxy you will also need to disable :remote_token, regardless for which browser.
10
+ set :protection, except: [:http_origin]
11
+ get '/' do
12
+ if authorized?
13
+ "Welcome, #{authorized_email}"
14
+ else
15
+ render_login_button
16
+ end
17
+ end
10
18
 
11
- set :sessions, true
19
+ get '/secure' do
20
+ authorize! # require a user be logged in
12
21
 
13
- get '/' do
14
- erb :index
15
- end
22
+ authorized_email # browserid email
23
+ end
16
24
 
17
- get '/logout' do
25
+ get '/logout' do
18
26
  logout!
19
27
 
20
28
  redirect '/'
21
- end
22
-
23
- get '/confidential' do
24
- authorize!
25
-
26
- "Hey #{authorized_email}, you're authorized!"
27
- end
28
- end
29
+ end
data/example/config.ru CHANGED
@@ -1,2 +1,8 @@
1
- require "./app"
2
- run TestApp
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ Bundler.require
5
+
6
+ require './app.rb'
7
+
8
+ run Sinatra::Application.new
@@ -28,6 +28,28 @@ module Sinatra
28
28
  session[:browserid_email]
29
29
  end
30
30
 
31
+ # Normalize the email like the broker will do it, see
32
+ # https://github.com/portier/portier.github.io/blob/master/specs/Email-Normalization.md
33
+ def normalize_email(email)
34
+ begin
35
+ user, domain = email.split("@")
36
+ if user == nil or user.empty?
37
+ raise ArgumentError.new('user part must not be empty')
38
+ end
39
+ user = user.downcase
40
+ domain = SimpleIDN.to_ascii(domain).downcase
41
+ begin
42
+ IPAddr.new(domain)
43
+ rescue
44
+ # if domain could not be parsed as IP we are good
45
+ return user + "@" + domain
46
+ end
47
+ raise ArgumentError.new('domain must not be an IP')
48
+ rescue Exception => e
49
+ raise ArgumentError, 'Not a valid email adress: ' + e.message
50
+ end
51
+ end
52
+
31
53
  # Returns the HTML to render the Persona login form.
32
54
  # Optionally takes a URL parameter for where the user should
33
55
  # be redirected to after the assert POST back.
@@ -39,9 +61,14 @@ module Sinatra
39
61
  redirect_url ||= request.url
40
62
  session['redirect_url'] = redirect_url
41
63
 
42
- nonce = session[:nonce]
43
- unless nonce
44
- session[:nonce] = nonce = SecureRandom.base64
64
+ if session[:nonce]
65
+ nonce = session[:nonce]
66
+ # Try to limit how many nonces are stored by keeping the session nonce alive
67
+ Cachy.delete_key(nonce)
68
+ Cachy.cache(nonce, expires_in: 600) { true }
69
+ else
70
+ session[:nonce] = nonce = SecureRandom.base64
71
+ Cachy.cache(nonce, expires_in: 600) { true }
45
72
  end
46
73
 
47
74
  template = ERB.new(Templates::LOGIN_BUTTON)
@@ -4,14 +4,27 @@ require "open-uri"
4
4
  require 'json'
5
5
  require 'url_safe_base64'
6
6
  require 'jwt'
7
+ require 'simpleidn'
8
+ require 'ipaddr'
7
9
  require "sinatra/base"
8
10
  require 'sinatra/browserid/helpers'
9
11
  require 'sinatra/browserid/template'
12
+ require 'addressable/uri'
13
+ require 'cachy'
14
+ require 'moneta'
15
+
10
16
 
11
17
  # This module provides an interface to verify a users email address
12
18
  # with browserid.org.
13
19
  module Sinatra
14
20
  module BrowserID
21
+
22
+ # Init an in-memory cache via the cachy gem. We use this
23
+ # instead of the session because of dropped sessions
24
+ # after redirects, see https://github.com/sinatra/sinatra/issues/1742.
25
+ Cachy.cache_store = Moneta.new(:Memory, expires: 600) # 10 minutes
26
+ # We need to set a global :expires here because of https://github.com/grosser/cachy/issues/7
27
+
15
28
  def self.registered(app)
16
29
  app.helpers BrowserID::Helpers
17
30
 
@@ -22,35 +35,48 @@ module Sinatra
22
35
  app.set :browserid_button_text, "Log in"
23
36
 
24
37
  app.get '/_browserid_login' do
25
- # TODO(petef): render a page that initiates login without
26
- # waiting for a user click.
27
38
  render_login_button
28
39
  end
29
40
 
30
41
  app.post '/_browserid_assert' do
31
42
  begin
32
- # 3. Server checks signature
33
- # for that, fetch the public key from the LA instance (TODO: Do that beforehand for trusted instances, and generally cache the key)
34
- public_key_jwks = ::JSON.parse(URI.parse(URI.escape(settings.browserid_url + '/keys.json')).read)
43
+ # Server checks signature
44
+ # For that, fetch the public key from the LA instance (TODO: Do that beforehand for trusted instances, and generally cache the key)
45
+ public_key_jwks_uri = Addressable::URI.parse(settings.browserid_url + '/keys.json')
46
+ public_key_jwks = ::JSON.parse(URI.parse(public_key_jwks_uri).read)
35
47
  public_key = OpenSSL::PKey::RSA.new
36
- public_key.e = OpenSSL::BN.new UrlSafeBase64.decode64(public_key_jwks["keys"][0]["e"]), 2
37
- public_key.n = OpenSSL::BN.new UrlSafeBase64.decode64(public_key_jwks["keys"][0]["n"]), 2
48
+ if public_key.respond_to? :set_key
49
+ # Set n and d via the new set_key function, as direct access to n and e is blocked for some ruby and openssl versions.
50
+ # Note that we have no d, as this is a public key, which would be the third param
51
+ public_key.set_key( (OpenSSL::BN.new UrlSafeBase64.decode64(public_key_jwks["keys"][0]["n"]), 2),
52
+ (OpenSSL::BN.new UrlSafeBase64.decode64(public_key_jwks["keys"][0]["e"]), 2),
53
+ nil)
54
+ else
55
+ public_key.e = OpenSSL::BN.new UrlSafeBase64.decode64(public_key_jwks["keys"][0]["e"]), 2
56
+ public_key.n = OpenSSL::BN.new UrlSafeBase64.decode64(public_key_jwks["keys"][0]["n"]), 2
57
+ end
38
58
 
39
59
  id_token = JWT.decode params[:id_token], public_key, true, { :algorithm => 'RS256' }
40
60
  id_token = id_token[0]
41
- # 4. Needs to make sure token is still valid
61
+ # Needs to make sure token is still valid
42
62
  if (id_token["iss"] == settings.browserid_url &&
43
63
  id_token["aud"] == request.base_url.chomp('/') &&
44
64
  id_token["exp"] > Time.now.to_i &&
45
65
  id_token["email_verified"] &&
46
- id_token["nonce"] == session[:nonce])
47
- session[:browserid_email] = id_token["email"]
48
- session.delete(:nonce)
66
+ # nonce is really known to us
67
+ Cachy.get(id_token["nonce"]))
68
+ session[:browserid_email] = id_token['email']
69
+ Cachy.delete_key(id_token["nonce"])
70
+ session.delete(:nonce) # it's possible the session persisted
49
71
  if session['redirect_url']
50
72
  redirect session['redirect_url']
51
73
  else
52
74
  redirect "/"
53
75
  end
76
+ else
77
+ # Even when the token check failed the nonce has to be invalidated
78
+ Cachy.delete_key(id_token["nonce"])
79
+ session.delete(:nonce)
54
80
  end
55
81
  rescue OpenURI::HTTPError => e
56
82
  puts "could not validate token: " + e.to_s
@@ -58,8 +84,8 @@ module Sinatra
58
84
  halt 403
59
85
 
60
86
  end
61
- end # def self.registered
62
- end # module BrowserID
87
+ end
88
+ end
63
89
  register BrowserID
64
- end # module Sinatra
90
+ end
65
91
 
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-portier
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: '2.0'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pete Fritchman
8
8
  - Malte Paskuda
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2017-09-05 00:00:00.000000000 Z
12
+ date: 2022-02-02 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: sinatra
@@ -53,7 +53,63 @@ dependencies:
53
53
  - - ">="
54
54
  - !ruby/object:Gem::Version
55
55
  version: 0.2.2
56
- description:
56
+ - !ruby/object:Gem::Dependency
57
+ name: simpleidn
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 0.0.9
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: 0.0.9
70
+ - !ruby/object:Gem::Dependency
71
+ name: addressable
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '2.8'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '2.8'
84
+ - !ruby/object:Gem::Dependency
85
+ name: cachy
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0.4'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0.4'
98
+ - !ruby/object:Gem::Dependency
99
+ name: moneta
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '1.4'
105
+ type: :runtime
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '1.4'
112
+ description:
57
113
  email:
58
114
  - malte@paskuda.biz
59
115
  executables: []
@@ -63,7 +119,6 @@ files:
63
119
  - README.md
64
120
  - example/app.rb
65
121
  - example/config.ru
66
- - example/views/index.erb
67
122
  - lib/sinatra/browserid.rb
68
123
  - lib/sinatra/browserid/helpers.rb
69
124
  - lib/sinatra/browserid/template.rb
@@ -71,7 +126,7 @@ files:
71
126
  homepage: https://github.com/onli/sinatra-portier
72
127
  licenses: []
73
128
  metadata: {}
74
- post_install_message:
129
+ post_install_message:
75
130
  rdoc_options:
76
131
  - "--inline-source"
77
132
  require_paths:
@@ -87,9 +142,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
87
142
  - !ruby/object:Gem::Version
88
143
  version: '0'
89
144
  requirements: []
90
- rubyforge_project:
91
- rubygems_version: 2.6.13
92
- signing_key:
145
+ rubygems_version: 3.2.32
146
+ signing_key:
93
147
  specification_version: 4
94
148
  summary: Sinatra extension for user authentication with portier
95
149
  test_files: []
@@ -1,21 +0,0 @@
1
- <html>
2
- <head>
3
- </head>
4
- <body>
5
-
6
- <h1>Test App</h1>
7
-
8
- <p>
9
- <% if authorized? %>
10
- Hello, <%= authorized_email %> <a href="/logout">(logout)</a>
11
- <% else %>
12
- <%= render_login_button %>
13
- <% end %>
14
- </p>
15
-
16
- <p>
17
- see a <a href="/confidential">page that requires a login</a>.
18
- </p>
19
-
20
- </body>
21
- </html>