browserid-rails 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +22 -0
- data/README.md +141 -0
- data/app/assets/javascripts/browserid.js.coffee +84 -0
- data/app/views/layouts/_browserid.html.erb +12 -0
- data/lib/browserid-rails.rb +41 -0
- data/lib/browserid/rails/base.rb +130 -0
- data/lib/browserid/rails/helpers.rb +62 -0
- data/lib/browserid/rails/version.rb +5 -0
- data/lib/browserid/verifier/persona.rb +85 -0
- metadata +76 -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,141 @@
|
|
1
|
+
# BrowserID::Rails
|
2
|
+
|
3
|
+
This gem provides a simple authentication structure to a Rails application
|
4
|
+
based on Mozilla's BrowserID protocol and Persona service. Users are uniquely
|
5
|
+
authenticated by email address using public-key cryptography. The advantage of
|
6
|
+
this is that the rails application does not need to worry about storing or
|
7
|
+
securing user passwords.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
gem 'browserid-rails'
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install browserid-rails
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
To use this gem once it is installed, it must be integrated into your Rails
|
26
|
+
application. The following sections cover the gem configuration, controller
|
27
|
+
integration, and view integration.
|
28
|
+
|
29
|
+
### Configuration
|
30
|
+
|
31
|
+
There are several configuration options available. There are a number of default
|
32
|
+
assumptions about the application, which may be overridden as needed.
|
33
|
+
Configuration settings are properties of `config.browserid`.
|
34
|
+
|
35
|
+
* `user_model` - The name of the ActiveModel class for application users.
|
36
|
+
The default is `"User"`.
|
37
|
+
* `email_field` - The name of the attribute on the user model which contains
|
38
|
+
the user's email. The default is `"email"`.
|
39
|
+
* `session_variable` - The location the authenticated email is stored in the
|
40
|
+
client's session. The default is `:browserid_email`.
|
41
|
+
* `verifier` - The type of verifier to use to authenticate client BrowserID
|
42
|
+
assertions. The default is `:persona`, which sends the request to Mozilla's
|
43
|
+
Persona verification service. In the future, `:local` will enable local
|
44
|
+
verification code. Alternately, this configuration option may be set to any
|
45
|
+
class which responds to `#verify(assertion)` with the verified email and
|
46
|
+
identity provider on success and raises an error on failure.
|
47
|
+
* `audience` - The BrowserID audience to authenticate to. This should consist
|
48
|
+
of a URI string containing the scheme (protocol), authority, and port of the
|
49
|
+
service (e.g., `"https://app.example.com:443"`). By default, the audience is
|
50
|
+
not hardcoded and the properties of the request object are used to construct
|
51
|
+
it dynamically. This gives greater flexibility while developing, but is also
|
52
|
+
a minor security risk. In production, this should be configured to a fixed
|
53
|
+
value.
|
54
|
+
|
55
|
+
### Controller Integration
|
56
|
+
|
57
|
+
The `BrowserID::Rails::Base` module makes several controller methods available
|
58
|
+
to interact with the authentication system. To access information, use one of:
|
59
|
+
|
60
|
+
* `browserid_email` - Returns the BrowserID-authenticated email address, if any.
|
61
|
+
* `current_user` - Retrieves the model for the currently authenticated user, if
|
62
|
+
there is an authenticated email and a matching user exists.
|
63
|
+
* `authenticated?` - Returns true if there is a current user.
|
64
|
+
|
65
|
+
These methods are also available in views as helpers.
|
66
|
+
|
67
|
+
To control authentication, the app should have a `SessionsController` which
|
68
|
+
connects the in-browser authentication code to the server. The gem provides
|
69
|
+
these methods:
|
70
|
+
|
71
|
+
* `login_browserid` - Sets the given string as the authenticated email.
|
72
|
+
* `logout_browserid` - Clears the current authenticated email.
|
73
|
+
* `verify_browserid` - Uses the configured verifier to confirm a BrowserID
|
74
|
+
assertion is correct for the service audience.
|
75
|
+
* `respond_to_browserid` - Wraps `verify_browserid` in logging and error
|
76
|
+
handling logic and generates controller responses to a `POST` assertion.
|
77
|
+
|
78
|
+
Implementing the required methods for `SessionsController` is straightforward:
|
79
|
+
|
80
|
+
# POST /login
|
81
|
+
def create
|
82
|
+
respond_to_browserid
|
83
|
+
end
|
84
|
+
|
85
|
+
# POST /logout
|
86
|
+
def destroy
|
87
|
+
logout_browserid
|
88
|
+
head :ok
|
89
|
+
end
|
90
|
+
|
91
|
+
TODO: write generator to create routes and session controller
|
92
|
+
|
93
|
+
### Layout Integration
|
94
|
+
|
95
|
+
The BrowserID javascript library needs to be loaded on your application pages.
|
96
|
+
There are two steps to accomplish this:
|
97
|
+
|
98
|
+
First, the coffeescript asset file needs to be loaded. In the
|
99
|
+
`app/assets/javascripts/application.js` manifest, add the following line:
|
100
|
+
|
101
|
+
//= require browserid
|
102
|
+
|
103
|
+
Second, the scripts need to be setup in your pages' `<head>` section. The
|
104
|
+
`setup_browserid` helper method takes care of this for you and gives a couple
|
105
|
+
of ways to control its behavior:
|
106
|
+
|
107
|
+
<!-- Perform basic BrowserID setup in the head section -->
|
108
|
+
<%= setup_browserid %>
|
109
|
+
|
110
|
+
<!-- Setup BrowserID with alert debugging -->
|
111
|
+
<%= setup_browserid debug: true %>
|
112
|
+
|
113
|
+
<!-- Setup BrowserID with a custom handler -->
|
114
|
+
<%= setup_browserid do %>
|
115
|
+
browserid.onLogin = function (data, status, xhr) {
|
116
|
+
// ...
|
117
|
+
}
|
118
|
+
<% end %>
|
119
|
+
|
120
|
+
Once that's accomplished, the app is ready to use BrowserID for authentication.
|
121
|
+
To add login and logout links to the site, use the `login_link` and
|
122
|
+
`logout_link` helpers. These accept optional link text and targets as parameters:
|
123
|
+
|
124
|
+
<%= login_link "Login with Persona" %>
|
125
|
+
|
126
|
+
<%= login_link "Login", auth_path %>
|
127
|
+
|
128
|
+
If the path is not provided, the link helpers will use `login_path` and
|
129
|
+
`logout_path` if they are available, otherwise the link targets will be `#`.
|
130
|
+
The coffeescript assets add on-click handlers to the links which trigger the
|
131
|
+
Persona code to request new assertions or destroy existing ones.
|
132
|
+
|
133
|
+
TODO: include Persona branding assets
|
134
|
+
|
135
|
+
## Contributing
|
136
|
+
|
137
|
+
1. Fork it
|
138
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
139
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
140
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
141
|
+
5. Create new Pull Request
|
@@ -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 result: #{status} #{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 #{err} - #{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 result: #{status} #{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 #{err} - #{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,41 @@
|
|
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
|
+
config.before_configuration do
|
12
|
+
BrowserIDConfig = Struct.new :user_model, :email_field, :session_variable, :verifier, :audience
|
13
|
+
|
14
|
+
config.browserid = BrowserIDConfig.new
|
15
|
+
config.browserid.user_model = 'User'
|
16
|
+
config.browserid.email_field = 'email'
|
17
|
+
config.browserid.session_variable = :browserid_email
|
18
|
+
config.browserid.verifier = :persona
|
19
|
+
# config.browserid.audience should only be set in production
|
20
|
+
end
|
21
|
+
|
22
|
+
initializer "browserid-rails.extend" do |app|
|
23
|
+
ActionController::Base.send :include, BrowserID::Rails::Base
|
24
|
+
ActionView::Base.send :include, BrowserID::Rails::Helpers
|
25
|
+
end
|
26
|
+
|
27
|
+
config.after_initialize do
|
28
|
+
cfg = config.browserid
|
29
|
+
|
30
|
+
# Replace type symbol with constructed verifier.
|
31
|
+
if cfg.verifier == :persona
|
32
|
+
cfg.verifier = BrowserID::Verifier::Persona.new
|
33
|
+
elsif cfg.verifier == :local
|
34
|
+
raise "Local BrowserID verification is not supported yet" # TODO
|
35
|
+
elsif !cfg.verifier.respond_to?(:verify)
|
36
|
+
raise "Unknown BrowserID verifier type #{cfg.verifier}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,130 @@
|
|
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_email, :current_user, :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
|
+
##### AUTHENTICATION METHODS #####
|
29
|
+
|
30
|
+
# Public: Sets the given email address as the currently-authenticated user.
|
31
|
+
# The address is saved in the client's session.
|
32
|
+
#
|
33
|
+
# email - The String email address to consider authenticated.
|
34
|
+
def login_browserid(email)
|
35
|
+
session[browserid_config.session_variable] = email
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Clears the saved email address for the currently-authenticated
|
39
|
+
# user. It is important to note that this does not remove the BrowserID
|
40
|
+
# assertion in the client's browser.
|
41
|
+
def logout_browserid
|
42
|
+
session[browserid_config.session_variable] = nil
|
43
|
+
end
|
44
|
+
|
45
|
+
# Public: Uses the configured verifier to check that a provided assertion
|
46
|
+
# is correct for the site audience.
|
47
|
+
#
|
48
|
+
# Returns the verified email, identity issuer, and audience on success.
|
49
|
+
# Raises an error with a failure message if the client was not
|
50
|
+
# successfully authenticated.
|
51
|
+
#
|
52
|
+
# Examples
|
53
|
+
#
|
54
|
+
# verify_browserid(assertion)
|
55
|
+
# # => "user@example.com", "persona.mozilla.com", "https://app.example.com:443"
|
56
|
+
#
|
57
|
+
def verify_browserid(assertion)
|
58
|
+
audience = browserid_config.audience
|
59
|
+
audience ||= "%s%s:%d" % [request.protocol, request.host, request.port]
|
60
|
+
browserid_config.verifier.verify(assertion, audience)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Public: Handles a POST-ed BrowserID assertion, responding appropriately
|
64
|
+
# to the request. If successful, this logs-in the authenticated email and
|
65
|
+
# returns an OK status. If unsuccessful, it returns FORBIDDEN and an
|
66
|
+
# error message in the response body.
|
67
|
+
#
|
68
|
+
# Returns nothing.
|
69
|
+
#
|
70
|
+
# Examples
|
71
|
+
#
|
72
|
+
# # POST /login
|
73
|
+
# def create
|
74
|
+
# respond_to_browserid
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
def respond_to_browserid
|
78
|
+
if params[:assertion].blank?
|
79
|
+
head :bad_request
|
80
|
+
else
|
81
|
+
email, issuer, audience = verify_browserid params[:assertion]
|
82
|
+
logger.info "Verified BrowserID assertion for #{email} issued by #{issuer} on #{audience}"
|
83
|
+
login_browserid email
|
84
|
+
head :ok
|
85
|
+
end
|
86
|
+
rescue StandardError => e
|
87
|
+
logger.warn "Failed to verify BrowserID assertion: #{e.message}"
|
88
|
+
render status: :forbidden, text: e.message
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
|
93
|
+
##### HELPER METHODS #####
|
94
|
+
|
95
|
+
# Public: Gets the email address of the currently-authenticated user.
|
96
|
+
#
|
97
|
+
# Returns the authenticated email address String.
|
98
|
+
def browserid_email
|
99
|
+
session[browserid_config.session_variable]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Public: Retrieves the user for the authenticated email address. This
|
103
|
+
# method uses the `browserid.user_model` and `browserid.email_field`
|
104
|
+
# config settings, which default to `User` and `email`.
|
105
|
+
#
|
106
|
+
# Returns the current authenticated user, or nil if no user exists.
|
107
|
+
def current_user
|
108
|
+
if browserid_email.nil?
|
109
|
+
nil
|
110
|
+
elsif @current_user
|
111
|
+
@current_user
|
112
|
+
else
|
113
|
+
config = browserid_config
|
114
|
+
user_model = config.user_model.constantize
|
115
|
+
find_method = "find_by_#{config.email_field}".intern
|
116
|
+
|
117
|
+
@current_user = user_model.send find_method, browserid_email
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Public: Determines whether the current client is authenticated as a
|
122
|
+
# registered User.
|
123
|
+
#
|
124
|
+
# Returns true if the client is authenticated and registered.
|
125
|
+
def authenticated?
|
126
|
+
!current_user.nil?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,62 @@
|
|
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.
|
11
|
+
# :logout_path - String giving the path to POST logout
|
12
|
+
# notifications to.
|
13
|
+
# :debug - Boolean determining whether the browserid
|
14
|
+
# javascript will refresh the page or show an
|
15
|
+
# alert dialog.
|
16
|
+
# block - An optional block which can be used to provide additional
|
17
|
+
# content to be rendered inside the browserid setup script tag.
|
18
|
+
#
|
19
|
+
# Examples
|
20
|
+
#
|
21
|
+
# <!-- Perform basic BrowserID setup in the head section -->
|
22
|
+
# <%= setup_browserid %>
|
23
|
+
#
|
24
|
+
# <!-- Setup BrowserID with alert debugging -->
|
25
|
+
# <%= setup_browserid debug: true %>
|
26
|
+
#
|
27
|
+
# <!-- Setup BrowserID with a custom handler -->
|
28
|
+
# <%= setup_browserid do %>
|
29
|
+
# browserid.onLogin = function (data, status, xhr) {
|
30
|
+
# // ...
|
31
|
+
# }
|
32
|
+
# <% end %>
|
33
|
+
#
|
34
|
+
def setup_browserid(options={}, &block)
|
35
|
+
content_for :browserid_setup, capture(&block) if block_given?
|
36
|
+
render 'layouts/browserid', options: options
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Renders a login link which will request a new authentication
|
40
|
+
# assertion from the BrowserID javascript code.
|
41
|
+
#
|
42
|
+
# text - String to use as link text (default: 'Login').
|
43
|
+
# path - String path to link to. If not provided, the `login_path` helper
|
44
|
+
# will be used if it exists. Otherwise, the link will be to '#'.
|
45
|
+
def login_link(text="Login", path=nil)
|
46
|
+
target = path || respond_to?(:login_path) && login_path || '#'
|
47
|
+
link_to text, target, class: :browserid_login
|
48
|
+
end
|
49
|
+
|
50
|
+
# Public: Renders a logout link which will clear the current BrowserID
|
51
|
+
# authentication status.
|
52
|
+
#
|
53
|
+
# text - String to use as link text (default: 'Logout').
|
54
|
+
# path - String path to link to. If not provided, the `logout_path` helper
|
55
|
+
# will be used if it exists. Otherwise, the link will be to '#'.
|
56
|
+
def logout_link(text="Logout", path=nil)
|
57
|
+
target = path || respond_to?(:logout_path) && logout_path || '#'
|
58
|
+
link_to text, target, class: :browserid_logout
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
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,76 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: browserid-rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.4.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Greg Look
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-12-31 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: railties
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.1'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '3.1'
|
30
|
+
description:
|
31
|
+
email:
|
32
|
+
- greg@mvxcvi.com
|
33
|
+
executables: []
|
34
|
+
extensions: []
|
35
|
+
extra_rdoc_files: []
|
36
|
+
files:
|
37
|
+
- app/views/layouts/_browserid.html.erb
|
38
|
+
- app/assets/javascripts/browserid.js.coffee
|
39
|
+
- lib/browserid/rails/helpers.rb
|
40
|
+
- lib/browserid/rails/version.rb
|
41
|
+
- lib/browserid/rails/base.rb
|
42
|
+
- lib/browserid/verifier/persona.rb
|
43
|
+
- lib/browserid-rails.rb
|
44
|
+
- LICENSE
|
45
|
+
- README.md
|
46
|
+
homepage: https://github.com/mvxcvi/browserid-rails
|
47
|
+
licenses: []
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
none: false
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
hash: -488449078399574217
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
hash: -488449078399574217
|
70
|
+
requirements: []
|
71
|
+
rubyforge_project:
|
72
|
+
rubygems_version: 1.8.24
|
73
|
+
signing_key:
|
74
|
+
specification_version: 3
|
75
|
+
summary: Rails authentication framework using Mozilla Persona and the BrowserID protocol.
|
76
|
+
test_files: []
|