browserid-auth-rails 0.5.3
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.
- data/LICENSE +22 -0
- data/README.md +194 -0
- data/app/assets/javascripts/browserid.js.coffee +84 -0
- data/app/views/shared/_browserid.html.erb +12 -0
- data/lib/browserid-auth-rails.rb +1 -0
- data/lib/browserid-rails.rb +56 -0
- data/lib/browserid/rails/base.rb +131 -0
- data/lib/browserid/rails/helpers.rb +67 -0
- data/lib/browserid/rails/version.rb +5 -0
- data/lib/browserid/verifier/persona.rb +85 -0
- metadata +148 -0
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 Greg Look
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,194 @@
|
|
1
|
+
Modified fork of [https://github.com/mvxcvi/browserid-rails](https://github.com/mvxcvi/browserid-rails) to be compatible with Devise.
|
2
|
+
|
3
|
+
# BrowserID::Rails
|
4
|
+
|
5
|
+
This gem provides a lightweight single-sign-on authentication structure to a
|
6
|
+
Rails application based on
|
7
|
+
[Mozilla's Persona service](https://login.persona.org/about). Persona
|
8
|
+
authenticates clients uniquely by their email address using the BrowserID
|
9
|
+
protocol, without exposing clients' browsing behaviors to the identity provider.
|
10
|
+
This also frees the application from needing to securely handle and store user
|
11
|
+
credentials.
|
12
|
+
|
13
|
+
## Overview
|
14
|
+
|
15
|
+
BrowserID affords a very easy SSO experience for clients. A simplified version
|
16
|
+
of the authentication flow goes like this:
|
17
|
+
|
18
|
+
1. The user clicks a login link on the site.
|
19
|
+
2. A pop-up window directs the user to authenticate with their identity
|
20
|
+
provider. This will either be their email provider or Mozilla's fall-back
|
21
|
+
Persona service.
|
22
|
+
3. If the authentication is successful, the browser acquires a certificate
|
23
|
+
proving that the client owns the email address in question. This only needs
|
24
|
+
to be done once for an email address across any number of domains; after
|
25
|
+
that the user can just click a button to use that address to authenticate.
|
26
|
+
4. The browser then uses the certificate to sign an authentication assertion
|
27
|
+
for the site (given by the `audience` parameter).
|
28
|
+
5. The browser POSTs the signed assertion to a session creation URL for
|
29
|
+
verification. If the assertion is valid, the authenticated email is stored
|
30
|
+
in the client's session and the page is reloaded.
|
31
|
+
|
32
|
+
At this point, the `browserid_email` method will return the stored email
|
33
|
+
address, and `browserid_current_user` will look up the authenticated user model.
|
34
|
+
See below for more detailed documentation of the available controller and helper
|
35
|
+
methods.
|
36
|
+
|
37
|
+
Logging out is also straightforward:
|
38
|
+
|
39
|
+
1. The (authenticated) user clicks a logout link on the site.
|
40
|
+
2. The browser clears its stored assertion for the site and POSTs a
|
41
|
+
request to the server to clear its login state.
|
42
|
+
3. The server removes the authenticated email stored in the client's session
|
43
|
+
and reloads the page.
|
44
|
+
|
45
|
+
## Installation
|
46
|
+
|
47
|
+
Add this line to your application's Gemfile:
|
48
|
+
|
49
|
+
gem 'browserid-rails'
|
50
|
+
|
51
|
+
And then execute:
|
52
|
+
|
53
|
+
$ bundle
|
54
|
+
|
55
|
+
Or install it yourself as:
|
56
|
+
|
57
|
+
$ gem install browserid-rails
|
58
|
+
|
59
|
+
## Usage
|
60
|
+
|
61
|
+
To use this gem once it is installed, it must be integrated into your Rails
|
62
|
+
application. The following sections cover the gem configuration, controller
|
63
|
+
integration, and view integration.
|
64
|
+
|
65
|
+
### Configuration
|
66
|
+
|
67
|
+
There are several configuration options available. There are a number of default
|
68
|
+
assumptions about the application, which may be overridden as needed.
|
69
|
+
Configuration settings are properties of `config.browserid`.
|
70
|
+
|
71
|
+
* `user_model` - The name of the ActiveModel class for application users.
|
72
|
+
The default is `"User"`.
|
73
|
+
* `email_field` - The attribute of the user model which contains the user's
|
74
|
+
email. The default is `:email`.
|
75
|
+
* `session_variable` - The location the authenticated email is stored in the
|
76
|
+
client's session. The default is `:browserid_email`.
|
77
|
+
* `verifier` - The type of verifier to use to authenticate client BrowserID
|
78
|
+
assertions. The default is `:persona`, which sends the request to Mozilla's
|
79
|
+
Persona verification service. In the future, `:local` will enable local
|
80
|
+
verification code. Alternately, this configuration option may be set to any
|
81
|
+
object which responds to `#verify(assertion, audience)` with the verified
|
82
|
+
email and identity provider on success and raises an error on failure.
|
83
|
+
* `audience` - The BrowserID audience to authenticate to. This should consist
|
84
|
+
of a URI string containing the scheme (protocol), authority, and port of the
|
85
|
+
service (e.g., `"https://app.example.com:443"`). By default, the audience is
|
86
|
+
not hardcoded and the properties of the request object are used to construct
|
87
|
+
it dynamically. This gives greater flexibility while developing, but is also
|
88
|
+
a minor security risk. In production, this should be configured to a fixed
|
89
|
+
value.
|
90
|
+
|
91
|
+
Additionally, there are two sub-structures `login` and `logout` for configuring
|
92
|
+
the associated paths and default link text. They have the following properties:
|
93
|
+
|
94
|
+
* `text` - The default text to give login and logout links.
|
95
|
+
* `path` - The target to give links and the path to `POST` authentication
|
96
|
+
requests to. Defaults to `"/login"` and `"/logout"` respectively.
|
97
|
+
|
98
|
+
So, if you wanted the application to use 'signin' and 'signout' instead, you
|
99
|
+
could do the following:
|
100
|
+
|
101
|
+
config.browserid.login.text = "Sign-in"
|
102
|
+
config.browserid.login.path = '/signin'
|
103
|
+
config.browserid.logout.text = "Sign-out"
|
104
|
+
config.browserid.logout.path = '/signout'
|
105
|
+
|
106
|
+
### Controller Integration
|
107
|
+
|
108
|
+
The `BrowserID::Rails::Base` module makes several controller methods available
|
109
|
+
to interact with the authentication system. To access information, use one of:
|
110
|
+
|
111
|
+
* `browserid_email` - Returns the BrowserID-authenticated email address, if any.
|
112
|
+
* `browserid_current_user` - Retrieves the model for the currently authenticated
|
113
|
+
user, if there is an authenticated email and a matching user exists.
|
114
|
+
* `browserid_authenticated?` - Returns true if there is a current user.
|
115
|
+
|
116
|
+
These methods are also available in views as helpers.
|
117
|
+
|
118
|
+
To control authentication, the app should have a `SessionsController` which
|
119
|
+
connects the in-browser authentication code to the server. The gem provides
|
120
|
+
these methods:
|
121
|
+
|
122
|
+
* `login_browserid` - Sets the given string as the authenticated email.
|
123
|
+
* `logout_browserid` - Clears the current authenticated email.
|
124
|
+
* `verify_browserid` - Uses the configured verifier to confirm a BrowserID
|
125
|
+
assertion is correct for the service audience.
|
126
|
+
* `respond_to_browserid` - Wraps `verify_browserid` in logging and error
|
127
|
+
handling logic and generates controller responses to a `POST` assertion.
|
128
|
+
|
129
|
+
Implementing the required methods for `SessionsController` is straightforward:
|
130
|
+
|
131
|
+
# POST /login
|
132
|
+
def create
|
133
|
+
respond_to_browserid
|
134
|
+
end
|
135
|
+
|
136
|
+
# POST /logout
|
137
|
+
def destroy
|
138
|
+
logout_browserid
|
139
|
+
head :ok
|
140
|
+
end
|
141
|
+
|
142
|
+
### Layout Integration
|
143
|
+
|
144
|
+
The BrowserID javascript library needs to be loaded on your application pages.
|
145
|
+
There are two steps to accomplish this:
|
146
|
+
|
147
|
+
First, the coffeescript asset file needs to be loaded. In the
|
148
|
+
`app/assets/javascripts/application.js` manifest, add the following line:
|
149
|
+
|
150
|
+
//= require browserid
|
151
|
+
|
152
|
+
Second, the scripts need to be setup in your pages' `<head>` section. The
|
153
|
+
`setup_browserid` helper method takes care of this for you and gives a couple
|
154
|
+
of ways to control its behavior:
|
155
|
+
|
156
|
+
<!-- Perform basic BrowserID setup in the head section -->
|
157
|
+
<%= setup_browserid %>
|
158
|
+
|
159
|
+
<!-- Setup BrowserID with alert debugging -->
|
160
|
+
<%= setup_browserid debug: true %>
|
161
|
+
|
162
|
+
<!-- Setup BrowserID with a custom handler -->
|
163
|
+
<%= setup_browserid do %>
|
164
|
+
browserid.onLogin = function (data, status, xhr) {
|
165
|
+
// ...
|
166
|
+
}
|
167
|
+
<% end %>
|
168
|
+
|
169
|
+
Once that's accomplished, the app is ready to use BrowserID for authentication.
|
170
|
+
To add login and logout links to the site, use the `login_link` and
|
171
|
+
`logout_link` helpers. These accept an optional link text as a parameter:
|
172
|
+
|
173
|
+
<%= logout_link %>
|
174
|
+
|
175
|
+
<%= login_link "Login with Persona" %>
|
176
|
+
|
177
|
+
The coffeescript asset adds on-click handlers to the links which trigger the
|
178
|
+
Persona code to request new assertions or destroy existing ones.
|
179
|
+
|
180
|
+
## Contributing
|
181
|
+
|
182
|
+
1. Fork it
|
183
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
184
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
185
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
186
|
+
5. Create new Pull Request
|
187
|
+
|
188
|
+
## Future Work
|
189
|
+
|
190
|
+
* In the future, it would be nice to have a generator to create routes and
|
191
|
+
session controller skeletons. This would simplify setup and integration with
|
192
|
+
new apps quite a bit.
|
193
|
+
* Another to-do item is to incorporate the Persona branding assets and add more
|
194
|
+
helpers for generating login buttons.
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# BrowserID javascript functions
|
2
|
+
|
3
|
+
@browserid = browserid =
|
4
|
+
|
5
|
+
### PROPERTIES ###
|
6
|
+
|
7
|
+
# Public: Path used to verify browserID authentication assertions. Assertions
|
8
|
+
# are POSTed to this path.
|
9
|
+
loginPath: '/login'
|
10
|
+
|
11
|
+
# Public: Path used to unset persisted authentication state when logging out.
|
12
|
+
# This should clear the currently-logged-in user email.
|
13
|
+
logoutPath: '/logout'
|
14
|
+
|
15
|
+
# Internal: Debugging toggle - if true, the results of logins will be alert
|
16
|
+
# dialogs instead of page refreshes. This is useful to set if the application
|
17
|
+
# starts going into a refresh loop.
|
18
|
+
debug: false
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
### HANDLERS ###
|
23
|
+
|
24
|
+
# Public: This method is called when a user successfully authenticates. By
|
25
|
+
# default, it reloads the current page.
|
26
|
+
onLogin: (data, status, xhr) ->
|
27
|
+
if @debug
|
28
|
+
alert("Login: #{status}\n#{data}")
|
29
|
+
else
|
30
|
+
window.location.reload()
|
31
|
+
|
32
|
+
# Public: This method is called when a user fails to authenticate.
|
33
|
+
onLoginError: (xhr, status, err) ->
|
34
|
+
alert("Login: #{status} #{err}\n#{xhr.responseText}")
|
35
|
+
|
36
|
+
# Public: This method is called when a user clears their authentication. By
|
37
|
+
# default, it reloads the current page.
|
38
|
+
onLogout: (data, status, xhr) ->
|
39
|
+
if @debug
|
40
|
+
alert("Logout: #{status}\n#{data}")
|
41
|
+
else
|
42
|
+
window.location.reload()
|
43
|
+
|
44
|
+
# Public: This method is called when a user fails to clear their
|
45
|
+
# authentication.
|
46
|
+
onLogoutError: (xhr, status, err) ->
|
47
|
+
alert("Logout: #{status} #{err}\n#{xhr.responseText}")
|
48
|
+
|
49
|
+
|
50
|
+
### INITIALIZATION ###
|
51
|
+
|
52
|
+
# Public: Watches the authentication state and takes action when the user
|
53
|
+
# logs in or logs out. This method MUST be called on every page of the
|
54
|
+
# application.
|
55
|
+
setup: (currentUser = null) ->
|
56
|
+
navigator.id.watch
|
57
|
+
loggedInUser: currentUser
|
58
|
+
onlogin: (assertion) =>
|
59
|
+
$.ajax
|
60
|
+
type: 'POST'
|
61
|
+
url: @loginPath
|
62
|
+
data: { assertion: assertion }
|
63
|
+
success: (data, status, xhr) => @onLogin(data, status, xhr)
|
64
|
+
error: (xhr, status, err) => @onLoginError(xhr, status, err)
|
65
|
+
onlogout: =>
|
66
|
+
$.ajax
|
67
|
+
type: 'POST'
|
68
|
+
url: @logoutPath
|
69
|
+
success: (data, status, xhr) => @onLogout(data, status, xhr)
|
70
|
+
error: (xhr, status, err) => @onLogoutError(xhr, status, err)
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
### Behavior Binding ###
|
75
|
+
|
76
|
+
jQuery ->
|
77
|
+
$('.browserid_login').click ->
|
78
|
+
navigator.id.request()
|
79
|
+
false
|
80
|
+
|
81
|
+
jQuery ->
|
82
|
+
$('.browserid_logout').click ->
|
83
|
+
navigator.id.logout()
|
84
|
+
false
|
@@ -0,0 +1,12 @@
|
|
1
|
+
<script src="https://login.persona.org/include.js"></script>
|
2
|
+
<script type="text/javascript">
|
3
|
+
<% if options[:login_path] %>browserid.loginPath = "<%= options[:login_path] %>";<% end %>
|
4
|
+
<% if options[:logout_path] %>browserid.logoutPath = "<%= options[:logout_path] %>";<% end %>
|
5
|
+
<% if options[:debug] %>browserid.debug = true;<% end %>
|
6
|
+
<%= yield :browserid_setup %>
|
7
|
+
<% if browserid_email %>
|
8
|
+
browserid.setup("<%= browserid_email %>");
|
9
|
+
<% else %>
|
10
|
+
browserid.setup();
|
11
|
+
<% end %>
|
12
|
+
</script>
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'browserid-rails'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'browserid/rails/base'
|
2
|
+
require 'browserid/rails/helpers'
|
3
|
+
require 'browserid/rails/version'
|
4
|
+
|
5
|
+
module BrowserID
|
6
|
+
module Rails
|
7
|
+
# This class defines a Rails engine which extends the base controller with
|
8
|
+
# the library methods. The presence of this engine also causes assets to
|
9
|
+
# be included when the gem is added as a dependency.
|
10
|
+
class Engine < ::Rails::Engine
|
11
|
+
# Initialize the engine configuration.
|
12
|
+
config.before_configuration do
|
13
|
+
BrowserIDConfig = Struct.new :user_model, :email_field, :session_variable, :verifier, :audience, :login, :logout
|
14
|
+
BrowserIDLinkConfig = Struct.new :text, :path
|
15
|
+
|
16
|
+
config.browserid = BrowserIDConfig.new.tap do |cfg|
|
17
|
+
cfg.user_model = 'User'
|
18
|
+
cfg.email_field = :email
|
19
|
+
cfg.session_variable = :browserid_email
|
20
|
+
cfg.verifier = :persona
|
21
|
+
# audience should only be set in production
|
22
|
+
|
23
|
+
cfg.login = BrowserIDLinkConfig.new.tap do |link|
|
24
|
+
link.text = "Login"
|
25
|
+
link.path = '/login'
|
26
|
+
end
|
27
|
+
|
28
|
+
cfg.logout = BrowserIDLinkConfig.new.tap do |link|
|
29
|
+
link.text = "Logout"
|
30
|
+
link.path = '/logout'
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
# Mix in the controller and view helper methods.
|
36
|
+
config.before_initialize do
|
37
|
+
ActionController::Base.send :include, BrowserID::Rails::Base
|
38
|
+
ActionView::Base.send :include, BrowserID::Rails::Helpers
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create the assertion verifier.
|
42
|
+
config.after_initialize do
|
43
|
+
cfg = config.browserid
|
44
|
+
|
45
|
+
# Replace type symbol with constructed verifier.
|
46
|
+
if cfg.verifier == :persona
|
47
|
+
cfg.verifier = BrowserID::Verifier::Persona.new
|
48
|
+
elsif cfg.verifier == :local
|
49
|
+
raise "Local BrowserID verification is not supported yet" # TODO
|
50
|
+
elsif !cfg.verifier.respond_to?(:verify)
|
51
|
+
raise "Unknown BrowserID verifier type #{cfg.verifier}"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
require 'browserid/verifier/persona'
|
2
|
+
|
3
|
+
module BrowserID
|
4
|
+
module Rails
|
5
|
+
# Public: Base module for inclusion into a controller. This module includes
|
6
|
+
# methods for dealing with BrowserID user authentication.
|
7
|
+
module Base
|
8
|
+
|
9
|
+
##### INTERNAL METHODS #####
|
10
|
+
|
11
|
+
# Internal: Modifies the controller this module is included in to provide
|
12
|
+
# authentication-related helper methods
|
13
|
+
#
|
14
|
+
# base - The Class this module is being included in.
|
15
|
+
def self.included(base)
|
16
|
+
base.send :helper_method, :browserid_config, :browserid_email, :browserid_current_user, :browserid_authenticated?
|
17
|
+
end
|
18
|
+
|
19
|
+
# Internal: Gets the application configuration for this gem.
|
20
|
+
#
|
21
|
+
# Returns the app config structure.
|
22
|
+
def browserid_config
|
23
|
+
::Rails.application.config.browserid
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
|
28
|
+
##### HELPER METHODS #####
|
29
|
+
|
30
|
+
# Public: Gets the email address of the currently-authenticated user.
|
31
|
+
#
|
32
|
+
# Returns the authenticated email address String.
|
33
|
+
def browserid_email
|
34
|
+
session[browserid_config.session_variable]
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Retrieves the user for the authenticated email address. This
|
38
|
+
# method uses the `browserid.user_model` and `browserid.email_field`
|
39
|
+
# config settings, which default to `User` and `email`.
|
40
|
+
#
|
41
|
+
# Returns the current authenticated user, or nil if no user exists.
|
42
|
+
def browserid_current_user
|
43
|
+
if browserid_email.nil?
|
44
|
+
nil
|
45
|
+
elsif @browserid_current_user
|
46
|
+
@browserid_current_user
|
47
|
+
else
|
48
|
+
config = browserid_config
|
49
|
+
user_model = config.user_model.constantize
|
50
|
+
find_method = "find_by_#{config.email_field}".intern
|
51
|
+
|
52
|
+
@browserid_current_user = user_model.send find_method, browserid_email
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Public: Determines whether the current client is authenticated as a
|
57
|
+
# registered User.
|
58
|
+
#
|
59
|
+
# Returns true if the client is authenticated and registered.
|
60
|
+
def browserid_authenticated?
|
61
|
+
!browserid_current_user.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
##### AUTHENTICATION METHODS #####
|
67
|
+
|
68
|
+
# Public: Sets the given email address as the currently-authenticated user.
|
69
|
+
# The address is saved in the client's session.
|
70
|
+
#
|
71
|
+
# email - The String email address to consider authenticated.
|
72
|
+
def login_browserid(email)
|
73
|
+
session[browserid_config.session_variable] = email
|
74
|
+
end
|
75
|
+
|
76
|
+
# Public: Clears the saved email address for the currently-authenticated
|
77
|
+
# user. It is important to note that this does not remove the BrowserID
|
78
|
+
# assertion in the client's browser.
|
79
|
+
def logout_browserid
|
80
|
+
session[browserid_config.session_variable] = nil
|
81
|
+
end
|
82
|
+
|
83
|
+
# Public: Uses the configured verifier to check that a provided assertion
|
84
|
+
# is correct for the site audience.
|
85
|
+
#
|
86
|
+
# Returns the verified email, identity issuer, and audience on success.
|
87
|
+
# Raises an error with a failure message if the client was not
|
88
|
+
# successfully authenticated.
|
89
|
+
#
|
90
|
+
# Examples
|
91
|
+
#
|
92
|
+
# verify_browserid(assertion)
|
93
|
+
# # => "user@example.com", "persona.mozilla.com", "https://app.example.com:443"
|
94
|
+
#
|
95
|
+
def verify_browserid(assertion)
|
96
|
+
audience = browserid_config.audience
|
97
|
+
audience ||= "%s%s:%d" % [request.protocol, request.host, request.port]
|
98
|
+
browserid_config.verifier.verify(assertion, audience)
|
99
|
+
end
|
100
|
+
|
101
|
+
# Public: Handles a POST-ed BrowserID assertion, responding appropriately
|
102
|
+
# to the request. If successful, this logs-in the authenticated email and
|
103
|
+
# returns an OK status. If unsuccessful, it returns FORBIDDEN and an
|
104
|
+
# error message in the response body.
|
105
|
+
#
|
106
|
+
# Returns nothing.
|
107
|
+
#
|
108
|
+
# Examples
|
109
|
+
#
|
110
|
+
# # POST /login
|
111
|
+
# def create
|
112
|
+
# respond_to_browserid
|
113
|
+
# end
|
114
|
+
#
|
115
|
+
def respond_to_browserid
|
116
|
+
if params[:assertion].blank?
|
117
|
+
head :bad_request
|
118
|
+
else
|
119
|
+
email, issuer, audience = verify_browserid params[:assertion]
|
120
|
+
logger.info "Verified BrowserID assertion for #{email} issued by #{issuer} on #{audience}"
|
121
|
+
login_browserid email
|
122
|
+
head :ok
|
123
|
+
end
|
124
|
+
rescue StandardError => e
|
125
|
+
# TODO: distinguish between process failures and invalid assertions
|
126
|
+
logger.warn "Failed to verify BrowserID assertion: #{e.message}"
|
127
|
+
render status: :forbidden, text: e.message
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module BrowserID
|
2
|
+
module Rails
|
3
|
+
# Public: Rails view helpers for use with BrowserID code.
|
4
|
+
module Helpers
|
5
|
+
# Public: Renders a layout partial which initializes the BrowserID
|
6
|
+
# system. This should be called in the head of the application layout.
|
7
|
+
#
|
8
|
+
# options - Hash used to adjust the browserid asset setup (default: {}).
|
9
|
+
# :login_path - String giving the path to POST assertions to
|
10
|
+
# for verification. Defaults to the configured
|
11
|
+
# `browserid.login.path`.
|
12
|
+
# :logout_path - String giving the path to POST logout
|
13
|
+
# notifications to. Defaults to the configured
|
14
|
+
# `browserid.logout.path`.
|
15
|
+
# :debug - Boolean determining whether the browserid
|
16
|
+
# javascript will refresh the page or show an
|
17
|
+
# alert dialog.
|
18
|
+
# block - An optional block which can be used to provide additional
|
19
|
+
# content to be rendered inside the browserid setup script tag.
|
20
|
+
#
|
21
|
+
# Examples
|
22
|
+
#
|
23
|
+
# <!-- Perform basic BrowserID setup in the head section -->
|
24
|
+
# <%= setup_browserid %>
|
25
|
+
#
|
26
|
+
# <!-- Setup BrowserID with alert debugging -->
|
27
|
+
# <%= setup_browserid debug: true %>
|
28
|
+
#
|
29
|
+
# <!-- Setup BrowserID with a custom handler -->
|
30
|
+
# <%= setup_browserid do %>
|
31
|
+
# browserid.onLogin = function (data, status, xhr) {
|
32
|
+
# // ...
|
33
|
+
# }
|
34
|
+
# <% end %>
|
35
|
+
#
|
36
|
+
def setup_browserid(options={}, &block)
|
37
|
+
defaults = { login_path: browserid_config.login.path, logout_path: browserid_config.logout.path }
|
38
|
+
content_for :browserid_setup, capture(&block) if block_given?
|
39
|
+
render 'shared/browserid', options: defaults.merge(options)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Public: Renders a login link which will request a new authentication
|
43
|
+
# assertion from the BrowserID javascript code. The default link text is
|
44
|
+
# configurable with `config.browserid.login.text`. The link target is
|
45
|
+
# similarly configurable with `config.browserid.login.path`.
|
46
|
+
#
|
47
|
+
# text - Optional String to use as link text (default: configured value).
|
48
|
+
def login_link(text=nil)
|
49
|
+
text ||= browserid_config.login.text
|
50
|
+
target = browserid_config.login.path || '#'
|
51
|
+
link_to text, target, class: :browserid_login
|
52
|
+
end
|
53
|
+
|
54
|
+
# Public: Renders a logout link which will clear the current BrowserID
|
55
|
+
# authentication status. The default link text is configurable with
|
56
|
+
# `config.browserid.logout.text`. The link target is similarly
|
57
|
+
# configurable with `config.browserid.logout.path`.
|
58
|
+
#
|
59
|
+
# text - Optional String to use as link text (default: configured value).
|
60
|
+
def logout_link(text=nil)
|
61
|
+
text ||= browserid_config.logout.text
|
62
|
+
target = browserid_config.logout.path || '#'
|
63
|
+
link_to text, target, class: :browserid_logout
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'net/https'
|
3
|
+
|
4
|
+
module BrowserID
|
5
|
+
module Verifier
|
6
|
+
# Public: This class sends the assertion to Mozilla's Persona server for
|
7
|
+
# verification.
|
8
|
+
class Persona
|
9
|
+
attr_accessor :server, :path
|
10
|
+
|
11
|
+
# Public: String defining the endpoint of the server to perform Persona
|
12
|
+
# verifications against.
|
13
|
+
VERIFICATION_SERVER = 'verifier.login.persona.org'
|
14
|
+
|
15
|
+
# Public: String defining the normal path to POST assertion verifications
|
16
|
+
# to.
|
17
|
+
VERIFICATION_PATH = '/verify'
|
18
|
+
|
19
|
+
# Public: Constructs a new Persona verifier.
|
20
|
+
#
|
21
|
+
# server - Domain String of the server to send assertions to for
|
22
|
+
# verifications (default: VERIFICATION_SERVER).
|
23
|
+
# path - Path String to POST to on the server (default:
|
24
|
+
# VERIFICATION_PATH).
|
25
|
+
#
|
26
|
+
def initialize(server=VERIFICATION_SERVER, path=VERIFICATION_PATH)
|
27
|
+
@server = server
|
28
|
+
@path = path
|
29
|
+
end
|
30
|
+
|
31
|
+
# Public: Verifies a Persona assertion for a given audience.
|
32
|
+
#
|
33
|
+
# assertion - Persona authentication assertion.
|
34
|
+
# audience - Audience String to verify assertion against. This should be
|
35
|
+
# the URI of the service with scheme, authority, and port.
|
36
|
+
#
|
37
|
+
# Returns the authenticated email address String and the issuing domain
|
38
|
+
# if the assertion is valid.
|
39
|
+
# Raises an exception with a failure message if the client was not
|
40
|
+
# successfully authenticated.
|
41
|
+
#
|
42
|
+
# Examples
|
43
|
+
#
|
44
|
+
# verify(assertion, "https://app.example.com:443")
|
45
|
+
# # => "user@example.com", "persona.mozilla.com"
|
46
|
+
#
|
47
|
+
def verify(assertion, audience)
|
48
|
+
http = Net::HTTP.new(@server, 443)
|
49
|
+
http.use_ssl = true
|
50
|
+
|
51
|
+
verification = Net::HTTP::Post.new(@path)
|
52
|
+
verification.set_form_data(assertion: assertion, audience: audience)
|
53
|
+
|
54
|
+
response = http.request(verification)
|
55
|
+
raise "Unsuccessful response from #{@server}: #{response}" unless response.kind_of? Net::HTTPSuccess
|
56
|
+
authentication = JSON.parse(response.body)
|
57
|
+
|
58
|
+
# Authentication response is a JSON hash which must contain a 'status'
|
59
|
+
# of "okay" or "failure".
|
60
|
+
status = authentication['status']
|
61
|
+
raise "Unknown authentication status '#{status}'" unless %w{okay failure}.include? status
|
62
|
+
|
63
|
+
# An unsuccessful authentication response should contain a reason string.
|
64
|
+
raise "Assertion failure: #{authentication['reason']}" unless status == "okay"
|
65
|
+
|
66
|
+
# A successful response looks like the following:
|
67
|
+
# {
|
68
|
+
# "status": "okay",
|
69
|
+
# "email": "user@example.com",
|
70
|
+
# "audience": "https://service.example.com:443",
|
71
|
+
# "expires": 1234567890,
|
72
|
+
# "issuer": "persona.mozilla.com"
|
73
|
+
# }
|
74
|
+
|
75
|
+
auth_audience = authentication['audience']
|
76
|
+
raise "Persona assertion audience '#{auth_audience}' does not match verifier audience '#{audience}'" unless auth_audience == audience
|
77
|
+
|
78
|
+
expires = authentication['expires'] && Time.at(authentication['expires'].to_i/1000.0)
|
79
|
+
raise "Persona assertion expired at #{expires}" if expires && expires < Time.now
|
80
|
+
|
81
|
+
[authentication['email'], authentication['issuer']]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: browserid-auth-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.3
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Greg Look
|
9
|
+
- Alex Kravets
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2013-03-04 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: railties
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ~>
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '3.2'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
none: false
|
27
|
+
requirements:
|
28
|
+
- - ~>
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: '3.2'
|
31
|
+
- !ruby/object:Gem::Dependency
|
32
|
+
name: rspec-rails
|
33
|
+
requirement: !ruby/object:Gem::Requirement
|
34
|
+
none: false
|
35
|
+
requirements:
|
36
|
+
- - ~>
|
37
|
+
- !ruby/object:Gem::Version
|
38
|
+
version: '2.11'
|
39
|
+
type: :development
|
40
|
+
prerelease: false
|
41
|
+
version_requirements: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ~>
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '2.11'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: simplecov
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ! '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: tzinfo
|
65
|
+
requirement: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ! '>='
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
version: '0'
|
71
|
+
type: :development
|
72
|
+
prerelease: false
|
73
|
+
version_requirements: !ruby/object:Gem::Requirement
|
74
|
+
none: false
|
75
|
+
requirements:
|
76
|
+
- - ! '>='
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
- !ruby/object:Gem::Dependency
|
80
|
+
name: jquery-rails
|
81
|
+
requirement: !ruby/object:Gem::Requirement
|
82
|
+
none: false
|
83
|
+
requirements:
|
84
|
+
- - ! '>='
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: '0'
|
87
|
+
type: :runtime
|
88
|
+
prerelease: false
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
description: ! " This gem provides a lightweight single-sign-on authentication
|
96
|
+
framework to\n a Rails application based on Mozilla's Persona service (see:\n
|
97
|
+
\ https://login.persona.org/about). Persona identifies users by email address\n
|
98
|
+
\ and authenticates them using the BrowserID protocol. This lets a client\n prove
|
99
|
+
they own an email address without exposing their browsing behaviors to\n the
|
100
|
+
identity provider. This also frees the application from needing to\n securely
|
101
|
+
handle and store user credentials.\n"
|
102
|
+
email:
|
103
|
+
- alex@slatestudio.com
|
104
|
+
executables: []
|
105
|
+
extensions: []
|
106
|
+
extra_rdoc_files: []
|
107
|
+
files:
|
108
|
+
- app/assets/javascripts/browserid.js.coffee
|
109
|
+
- app/views/shared/_browserid.html.erb
|
110
|
+
- lib/browserid/rails/base.rb
|
111
|
+
- lib/browserid/rails/helpers.rb
|
112
|
+
- lib/browserid/rails/version.rb
|
113
|
+
- lib/browserid/verifier/persona.rb
|
114
|
+
- lib/browserid-auth-rails.rb
|
115
|
+
- lib/browserid-rails.rb
|
116
|
+
- LICENSE
|
117
|
+
- README.md
|
118
|
+
homepage: https://github.com/alexkravets/browserid-auth-rails
|
119
|
+
licenses: []
|
120
|
+
post_install_message:
|
121
|
+
rdoc_options: []
|
122
|
+
require_paths:
|
123
|
+
- lib
|
124
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
125
|
+
none: false
|
126
|
+
requirements:
|
127
|
+
- - ! '>='
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
segments:
|
131
|
+
- 0
|
132
|
+
hash: -465273068887906201
|
133
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
134
|
+
none: false
|
135
|
+
requirements:
|
136
|
+
- - ! '>='
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
segments:
|
140
|
+
- 0
|
141
|
+
hash: -465273068887906201
|
142
|
+
requirements: []
|
143
|
+
rubyforge_project:
|
144
|
+
rubygems_version: 1.8.24
|
145
|
+
signing_key:
|
146
|
+
specification_version: 3
|
147
|
+
summary: Lightweight Rails authentication framework using the BrowserID protocol.
|
148
|
+
test_files: []
|