browserid-provider 0.4.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/.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: []