sinatra-portier 1.4.0 → 2.0

Sign up to get free protection for your applications and to get access to all the features.
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>