browserid-provider 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ config
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in browserid-provider.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # A Rack BrowserID Provider
2
+
3
+ Become a Mozilla BrowserID Primary Identity Provider.
4
+
5
+ This is a Rack middleware for providing the BrowserID Primary Identity
6
+ service. I have so far tested this only with Ruby on Rails.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'browserid-provider'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ Or install it yourself as:
19
+
20
+ $ gem install browserid-provider
21
+
22
+ ## Usage
23
+
24
+ In you Rails app config/application.rb, add:
25
+
26
+ ```ruby
27
+ config.middleware.use BrowserID::Provider({:authentication_path => "/login" })
28
+ ```
29
+
30
+ The default setup relies on Warden to see which user is logged in. This
31
+ can easily be customized to fit any middleware function.
32
+
33
+ The available configuration options are the following:
34
+
35
+ ```ruby
36
+ #
37
+ # authentication_path Where to redirect users for login
38
+ # defaults to: "/users/sign_in" (Devise default)
39
+ #
40
+ # provision_path What HTTP path to deliver provisioning from
41
+ # defaults to: "/browserid/provision"
42
+ # certify_path What HTTP path to deliver certifying from
43
+ # defaults to: "/browserid/certify"
44
+ # whoami_path What HTTP path to serve user credentials at
45
+ # defaults to: "/browserid/whoami"
46
+ #
47
+ # whoami What function to call for the current user object (must respond to :email method)
48
+ # defaults to: "@env['warden'].user"
49
+ #
50
+ # private_key_path Where is the BrowserID OpenSSL private key located
51
+ # defaults to: "config/browserid_provider.pem"
52
+ #
53
+ # The "/.well-known/browserid" path is required from the BrowserID spec and used here.
54
+ #
55
+ # browserid_url Which BrowserID server to use, ca be one of the following:
56
+ # * dev.diresworb.org for development (default)
57
+ # * diresworb.org for beta
58
+ # * browserid.org for production
59
+ #
60
+ # server_name The domain name we are providing BrowserID for (default to example.org)
61
+ #
62
+ ```
63
+
64
+ The client side is JavaScript enabled. For Rails use:
65
+
66
+ ```erb
67
+ <%= browserid_authentication_tag %>
68
+ <!-- Enable BrowserID authentication API on the form #new_user -->
69
+ <%= enable_browserid_javascript_tag "new_user" %>
70
+ ```
71
+
72
+ In your login form, add a cancel button like this:
73
+
74
+ ```erb
75
+ <%= button_to_function "Cancel", "navigator.id.cancelAuthentication()" %>
76
+ ```
77
+
78
+ Without Rails view helpers (in any framework), you can do:
79
+
80
+ ```javascript
81
+ $('form#new_user').bind('ajax:success', function(data, status, xhr) { navigator.id.completeAuthentication() })
82
+ ```
83
+
84
+ ## Contributing
85
+
86
+ 1. Fork it
87
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
88
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
89
+ 4. Push to the branch (`git push origin my-new-feature`)
90
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,40 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <title>404 - BrowserID</title>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <meta name="description" content="">
8
+ <meta name="author" content="">
9
+
10
+ <!-- Le styles -->
11
+ <style>
12
+ <%= BrowserID::Template.css_styles %>
13
+ body {
14
+ padding-top: 60px; /* 60px to make the container go all the way to the bottom of the topbar */
15
+ }
16
+ .hero-unit img {
17
+ float: right;
18
+ }
19
+ </style>
20
+
21
+ <!-- Le HTML5 shim, for IE6-8 support of HTML5 elements -->
22
+ <!--[if lt IE 9]>
23
+ <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script>
24
+ <![endif]-->
25
+ </head>
26
+
27
+ <body>
28
+ <div class="hero-unit">
29
+ <h1>404</h1>
30
+ <p>No BrowserID content found here.
31
+ <img src="https://github.com/mozilla/browserid/raw/dev/resources/assets/browserID-366x72.png" alt="BrowserID" />
32
+ </p>
33
+ <p>
34
+ <a class="btn btn-primary btn-large">
35
+ Learn more
36
+ </a>
37
+ </p>
38
+ </div>
39
+ </body>
40
+ </html>
@@ -0,0 +1,373 @@
1
+ /*!
2
+ * Bootstrap v2.0.2
3
+ *
4
+ * Copyright 2012 Twitter, Inc
5
+ * Licensed under the Apache License v2.0
6
+ * http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
9
+ */
10
+ .clearfix {
11
+ *zoom: 1;
12
+ }
13
+ .clearfix:before,
14
+ .clearfix:after {
15
+ display: table;
16
+ content: "";
17
+ }
18
+ .clearfix:after {
19
+ clear: both;
20
+ }
21
+ .hide-text {
22
+ overflow: hidden;
23
+ text-indent: 100%;
24
+ white-space: nowrap;
25
+ }
26
+ .input-block-level {
27
+ display: block;
28
+ width: 100%;
29
+ min-height: 28px;
30
+ /* Make inputs at least the height of their button counterpart */
31
+
32
+ /* Makes inputs behave like true block-level elements */
33
+
34
+ -webkit-box-sizing: border-box;
35
+ -moz-box-sizing: border-box;
36
+ -ms-box-sizing: border-box;
37
+ box-sizing: border-box;
38
+ }
39
+ article,
40
+ aside,
41
+ details,
42
+ figcaption,
43
+ figure,
44
+ footer,
45
+ header,
46
+ hgroup,
47
+ nav,
48
+ section {
49
+ display: block;
50
+ }
51
+ audio,
52
+ canvas,
53
+ video {
54
+ display: inline-block;
55
+ *display: inline;
56
+ *zoom: 1;
57
+ }
58
+ audio:not([controls]) {
59
+ display: none;
60
+ }
61
+ html {
62
+ font-size: 100%;
63
+ -webkit-text-size-adjust: 100%;
64
+ -ms-text-size-adjust: 100%;
65
+ }
66
+ a:focus {
67
+ outline: thin dotted #333;
68
+ outline: 5px auto -webkit-focus-ring-color;
69
+ outline-offset: -2px;
70
+ }
71
+ a:hover,
72
+ a:active {
73
+ outline: 0;
74
+ }
75
+ sub,
76
+ sup {
77
+ position: relative;
78
+ font-size: 75%;
79
+ line-height: 0;
80
+ vertical-align: baseline;
81
+ }
82
+ sup {
83
+ top: -0.5em;
84
+ }
85
+ sub {
86
+ bottom: -0.25em;
87
+ }
88
+ img {
89
+ height: auto;
90
+ border: 0;
91
+ -ms-interpolation-mode: bicubic;
92
+ vertical-align: middle;
93
+ }
94
+ button,
95
+ input,
96
+ select,
97
+ textarea {
98
+ margin: 0;
99
+ font-size: 100%;
100
+ vertical-align: middle;
101
+ }
102
+ button,
103
+ input {
104
+ *overflow: visible;
105
+ line-height: normal;
106
+ }
107
+ button::-moz-focus-inner,
108
+ input::-moz-focus-inner {
109
+ padding: 0;
110
+ border: 0;
111
+ }
112
+ button,
113
+ input[type="button"],
114
+ input[type="reset"],
115
+ input[type="submit"] {
116
+ cursor: pointer;
117
+ -webkit-appearance: button;
118
+ }
119
+ input[type="search"] {
120
+ -webkit-appearance: textfield;
121
+ -webkit-box-sizing: content-box;
122
+ -moz-box-sizing: content-box;
123
+ box-sizing: content-box;
124
+ }
125
+ input[type="search"]::-webkit-search-decoration,
126
+ input[type="search"]::-webkit-search-cancel-button {
127
+ -webkit-appearance: none;
128
+ }
129
+ textarea {
130
+ overflow: auto;
131
+ vertical-align: top;
132
+ }
133
+ body {
134
+ margin: 0;
135
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
136
+ font-size: 13px;
137
+ line-height: 18px;
138
+ color: #333333;
139
+ background-color: #ffffff;
140
+ }
141
+ a {
142
+ color: #0088cc;
143
+ text-decoration: none;
144
+ }
145
+ a:hover {
146
+ color: #005580;
147
+ text-decoration: underline;
148
+ }
149
+ p {
150
+ margin: 0 0 9px;
151
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
152
+ font-size: 13px;
153
+ line-height: 18px;
154
+ }
155
+ p small {
156
+ font-size: 11px;
157
+ color: #999999;
158
+ }
159
+ .lead {
160
+ margin-bottom: 18px;
161
+ font-size: 20px;
162
+ font-weight: 200;
163
+ line-height: 27px;
164
+ }
165
+ h1,
166
+ h2,
167
+ h3,
168
+ h4,
169
+ h5,
170
+ h6 {
171
+ margin: 0;
172
+ font-family: inherit;
173
+ font-weight: bold;
174
+ color: inherit;
175
+ text-rendering: optimizelegibility;
176
+ }
177
+ h1 small,
178
+ h2 small,
179
+ h3 small,
180
+ h4 small,
181
+ h5 small,
182
+ h6 small {
183
+ font-weight: normal;
184
+ color: #999999;
185
+ }
186
+ h1 {
187
+ font-size: 30px;
188
+ line-height: 36px;
189
+ }
190
+ h1 small {
191
+ font-size: 18px;
192
+ }
193
+ h2 {
194
+ font-size: 24px;
195
+ line-height: 36px;
196
+ }
197
+ h2 small {
198
+ font-size: 18px;
199
+ }
200
+ h3 {
201
+ line-height: 27px;
202
+ font-size: 18px;
203
+ }
204
+ h3 small {
205
+ font-size: 14px;
206
+ }
207
+ h4,
208
+ h5,
209
+ h6 {
210
+ line-height: 18px;
211
+ }
212
+ h4 {
213
+ font-size: 14px;
214
+ }
215
+ h4 small {
216
+ font-size: 12px;
217
+ }
218
+ h5 {
219
+ font-size: 12px;
220
+ }
221
+ h6 {
222
+ font-size: 11px;
223
+ color: #999999;
224
+ text-transform: uppercase;
225
+ }
226
+ .page-header {
227
+ padding-bottom: 17px;
228
+ margin: 18px 0;
229
+ border-bottom: 1px solid #eeeeee;
230
+ }
231
+ .page-header h1 {
232
+ line-height: 1;
233
+ }
234
+ ul,
235
+ ol {
236
+ padding: 0;
237
+ margin: 0 0 9px 25px;
238
+ }
239
+ ul ul,
240
+ ul ol,
241
+ ol ol,
242
+ ol ul {
243
+ margin-bottom: 0;
244
+ }
245
+ ul {
246
+ list-style: disc;
247
+ }
248
+ ol {
249
+ list-style: decimal;
250
+ }
251
+ li {
252
+ line-height: 18px;
253
+ }
254
+ ul.unstyled,
255
+ ol.unstyled {
256
+ margin-left: 0;
257
+ list-style: none;
258
+ }
259
+ dl {
260
+ margin-bottom: 18px;
261
+ }
262
+ dt,
263
+ dd {
264
+ line-height: 18px;
265
+ }
266
+ dt {
267
+ font-weight: bold;
268
+ line-height: 17px;
269
+ }
270
+ dd {
271
+ margin-left: 9px;
272
+ }
273
+ .dl-horizontal dt {
274
+ float: left;
275
+ clear: left;
276
+ width: 120px;
277
+ text-align: right;
278
+ }
279
+ .dl-horizontal dd {
280
+ margin-left: 130px;
281
+ }
282
+ hr {
283
+ margin: 18px 0;
284
+ border: 0;
285
+ border-top: 1px solid #eeeeee;
286
+ border-bottom: 1px solid #ffffff;
287
+ }
288
+ strong {
289
+ font-weight: bold;
290
+ }
291
+ em {
292
+ font-style: italic;
293
+ }
294
+ .muted {
295
+ color: #999999;
296
+ }
297
+ abbr[title] {
298
+ border-bottom: 1px dotted #ddd;
299
+ cursor: help;
300
+ }
301
+ abbr.initialism {
302
+ font-size: 90%;
303
+ text-transform: uppercase;
304
+ }
305
+ blockquote {
306
+ padding: 0 0 0 15px;
307
+ margin: 0 0 18px;
308
+ border-left: 5px solid #eeeeee;
309
+ }
310
+ blockquote p {
311
+ margin-bottom: 0;
312
+ font-size: 16px;
313
+ font-weight: 300;
314
+ line-height: 22.5px;
315
+ }
316
+ blockquote small {
317
+ display: block;
318
+ line-height: 18px;
319
+ color: #999999;
320
+ }
321
+ blockquote small:before {
322
+ content: '\2014 \00A0';
323
+ }
324
+ blockquote.pull-right {
325
+ float: right;
326
+ padding-left: 0;
327
+ padding-right: 15px;
328
+ border-left: 0;
329
+ border-right: 5px solid #eeeeee;
330
+ }
331
+ blockquote.pull-right p,
332
+ blockquote.pull-right small {
333
+ text-align: right;
334
+ }
335
+ q:before,
336
+ q:after,
337
+ blockquote:before,
338
+ blockquote:after {
339
+ content: "";
340
+ }
341
+ address {
342
+ display: block;
343
+ margin-bottom: 18px;
344
+ line-height: 18px;
345
+ font-style: normal;
346
+ }
347
+ small {
348
+ font-size: 100%;
349
+ }
350
+ cite {
351
+ font-style: normal;
352
+ }
353
+ .hero-unit {
354
+ padding: 60px;
355
+ margin-bottom: 30px;
356
+ background-color: #eeeeee;
357
+ -webkit-border-radius: 6px;
358
+ -moz-border-radius: 6px;
359
+ border-radius: 6px;
360
+ }
361
+ .hero-unit h1 {
362
+ margin-bottom: 0;
363
+ font-size: 60px;
364
+ line-height: 1;
365
+ color: inherit;
366
+ letter-spacing: -1px;
367
+ }
368
+ .hero-unit p {
369
+ font-size: 18px;
370
+ font-weight: 200;
371
+ line-height: 27px;
372
+ color: inherit;
373
+ }
@@ -0,0 +1,72 @@
1
+ /*!
2
+ * Bootstrap v2.0.2
3
+ *
4
+ * Copyright 2012 Twitter, Inc
5
+ * Licensed under the Apache License v2.0
6
+ * http://www.apache.org/licenses/LICENSE-2.0
7
+ *
8
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
9
+ */
10
+ .clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";}
11
+ .clearfix:after{clear:both;}
12
+ .hide-text{overflow:hidden;text-indent:100%;white-space:nowrap;}
13
+ .input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;}
14
+ article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
15
+ audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
16
+ audio:not([controls]){display:none;}
17
+ html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
18
+ a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
19
+ a:hover,a:active{outline:0;}
20
+ sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
21
+ sup{top:-0.5em;}
22
+ sub{bottom:-0.25em;}
23
+ img{height:auto;border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;}
24
+ button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}
25
+ button,input{*overflow:visible;line-height:normal;}
26
+ button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}
27
+ button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
28
+ input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
29
+ input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
30
+ textarea{overflow:auto;vertical-align:top;}
31
+ body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-color:#ffffff;}
32
+ a{color:#0088cc;text-decoration:none;}
33
+ a:hover{color:#005580;text-decoration:underline;}
34
+ p{margin:0 0 9px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;}p small{font-size:11px;color:#999999;}
35
+ .lead{margin-bottom:18px;font-size:20px;font-weight:200;line-height:27px;}
36
+ h1,h2,h3,h4,h5,h6{margin:0;font-family:inherit;font-weight:bold;color:inherit;text-rendering:optimizelegibility;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;color:#999999;}
37
+ h1{font-size:30px;line-height:36px;}h1 small{font-size:18px;}
38
+ h2{font-size:24px;line-height:36px;}h2 small{font-size:18px;}
39
+ h3{line-height:27px;font-size:18px;}h3 small{font-size:14px;}
40
+ h4,h5,h6{line-height:18px;}
41
+ h4{font-size:14px;}h4 small{font-size:12px;}
42
+ h5{font-size:12px;}
43
+ h6{font-size:11px;color:#999999;text-transform:uppercase;}
44
+ .page-header{padding-bottom:17px;margin:18px 0;border-bottom:1px solid #eeeeee;}
45
+ .page-header h1{line-height:1;}
46
+ ul,ol{padding:0;margin:0 0 9px 25px;}
47
+ ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
48
+ ul{list-style:disc;}
49
+ ol{list-style:decimal;}
50
+ li{line-height:18px;}
51
+ ul.unstyled,ol.unstyled{margin-left:0;list-style:none;}
52
+ dl{margin-bottom:18px;}
53
+ dt,dd{line-height:18px;}
54
+ dt{font-weight:bold;line-height:17px;}
55
+ dd{margin-left:9px;}
56
+ .dl-horizontal dt{float:left;clear:left;width:120px;text-align:right;}
57
+ .dl-horizontal dd{margin-left:130px;}
58
+ hr{margin:18px 0;border:0;border-top:1px solid #eeeeee;border-bottom:1px solid #ffffff;}
59
+ strong{font-weight:bold;}
60
+ em{font-style:italic;}
61
+ .muted{color:#999999;}
62
+ abbr[title]{border-bottom:1px dotted #ddd;cursor:help;}
63
+ abbr.initialism{font-size:90%;text-transform:uppercase;}
64
+ blockquote{padding:0 0 0 15px;margin:0 0 18px;border-left:5px solid #eeeeee;}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:22.5px;}
65
+ blockquote small{display:block;line-height:18px;color:#999999;}blockquote small:before{content:'\2014 \00A0';}
66
+ blockquote.pull-right{float:right;padding-left:0;padding-right:15px;border-left:0;border-right:5px solid #eeeeee;}blockquote.pull-right p,blockquote.pull-right small{text-align:right;}
67
+ q:before,q:after,blockquote:before,blockquote:after{content:"";}
68
+ address{display:block;margin-bottom:18px;line-height:18px;font-style:normal;}
69
+ small{font-size:100%;}
70
+ cite{font-style:normal;}
71
+ .hero-unit{padding:60px;margin-bottom:30px;background-color:#eeeeee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;color:inherit;letter-spacing:-1px;}
72
+ .hero-unit p{font-size:18px;font-weight:200;line-height:27px;color:inherit;}
@@ -0,0 +1,53 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
5
+ <script type="text/javascript" src="https://<%= @env[:browserid_url] %>/provisioning_api.js"></script>
6
+ <script type="text/javascript" src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
7
+ <script type="text/javascript">
8
+ // an alias
9
+ var fail = navigator.id.raiseProvisioningFailure;
10
+
11
+ // begin provisioning! This both gives us indicated to browserid that we're
12
+ // a well formed provisioning page and gives us the parameters of the provisioning
13
+ navigator.id.beginProvisioning(function(email, cert_duration) {
14
+ // now we have the email address that wishes to be provisioned!
15
+ // is he authenticated to underpin.no?
16
+ $.get('<%= @env[:whoami_path] %>')
17
+ .success(function(r) {
18
+ email = email.replace('@<%= @env[:server_name] %>', '').toLowerCase();
19
+ if (email != r.user) {
20
+ return fail('user is not authenticated as target user');
21
+ }
22
+
23
+ // Awesome! The user is authenticated as who we want to provision. let's
24
+ // generate a keypair
25
+ navigator.id.genKeyPair(function(pubkey) {
26
+ // finally, once we have a public key from the browser, we'll certify it, and
27
+ // go pass it back
28
+ $.ajax({
29
+ url: '<%= @env[:certify_path] %>',
30
+ data: JSON.stringify({
31
+ pubkey: pubkey,
32
+ duration: cert_duration
33
+ }),
34
+ type: 'POST',
35
+ headers: { "Content-Type": 'application/json' },
36
+ dataType: 'json',
37
+ success: function(r) {
38
+ // all done! woo!
39
+ navigator.id.registerCertificate(r.cert);
40
+ },
41
+ error: function(r) {
42
+ fail("couldn't certify key");
43
+ }
44
+ });
45
+ });
46
+ })
47
+ .error(function() {
48
+ fail('user is not authenticated');
49
+ });
50
+ });
51
+ </script>
52
+ </head>
53
+ </html>
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "browserid-provider/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "browserid-provider"
7
+ s.version = BrowserID::VERSION
8
+ s.authors = ["ringe"]
9
+ s.email = ["runar@rin.no"]
10
+ s.homepage = "https://github.com/ringe/browserid-provider"
11
+ s.summary = %q{Rack-based Mozilla BrowserID Provider}
12
+ s.description = %q{With the BrowserID provider you enable your users to authenticate themselves across the web using a single authority.}
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ s.add_dependency "json-jwt"
20
+ s.add_development_dependency "rack-test"
21
+ s.add_development_dependency "mocha"
22
+ s.add_development_dependency "warden"
23
+ end
@@ -0,0 +1,76 @@
1
+ module BrowserID
2
+ #
3
+ # authentication_path Where to redirect users for login
4
+ # defaults to: "/users/sign_in" (Devise default)
5
+ #
6
+ # provision_path What HTTP path to deliver provisioning from
7
+ # defaults to: "/browserid/provision"
8
+ # certify_path What HTTP path to deliver certifying from
9
+ # defaults to: "/browserid/certify"
10
+ # whoami_path What HTTP path to serve user credentials at
11
+ # defaults to: "/browserid/whoami"
12
+ #
13
+ # whoami What function to call for the current user object (must respond to :email method)
14
+ # defaults to: "@env['warden'].user"
15
+ #
16
+ # private_key_path Where is the BrowserID OpenSSL private key located
17
+ # defaults to: "config/browserid_provider.pem"
18
+ #
19
+ # The "/.well-known/browserid" path is required from the BrowserID spec and used here.
20
+ #
21
+ # browserid_url Which BrowserID server to use, ca be one of the following:
22
+ # * dev.diresworb.org for development (default)
23
+ # * diresworb.org for beta
24
+ # * browserid.org for production
25
+ #
26
+ # server_name The domain name we are providing BrowserID for (default to example.org)
27
+ #
28
+ class Config < Hash
29
+ # Creates an accessor that simply sets and reads a key in the hash:
30
+ #
31
+ # class Config < Hash
32
+ # hash_accessor :login_path
33
+ # end
34
+ #
35
+ # config = Config.new
36
+ # config.login_path = "/users/sign_in"
37
+ # config[:login_path] #=> "/users/sign_in"
38
+ #
39
+ # config[:login_path] = "/login"
40
+ # config.login_path #=> "/login"
41
+ #
42
+ # Thanks to Warden. :)
43
+ def self.hash_accessor(*names) #:nodoc:
44
+ names.each do |name|
45
+ class_eval <<-METHOD, __FILE__, __LINE__ + 1
46
+ def #{name}
47
+ self[:#{name}]
48
+ end
49
+
50
+ def #{name}=(value)
51
+ self[:#{name}] = value
52
+ end
53
+ METHOD
54
+ end
55
+ end
56
+
57
+ hash_accessor :login_path, :provision_path, :whoami, :whoami_path, :certify_path, :private_key_path, :browserid_url, :server_name
58
+
59
+ def initialize(other={})
60
+ merge!(other)
61
+ self[:login_path] ||= "/users/sign_in"
62
+ self[:provision_path] ||= "/browserid/provision"
63
+ self[:certify_path] ||= "/browserid/certify"
64
+ self[:whoami_path] ||= "/browserid/whoami"
65
+ self[:whoami] ||= "@env['warden'].user"
66
+ self[:private_key_path] ||= "config/browserid_provider.pem"
67
+ self[:browserid_url] ||= "dev.diresworb.org"
68
+ self[:server_name] ||= "example.org"
69
+ end
70
+
71
+ def urls
72
+ [ self[:provision_path], self[:certify_path], self[:whoami_path], "/.well-known/browserid" ]
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,6 @@
1
+ module BrowserID
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,44 @@
1
+ require "openssl"
2
+ require "json/jwt"
3
+
4
+ module BrowserID
5
+ class Identity
6
+ attr_accessor :config
7
+
8
+ # == Options ==
9
+ # :key_path where to store the OpenSSL private key
10
+ def initialize(options = {})
11
+ @config = BrowserID::Config.new(options)
12
+
13
+ keypath = @config.private_key_path
14
+
15
+ if File.exists?(keypath)
16
+ File.open(keypath) {|f| @privkey = OpenSSL::PKey::RSA.new(f.read) }
17
+ @pubkey = @privkey.public_key
18
+ else
19
+ require 'fileutils'
20
+ FileUtils.mkdir_p keypath.sub(File.basename(keypath),'')
21
+ @privkey = OpenSSL::PKey::RSA.new(2048)
22
+ @pubkey = @privkey.public_key
23
+ File.open(keypath, "w") {|f| f.write(@privkey) }
24
+ end
25
+ end
26
+
27
+ def public_key
28
+ @pubkey
29
+ end
30
+
31
+ def private_key
32
+ @privkey
33
+ end
34
+
35
+ # Return BrowserID Identity JSON
36
+ def to_json
37
+ {
38
+ "public-key" => { "algorithm"=> "RS", "n" => public_key.n.to_s, "e" => public_key.e.to_s },
39
+ "authentication" => @config.login_path,
40
+ "provisioning" => @config.provision_path
41
+ }.to_json
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,111 @@
1
+ module BrowserID
2
+ # The BrowserID Provider Rack App
3
+ #
4
+ # Default paths are:
5
+ # GET /users/sign_in
6
+ # GET /browserid/provision
7
+ # GET /browserid/whoami
8
+ # POST /browserid/certify
9
+ class Provider
10
+ attr_accessor :config, :env, :req, :identity
11
+
12
+ def initialize(app = nil, options = {})
13
+ @app, @config = app, BrowserID::Config.new(options)
14
+ @identity = BrowserID::Identity.new
15
+ end
16
+
17
+ # Rack enabled!
18
+ def call(env)
19
+ @env, @path = env, env["PATH_INFO"], @req = Rack::Request.new(env)
20
+ env['browserid'] = @config
21
+
22
+ # Return Not found or send call back to middleware stack unless the URL is captured here
23
+ return (@app ? @app.call(env) : not_found) unless @config.urls.include? @path
24
+
25
+ case @path
26
+ when "/.well-known/browserid"
27
+ @req.get? ? well_known_browserid : not_found
28
+ when config.whoami_path
29
+ @req.get? ? whoami : not_found
30
+ when config.provision_path
31
+ @req.get? ? provision : not_found
32
+ when config.certify_path
33
+ @req.post? ? certify : not_found
34
+ else not_found
35
+ end
36
+ end
37
+
38
+ private
39
+ def well_known_browserid
40
+ [ 200, {"Content-Type" => "application/json"}, [@identity.to_json] ]
41
+ end
42
+
43
+ def whoami
44
+ email = current_user_email
45
+ [ 200, {"Content-Type" => "application/json"}, [{ user: email ? email.sub(/@.*/,'') : nil }.to_json] ]
46
+ end
47
+
48
+ #
49
+ # The certify function will be called by BrowserID with a public key and duration as parameters. The
50
+ # request is a POST with JSON data that looks like this:
51
+ #
52
+ # {
53
+ # "pubkey" :
54
+ # {
55
+ # "algorithm":"DS",
56
+ # "y":"62b0ea6936a7ab30c95d8ffbbc77438a342faed99b6fc643a58f28d9ed2017177354f9f1d1d7e6b9e1c543780c3517953a124e66bc409fcaaa671d87a39cf897b32f47aaaffb7a3d297b89f9e116870a2182e2b2f84d68a7bc21a3f7934727e45e50a083e71a965d0cc320062598e407463f0c31cc2c20ed74d9bda98b21c902",
57
+ # "p":"ff600483db6abfc5b45eab78594b3533d550d9f1bf2a992a7a8daa6dc34f8045ad4e6e0c429d334eeeaaefd7e23d4810be00e4cc1492cba325ba81ff2d5a5b305a8d17eb3bf4a06a349d392e00d329744a5179380344e82a18c47933438f891e22aeef812d69c8f75e326cb70ea000c3f776dfdbd604638c2ef717fc26d02e17",
58
+ # "q":"e21e04f911d1ed7991008ecaab3bf775984309c3",
59
+ # "g":"c52a4a0ff3b7e61fdf1867ce84138369a6154f4afa92966e3c827e25cfa6cf508b90e5de419e1337e07a2e9e2a3cd5dea704d175f8ebf6af397d69e110b96afb17c7a03259329e4829b0d03bbc7896b15b4ade53e130858cc34d96269aa89041f409136c7242a38895c9d5bccad4f389af1d7a4bd1398bd072dffa896233397a"
60
+ # },
61
+ # "duration":3600
62
+ # }
63
+ #
64
+ # We're going to certify that public key for the currently logged in user.
65
+ #
66
+ def certify
67
+ email = current_user_email
68
+ return err "No user is logged in." unless email
69
+
70
+ # Get params from Rails' ActionDispatch or from Rack Request
71
+ params = env["action_dispatch.request.request_parameters"] ? env["action_dispatch.request.request_parameters"] : @req.params
72
+ return err "Missing a required parameter (duration, pubkey)" if params.keys.sort != ["duration", "pubkey"]
73
+
74
+ expiration = (Time.now.strftime("%s").to_i + params["duration"].to_i) * 1000
75
+ issue = { "iss" => @config.server_name,
76
+ "exp" => expiration,
77
+ "public-key" => params["pubkey"],
78
+ "principal" => { "email"=> email }
79
+ }
80
+ jwt = JSON::JWT.new(issue)
81
+ jws = jwt.sign(@identity.private_key, :RS256)
82
+
83
+ return [ 200, {"Content-Type" => "application/json"}, [{ "cert" => jws.to_s }.to_json] ]
84
+ end
85
+
86
+ # Something went wrong.
87
+ def err(message)
88
+ [ 403, {"Content-Type" => "text/plain"}, [message] ]
89
+ end
90
+
91
+ # Return the provision iframe content.
92
+ def provision
93
+ [200, {"Content-Type" => "text/html"}, BrowserID::Template.render("provision", @config)]
94
+ end
95
+
96
+ # This middleware doesn't find what you are looking for.
97
+ def not_found
98
+ [404, {"Content-Type" => "text/html"}, BrowserID::Template.render("404", @env)]
99
+ end
100
+
101
+ # Return the email of the user logged in currently, or nil
102
+ def current_user_email
103
+ begin
104
+ current_user = eval config.whoami
105
+ current_user ? current_user.email : nil
106
+ rescue NoMethodError
107
+ raise NoMethodError, "The function provided in BrowserID::Config.whoami doesn't exist."
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,8 @@
1
+ require 'browserid-provider/view_helpers'
2
+ module BrowserId
3
+ class Railtie < Rails::Railtie
4
+ initializer "browser_id.view_helpers" do
5
+ ActionView::Base.send :include, ViewHelpers
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,24 @@
1
+ require 'erb'
2
+ module BrowserID
3
+ class Template
4
+ PATH = File.expand_path(File.join(File.dirname(__FILE__), "../..", "app", "assets", "browserid"))
5
+
6
+ def initialize(env)
7
+ @env = env
8
+ end
9
+
10
+ def get_binding
11
+ binding
12
+ end
13
+
14
+ def self.render(template, env)
15
+ rhtml = ERB.new File.read(PATH + "/" + template + ".html.erb")
16
+ view = BrowserID::Template.new(env)
17
+ [rhtml.result(view.get_binding)]
18
+ end
19
+
20
+ def self.css_styles
21
+ File.read(PATH + "/bootstrap.css")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module BrowserID
2
+ VERSION = "0.4.3"
3
+ end
@@ -0,0 +1,22 @@
1
+ module BrowserId
2
+ module ViewHelpers
3
+
4
+ # BrowserID JavaScript tags:
5
+ # - The official BrowserID include.js
6
+ # - The Devise enabled login and assertion reponse
7
+ def browserid_authentication_tag
8
+ javascript_include_tag(browserid_authentication_api_js_url)
9
+ end
10
+
11
+ # JavaScript enable BrowserID authentication for the form with the given #id
12
+ def enable_browserid_javascript_tag(id)
13
+ raw "<script type='text/javascript'>$('form##{id}').bind('ajax:success', function(data, status, xhr) { navigator.id.completeAuthentication() })</script>"
14
+ end
15
+
16
+ # The URL to the BrowserID official JavaScript
17
+ def browserid_authentication_api_js_url
18
+ "https://#{ request.env['browserid'][:browserid_url] }/authentication_api.js"
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ require "browserid-provider/version"
2
+ require "browserid-provider/config"
3
+ require "browserid-provider/identity"
4
+ require "browserid-provider/provider"
5
+ require "browserid-provider/template"
6
+
7
+ if defined?(Rails)
8
+ require "browserid-provider/engine"
9
+ require "browserid-provider/railtie"
10
+ end
@@ -0,0 +1,81 @@
1
+ require "test/unit"
2
+ require "json"
3
+ require "rack"
4
+ require "rack/test"
5
+ require "mocha"
6
+ require "browserid-provider"
7
+ require "ruby-debug"
8
+ require "warden"
9
+
10
+ class MyTest < Test::Unit::TestCase
11
+ include Rack::Test::Methods
12
+ include Warden::Test::Helpers
13
+ attr_accessor :thisapp
14
+
15
+ def app
16
+ @thisapp = Rack::Builder.new do
17
+ use Rack::CommonLogger
18
+ use Rack::Session::Cookie
19
+ use Warden::Manager do |manager|
20
+ manager.default_strategies :basic
21
+ end
22
+
23
+ run BrowserID::Provider.new
24
+ end
25
+ end
26
+
27
+ def test_get_root
28
+ get "/"
29
+ assert last_response.status == 404, "BrowserID Provider should not respond to root"
30
+ end
31
+
32
+ def test_get_well_known_browserid
33
+ get "/.well-known/browserid"
34
+
35
+ assert last_response.ok?
36
+ assert_json_response
37
+
38
+ # Test the JSON output
39
+ json = JSON.parse(last_response.body)
40
+ assert json.keys == ["public-key", "authentication", "provisioning"], "Malformed JSON response, see https://eyedee.me/.well-known/browserid for example data"
41
+ assert json["public-key"].keys == ["algorithm","n","e"], "Invalid public key provided, see https://wiki.mozilla.org/Identity/BrowserID"
42
+ end
43
+
44
+ def test_get_whoami
45
+ fake_user "mormor@example.org"
46
+ get "/browserid/whoami"
47
+ assert last_response.ok?
48
+ assert_json_response
49
+ assert last_response.body == '{"user":"mormor"}', "The whoami_path should return JSON with user name only, without domain"
50
+ end
51
+
52
+ def test_get_provision
53
+ get "/browserid/provision"
54
+ assert last_response.ok?
55
+ assert last_response.body.include?("https://dev.diresworb.org/provisioning_api.js"), "The default provisions_api.js must be provided, see https://developer.mozilla.org/en/BrowserID/Primary/Developer_tips"
56
+ end
57
+
58
+ def test_post_certify
59
+ get "/browserid/certify"
60
+ assert last_response.status == 404, "Should only allow POST to /browserid/certify"
61
+
62
+ fake_user "mormor@example.org"
63
+ post "/browserid/certify", params = { "duration" => 2500, "pubkey" => "aasdcasd" }
64
+ assert_json_response
65
+ assert last_response.body =~ /\{"cert":.*\}/, "The certify_path should return JSON with a signed certificate"
66
+ end
67
+
68
+ private
69
+ def fake_user(email)
70
+ Warden.on_next_request do |proxy|
71
+ u = mock()
72
+ u.stubs(:email).returns(email).at_least_once
73
+ proxy.set_user(u)
74
+ end
75
+ end
76
+
77
+ def assert_json_response
78
+ assert last_response.content_type == "application/json", "Content type was #{last_response.content_type} but must be application/json"
79
+ end
80
+ end
81
+
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: browserid-provider
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.4.3
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - ringe
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-20 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json-jwt
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
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: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rack-test
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ - !ruby/object:Gem::Dependency
47
+ name: mocha
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: warden
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ description: With the BrowserID provider you enable your users to authenticate themselves
79
+ across the web using a single authority.
80
+ email:
81
+ - runar@rin.no
82
+ executables: []
83
+ extensions: []
84
+ extra_rdoc_files: []
85
+ files:
86
+ - .gitignore
87
+ - Gemfile
88
+ - README.md
89
+ - Rakefile
90
+ - app/assets/browserid/404.html.erb
91
+ - app/assets/browserid/bootstrap.css
92
+ - app/assets/browserid/bootstrap.min.css
93
+ - app/assets/browserid/provision.html.erb
94
+ - browserid-provider.gemspec
95
+ - lib/browserid-provider.rb
96
+ - lib/browserid-provider/config.rb
97
+ - lib/browserid-provider/engine.rb
98
+ - lib/browserid-provider/identity.rb
99
+ - lib/browserid-provider/provider.rb
100
+ - lib/browserid-provider/railtie.rb
101
+ - lib/browserid-provider/template.rb
102
+ - lib/browserid-provider/version.rb
103
+ - lib/browserid-provider/view_helpers.rb
104
+ - test/browserid-provider.rb
105
+ homepage: https://github.com/ringe/browserid-provider
106
+ licenses: []
107
+ post_install_message:
108
+ rdoc_options: []
109
+ require_paths:
110
+ - lib
111
+ required_ruby_version: !ruby/object:Gem::Requirement
112
+ none: false
113
+ requirements:
114
+ - - ! '>='
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ required_rubygems_version: !ruby/object:Gem::Requirement
118
+ none: false
119
+ requirements:
120
+ - - ! '>='
121
+ - !ruby/object:Gem::Version
122
+ version: '0'
123
+ requirements: []
124
+ rubyforge_project:
125
+ rubygems_version: 1.8.22
126
+ signing_key:
127
+ specification_version: 3
128
+ summary: Rack-based Mozilla BrowserID Provider
129
+ test_files: []