sinatra-canvas_auth 0.0.3 → 0.1.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 18c5767aed8a836503fa4c9880faa99e5e3b21c1
4
- data.tar.gz: 675e5cbbd76fa389ebd9b63580c5a25454be275d
3
+ metadata.gz: 829a0ea15cee0cbe68d6e29af2ffb52a2f839903
4
+ data.tar.gz: 651e5961b52eba8b9423c6acecd0b2f34ecaa0fc
5
5
  SHA512:
6
- metadata.gz: c002fbf02f0f41127c26aa81d2d7d1cf438ea152e55ec5e8d67d0d103b26aaad69e213d9006b95a1c227e15554e8a6493a031c1a7471f6e269608c3ec914f806
7
- data.tar.gz: c1132a29cc37e8a34f0daada820b865629de65f6af4098883852e945737eb2e23dae937c609949b0f34eccbfc8741f3651f3bb747d4e10bb9af3fc71ecaaa15c
6
+ metadata.gz: f92f0c1a68083d4b58e907b0d0d811037619223e29fbfb40c6c684e005303803b1058552a30b0e8437f83359412c45e5222aa464b2b72e2a008318660d737887
7
+ data.tar.gz: 4d4635ca2e3c66edd45d06dc8cfa211f9f8909ad8c9c58781ebb6054bb707c703edc66a2361aa46a170c7225757b5b007c87730dba60981c14f103b1eb439b3d
data/README.md CHANGED
@@ -105,12 +105,19 @@ CanvasAuth requires a baseline configuration to function, as Canvas API settings
105
105
  Default: [/.*/]
106
106
  To only require authentication for certain routes, they may be explicitly specified here with either strings or regular expression. By default, all app routes will require authentication.
107
107
  ```ruby
108
- set :auth_paths, ['/admin', /^\/courses\/(\d)+$]
108
+ set :auth_paths, ['/admin', /^\/courses\/(\d)+$/]
109
109
  ```
110
110
 
111
111
  Alternative syntax:
112
112
  ```ruby
113
- authenticate '/admin', /^\/courses\/(\d)+$
113
+ authenticate '/admin', /^\/courses\/(\d)+$/
114
+ ```
115
+
116
+ * **public_paths** (Array)
117
+ Default: []
118
+ The inverse of auth_paths, routes matching strings or regexps in this array will not require authentication
119
+ ```ruby
120
+ set :public_paths, ['/homepage', /^\/assets\/.+$/]
114
121
  ```
115
122
 
116
123
  * **unauthorized_redirect** (String)
@@ -126,6 +133,13 @@ CanvasAuth requires a baseline configuration to function, as Canvas API settings
126
133
  ```ruby
127
134
  set :logout_redirect, '/goodbye'
128
135
  ```
136
+
137
+ * **failure_redirect** (String)
138
+ Default: "/login-failure"
139
+ If the user declines to grant the app access to their Canvas account, or the API request for a Canvas token raises an unexpected error, the user will be redirected to this path.
140
+ ```ruby
141
+ set :error_path, '/auth-error'
142
+ ```
129
143
   
130
144
 
131
145
  #### Callbacks
@@ -164,9 +178,10 @@ The following are optional hooks called by CanvasAuth which allow you to customi
164
178
  * GET /canvas-auth-logout
165
179
  * POST /canvas-auth-token
166
180
 
167
- * The following routes are also defined by CanvasAuth, but only as placeholders that may (and should) be overridden by your application. They do not include any functionality and serve only as landing pages to prevent 404ing on the default redirects.
181
+ * The following routes are also defined by CanvasAuth, and may be overridden by your application, should you wish to replace the default view/behavior provided:
168
182
  * GET /unauthorized
169
183
  * GET /logged-out
184
+ * GET /login-failure
170
185
 
171
186
  * All routes defined by CanvasAuth are permanently exempt from the requiring authentication, to avoid redirect loops.
172
187
  ## Contributing
@@ -0,0 +1,89 @@
1
+ <% params ||= {} %>
2
+
3
+ <!DOCTYPE html>
4
+ <html>
5
+ <head>
6
+ <style>
7
+ #box-content {
8
+ background-color: #FFF;
9
+ padding: 24px;
10
+ }
11
+
12
+ #modal-box {
13
+ border-radius: 3px;
14
+ box-shadow: 0 1px 4px 1px rgba(0,0,0,0.4);
15
+ box-sizing: border-box;
16
+ margin: 36px auto 0;
17
+ width: 696px;
18
+ }
19
+
20
+ .btn {
21
+ background-color: #CFB87C;
22
+ color: #2D3B45;
23
+ border: 1px solid;
24
+ border-color: #C7CDD1;
25
+ border-radius: 3px;
26
+ padding: 8px 14px;
27
+ font-size: 14px;
28
+ font-size: .875rem;
29
+ line-height: 20px;
30
+ text-align: center;
31
+ cursor: pointer;
32
+ font-weight: 300;
33
+ }
34
+
35
+ .btn:hover {
36
+ background-color: #c8ae69;
37
+ }
38
+
39
+ body {
40
+ background-color: #2e3c46;
41
+ font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
42
+ font-weight: 300;
43
+ }
44
+
45
+ form {
46
+ margin-top: 30px;
47
+ }
48
+
49
+ header {
50
+ background-color: #34444f;
51
+ padding: 18px;
52
+ }
53
+
54
+ h2 {
55
+ margin-top: 0;
56
+ font-weight: 300;
57
+ }
58
+
59
+ img {
60
+ width: 140px;
61
+ height: 57px;
62
+ }
63
+ </style>
64
+ </head>
65
+
66
+ <body>
67
+ <div id="modal-box">
68
+ <header>
69
+ <img alt="Canvas by Instructure" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARgAAAByCAYAAACIls4EAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMDY3IDc5LjE1Nzc0NywgMjAxNS8wMy8zMC0yMzo0MDo0MiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTUgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODIxNkEzODM0OUI4MTFFNUIwMTFCREYzMTYyNDQwM0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODIxNkEzODQ0OUI4MTFFNUIwMTFCREYzMTYyNDQwM0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4MjE2QTM4MTQ5QjgxMUU1QjAxMUJERjMxNjI0NDAzRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4MjE2QTM4MjQ5QjgxMUU1QjAxMUJERjMxNjI0NDAzRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pruwz2EAABbFSURBVHja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" />
70
+ </header>
71
+
72
+ <div id="box-content">
73
+ <h2>
74
+ <%= header %>
75
+ </h2>
76
+
77
+ <p>
78
+ <%= message %>
79
+ </p>
80
+
81
+ <% if login_url(params['state']) %>
82
+ <form method='GET' action='<%= login_url(params['state']) %>'>
83
+ <input class='btn' type='submit' value='Return to login'/>
84
+ </form>
85
+ <% end %>
86
+ </div>
87
+ </div>
88
+ </body>
89
+ </html>
@@ -1,18 +1,23 @@
1
1
  require 'sinatra'
2
2
  require 'rest-client'
3
+ require 'securerandom'
3
4
 
4
5
  module Sinatra
5
6
  module CanvasAuth
6
7
 
8
+ class StateError < ::StandardError; end
9
+
7
10
  DEFAULT_SETTINGS = {
8
11
  :auth_paths => [/.*/],
9
12
  :canvas_url => nil,
10
13
  :client_id => nil,
11
14
  :client_secret => nil,
15
+ :failure_redirect => '/login-failure',
12
16
  :login_path => '/canvas-auth-login',
13
17
  :token_path => '/canvas-auth-token',
14
18
  :logout_path => '/canvas-auth-logout',
15
19
  :logout_redirect => '/logged-out',
20
+ :public_paths => [],
16
21
  :unauthorized_redirect => '/unauthorized'
17
22
  }.freeze
18
23
 
@@ -24,14 +29,35 @@ module Sinatra
24
29
  def self.registered(app)
25
30
  self.merge_defaults(app)
26
31
 
32
+ app.helpers do
33
+ def login_url(state = nil)
34
+ return false if request.nil?
35
+ path_elements = [request.env['SCRIPT_NAME'], settings.login_path]
36
+ path_elements << state if state
37
+ File.join(path_elements)
38
+ end
39
+
40
+ def render_view(header='', message='')
41
+ render(:erb, :canvas_auth, {
42
+ :views => File.expand_path(File.dirname(__FILE__)),
43
+ :locals => {
44
+ :header => header,
45
+ :message => message
46
+ }
47
+ })
48
+ end
49
+ end
50
+
27
51
  app.get app.login_path do
28
- params['state'] ||= request.env['SCRIPT_NAME']
52
+ session['oauth_redirect'] ||= request.env['SCRIPT_NAME']
53
+ session['oauth_state'] = SecureRandom.urlsafe_base64(24)
54
+
29
55
  redirect_uri = "#{request.scheme}://#{request.host_with_port}" \
30
- "#{request.env['SCRIPT_NAME']}#{app.token_path}"
56
+ "#{request.env['SCRIPT_NAME']}#{settings.token_path}"
31
57
 
32
- redirect_params = "client_id=#{app.client_id}&" \
58
+ redirect_params = "client_id=#{settings.client_id}&" \
33
59
  "response_type=code&" \
34
- "state=#{CGI.escape(params['state'])}&" \
60
+ "state=#{session['oauth_state']}&" \
35
61
  "redirect_uri=#{CGI.escape(redirect_uri)}"
36
62
 
37
63
  ['scope', 'purpose', 'force_login', 'unique_id'].each do |optional_param|
@@ -40,7 +66,7 @@ module Sinatra
40
66
  end
41
67
  end
42
68
 
43
- redirect "#{app.canvas_url}/login/oauth2/auth?#{redirect_params}"
69
+ redirect "#{settings.canvas_url}/login/oauth2/auth?#{redirect_params}"
44
70
  end
45
71
 
46
72
  app.get app.token_path do
@@ -50,14 +76,20 @@ module Sinatra
50
76
  :client_secret => settings.client_secret
51
77
  }
52
78
 
53
- response = RestClient.post("#{settings.canvas_url}/login/oauth2/token", payload)
79
+ begin
80
+ CanvasAuth.verify_oauth_state(session, params)
81
+ response = RestClient.post("#{settings.canvas_url}/login/oauth2/token", payload)
82
+ rescue RestClient::Exception, CanvasAuth::StateError => e
83
+ failure_url = File.join(request.env['SCRIPT_NAME'], settings.failure_redirect)
84
+ redirect (failure_url + "?error=#{params[:error] || CGI.escape(e.to_s)}")
85
+ end
86
+
54
87
  response = JSON.parse(response)
55
88
  session['user_id'] = response['user']['id']
56
89
  session['access_token'] = response['access_token']
57
-
58
90
  oauth_callback(response) if self.respond_to?(:oauth_callback)
59
91
 
60
- redirect params['state']
92
+ redirect session['oauth_redirect']
61
93
  end
62
94
 
63
95
  app.get app.logout_path do
@@ -77,24 +109,31 @@ module Sinatra
77
109
  redirect to(settings.logout_redirect)
78
110
  end
79
111
 
80
- # These two routes exist to prevent 404'ing with default options, but
81
- # ideally they should be overridden by the app, or alternate paths given
82
- app.get '/unauthorized' do
83
- 'Your canvas account unauthorized to view this resource'
112
+ app.get app.logout_redirect do
113
+ render_view('Logged out', 'You have been successfully logged out')
84
114
  end
85
115
 
86
- app.get '/logged-out' do
87
- "You have been logged out <a href='canvas-auth-login'>" \
88
- "Click here</a> to log in again."
116
+ app.get app.unauthorized_redirect do
117
+ render_view('Authentication Failed',
118
+ 'Your canvas account is unauthorized to view this resource')
89
119
  end
90
120
 
121
+ app.get app.failure_redirect do
122
+ message = "Login could not be completed."
123
+ if params[:error] && !params[:error].empty?
124
+ message += " (#{CGI.unescape(params[:error])})"
125
+ end
126
+
127
+ render_view("Authentication Failed", message)
128
+ end
91
129
 
92
130
  # Redirect unauthenticated/unauthorized users before hitting app routes
93
131
  app.before do
94
132
  current_path = "#{request.env['SCRIPT_NAME']}#{request.env['PATH_INFO']}"
95
133
  if CanvasAuth.auth_path?(self.settings, current_path, request.env['SCRIPT_NAME'])
96
134
  if session['user_id'].nil?
97
- redirect "#{request.env['SCRIPT_NAME']}#{settings.login_path}?state=#{current_path}"
135
+ session['oauth_redirect'] = current_path
136
+ redirect "#{request.env['SCRIPT_NAME']}#{settings.login_path}"
98
137
  elsif self.respond_to?(:authorized) && !authorized
99
138
  redirect "#{request.env['SCRIPT_NAME']}#{settings.unauthorized_redirect}"
100
139
  end
@@ -105,9 +144,11 @@ module Sinatra
105
144
  # Should the current path ask for authentication or is it public?
106
145
  def self.auth_path?(app, current_path, script_name = '')
107
146
  exempt_paths = [ app.login_path, app.token_path, app.logout_path,
108
- app.logout_redirect, app.unauthorized_redirect ]
147
+ app.logout_redirect, app.unauthorized_redirect,
148
+ app.failure_redirect ]
109
149
 
110
150
  app.auth_paths.select{ |p| current_path.match(p) }.any? &&
151
+ !app.public_paths.select{ |p| current_path.match(p) }.any? &&
111
152
  !exempt_paths.map{|p| File.join(script_name, p)}.include?(current_path)
112
153
  end
113
154
 
@@ -118,6 +159,17 @@ module Sinatra
118
159
  end
119
160
  end
120
161
  end
162
+
163
+ # Verify state param from Canvas is the same one originally sent. Otherwise,
164
+ # unauthorized requests can be made by intercepting the redirect from Canvas
165
+ # to app token_path and tricking an authorized user into accessing the link.
166
+ # http://homakov.blogspot.com/2012/07/saferweb-most-common-oauth2.html
167
+ def self.verify_oauth_state(params, session)
168
+ saved_state = session['oauth_state']
169
+ if saved_state != params['state'] || (saved_state && saved_state.empty?)
170
+ raise CanvasAuth::StateError, 'Invalid OAuth state token provided'
171
+ end
172
+ end
121
173
  end
122
174
 
123
175
  register CanvasAuth
@@ -1,5 +1,5 @@
1
1
  module Sinatra
2
2
  module CanvasAuth
3
- VERSION = "0.0.3"
3
+ VERSION = "0.1.0"
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sinatra-canvas_auth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Connor Ford
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-09-27 00:00:00.000000000 Z
11
+ date: 2016-11-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -73,6 +73,7 @@ extensions: []
73
73
  extra_rdoc_files: []
74
74
  files:
75
75
  - README.md
76
+ - lib/sinatra/canvas_auth.erb
76
77
  - lib/sinatra/canvas_auth.rb
77
78
  - lib/sinatra/canvas_auth/version.rb
78
79
  homepage: https://github.com/cuonline