sinatra-canvas_auth 0.0.1
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 +7 -0
- data/README.md +180 -0
- data/lib/sinatra/canvas_auth/version.rb +5 -0
- data/lib/sinatra/canvas_auth.rb +123 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2de2f77ea5c253f39b90e8fa838a3da4fd332392
|
4
|
+
data.tar.gz: 404e2f5985d52d798ba61f4680205cf56ffbcd0e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1241210da5cc56803b178946eb18f4dbfba09b2f93239e641a01d20c86ca24963b4203792c2eb34f9c43d7e114bacd3bef3fc0071304d84c2d8766d5f4298575
|
7
|
+
data.tar.gz: afe435e636bbf4bbae985de9029d622b1da6ed93073f2509ca65f930da81e0b37581fde9b6d46fa5c25af32b57c763382e692a871638c2c870db019100dadc46
|
data/README.md
ADDED
@@ -0,0 +1,180 @@
|
|
1
|
+
# Sinatra::CanvasAuth
|
2
|
+
|
3
|
+
CanvasAuth is a Sinatra extension that implements the [OAuth2 flow](https://canvas.instructure.com/doc/api/file.oauth.html) used for authenticating a user via [Canvas LMS](https://github.com/instructure/canvas-lms).
|
4
|
+
|
5
|
+
This gem handles redirection of unauthenticated/unauthorized users, as well as the routing & API calls necessary for logging in/obtaining an access token, logging out/deleting an access token.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'sinatra-canvas_auth'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install sinatra-canvas_auth
|
22
|
+
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
If you are developing a ["classic-style"](http://www.sinatrarb.com/intro.html#Modular%20vs.%20Classic%20Style) Sinatra app, require the files at the top of your app, enable sessions, and set the required configuration options.
|
26
|
+
|
27
|
+
``` ruby
|
28
|
+
require sinatra
|
29
|
+
require sinatra/canvas_auth
|
30
|
+
|
31
|
+
# These settings are required
|
32
|
+
configure do
|
33
|
+
enable :sessions
|
34
|
+
set :canvas_url, 'https://ucdenver.instructure.com'
|
35
|
+
set :client_id, 10230000000000045
|
36
|
+
set :client_secret, '659df93f24affc25948ee437f8ac825edfa903d95e3a5ace0bb5ac4fb61686c6'
|
37
|
+
end
|
38
|
+
|
39
|
+
get '/' do
|
40
|
+
'Hello World'
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
For "modular-style" apps, you must also explicitly register the extension
|
45
|
+
``` ruby
|
46
|
+
require sinatra/base
|
47
|
+
require sinatra/canvas_auth
|
48
|
+
|
49
|
+
class App < Sinatra::Base
|
50
|
+
|
51
|
+
# These settings are required
|
52
|
+
configure do
|
53
|
+
enable :sessions
|
54
|
+
set :canvas_url, 'https://ucdenver.instructure.com'
|
55
|
+
set :client_id, 10230000000000045
|
56
|
+
set :client_secret, '659df93f24affc25948ee437f8ac825edfa903d95e3a5ace0bb5ac4fb61686c6'
|
57
|
+
end
|
58
|
+
|
59
|
+
register Sinatra::CanvasAuth
|
60
|
+
|
61
|
+
get '/' do
|
62
|
+
'Hello World'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
```
|
66
|
+
|
67
|
+
## Configuration
|
68
|
+
CanvasAuth pulls settings from your Sinatra app's configuration, which can be set with [the built-in DSL](http://www.sinatrarb.com/configuration.html).
|
69
|
+
|
70
|
+
For simplicity, examples in this documentation hard-code configuration within the application, though it is often wise to use alternate methods when configuring sensitive data (e.g. API keys), especially when working with open source. Here are just a few options that can help you with this:
|
71
|
+
* [Sinatra::ConfigFile](http://www.sinatrarb.com/contrib/config_file.html)
|
72
|
+
* [Dotenv](https://github.com/bkeepers/dotenv)
|
73
|
+
* [Figaro](https://github.com/laserlemon/figaro)
|
74
|
+
|
75
|
+
CanvasAuth requires a baseline configuration to function, as Canvas API settings will differ between instances. Below is a full list of the available configuraiton options.
|
76
|
+
|
77
|
+
#### Required Settings
|
78
|
+
|
79
|
+
* **canvas_url** (String)
|
80
|
+
|
81
|
+
The full URL of the Canvas instance used for authentication.
|
82
|
+
```ruby
|
83
|
+
set :canvas_url, 'https://ucdenver.instructure.com'
|
84
|
+
```
|
85
|
+
|
86
|
+
* **client_id** (String)
|
87
|
+
|
88
|
+
The client id (AKA "ID") for the developer key associated with your app. Developer keys can be requested [here](http://goo.gl/yu4lT), or created directly by Canvas admins by clicking "Developer Keys" on the sidebar under your account's admin panel.
|
89
|
+
```ruby
|
90
|
+
set :client_id, 10230000000000045
|
91
|
+
```
|
92
|
+
|
93
|
+
* **client_secret** (String)
|
94
|
+
|
95
|
+
The 64 character client secret (AKA "Key") for the developer key associated with your app.
|
96
|
+
```ruby
|
97
|
+
set :client_secret, '659df93f24affc25948ee437f8ac825edfa903d95e3a5ace0bb5ac4fb61686c6'
|
98
|
+
```
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
#### Optional Settings
|
103
|
+
|
104
|
+
* **auth_paths** (Array)
|
105
|
+
Default: [/.*/]
|
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
|
+
```ruby
|
108
|
+
set :auth_paths, ['/admin', /^\/courses\/(\d)+$]
|
109
|
+
```
|
110
|
+
|
111
|
+
Alternative syntax:
|
112
|
+
```ruby
|
113
|
+
authenticate '/admin', /^\/courses\/(\d)+$
|
114
|
+
```
|
115
|
+
|
116
|
+
* **unauthorized_redirect** (String)
|
117
|
+
Default: "/unauthorized"
|
118
|
+
If the above "authorized" setting is provided and returns falsy when called, the user will be redirected to this path.
|
119
|
+
```ruby
|
120
|
+
set :unauthorized_redirect, '/not_allowed'
|
121
|
+
```
|
122
|
+
|
123
|
+
* **logout_redirect** (String)
|
124
|
+
Default: "/logged-out"
|
125
|
+
After a user is logged out, they will be redirected to this path.
|
126
|
+
```ruby
|
127
|
+
set :logout_redirect, '/goodbye'
|
128
|
+
```
|
129
|
+
|
130
|
+
|
131
|
+
#### Callbacks
|
132
|
+
|
133
|
+
The following are optional hooks called by CanvasAuth which allow you to customize certain behavior. They should be defined as [helper methods](http://www.sinatrarb.com/intro.html#Helpers) within your application.
|
134
|
+
|
135
|
+
* **oauth_callback(oauth_response)**
|
136
|
+
|
137
|
+
Once the OAuth authentication request has been made, this method is called with the API response from Canvas as an argument. This may be used to define a custom response handling action, such as saving the user's token in a database.
|
138
|
+
```ruby
|
139
|
+
helpers do
|
140
|
+
def oauth_callback(oauth_response)
|
141
|
+
uid = oauth_response['user']['id']
|
142
|
+
token = oauth_response['access_token']
|
143
|
+
db_connection.execute("UPDATE users SET access_token = ? where uid = ?", token, uid)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
* **authorized()**
|
149
|
+
|
150
|
+
This method may be defined to check the authorization priveleges of a user once they have logged in. It should return truthy for authorized users, falsy otherwise. Since this is called without parameters, it generally makes use of session variables and/or settings which were set during the oauth_callback method or elsewhere in the app.
|
151
|
+
```ruby
|
152
|
+
helpers do
|
153
|
+
def authorized
|
154
|
+
session[:allowed_roles].includes?(session[:user_roles])
|
155
|
+
end
|
156
|
+
end
|
157
|
+
```
|
158
|
+
|
159
|
+
## Miscellaneous Notes
|
160
|
+
* CanvasAuth automatically assigns `session['user_id']` and `session['access_token']` to the values returned by the OAuth response, so there is no need to do this manually in your oauth_callback proc.
|
161
|
+
|
162
|
+
* The following routes are defined by CanvasAuth for use in the OAuth flow and should not be overridden by your application:
|
163
|
+
* GET /canvas-auth-login
|
164
|
+
* GET /canvas-auth-logout
|
165
|
+
* POST /canvas-auth-token
|
166
|
+
|
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.
|
168
|
+
* GET /unauthorized
|
169
|
+
* GET /logged-out
|
170
|
+
|
171
|
+
* All routes defined by CanvasAuth are permanently exempt from the requiring authentication, to avoid redirect loops.
|
172
|
+
## Contributing
|
173
|
+
|
174
|
+
Feeback, bug reports, and pull requests are welcome and appreciated.
|
175
|
+
|
176
|
+
1. Fork it ( https://github.com/CUOnline/sinatra-canvas_auth/fork )
|
177
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
178
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
179
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
180
|
+
5. Create a new Pull Request
|
@@ -0,0 +1,123 @@
|
|
1
|
+
require 'sinatra'
|
2
|
+
require 'rest-client'
|
3
|
+
|
4
|
+
module Sinatra
|
5
|
+
module CanvasAuth
|
6
|
+
|
7
|
+
DEFAULT_SETTINGS = {
|
8
|
+
:auth_paths => [/.*/],
|
9
|
+
:canvas_url => nil,
|
10
|
+
:client_id => nil,
|
11
|
+
:client_secret => nil,
|
12
|
+
:login_path => '/canvas-auth-login',
|
13
|
+
:token_path => '/canvas-auth-token',
|
14
|
+
:logout_path => '/canvas-auth-logout',
|
15
|
+
:logout_redirect => '/logged-out',
|
16
|
+
:unauthorized_redirect => '/unauthorized'
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
# Just a prettier syntax for setting auth_paths
|
20
|
+
def authenticate(*paths)
|
21
|
+
set :auth_paths, paths
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.registered(app)
|
25
|
+
self.merge_defaults(app)
|
26
|
+
|
27
|
+
app.get app.login_path do
|
28
|
+
redirect_uri = "#{request.scheme}://#{request.host_with_port}" \
|
29
|
+
"#{request.env['SCRIPT_NAME']}#{app.token_path}"
|
30
|
+
|
31
|
+
redirect_params = "client_id=#{app.client_id}&" \
|
32
|
+
"response_type=code&" \
|
33
|
+
"state=#{CGI.escape(params['state'])}&" \
|
34
|
+
"redirect_uri=#{CGI.escape(redirect_uri)}"
|
35
|
+
|
36
|
+
['scope', 'purpose', 'force_login', 'unique_id'].each do |optional_param|
|
37
|
+
if params[optional_param]
|
38
|
+
redirect_params += "&#{optional_param}=#{CGI.escape(params[optional_param])}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
redirect "#{app.canvas_url}/login/oauth2/auth?#{redirect_params}"
|
43
|
+
end
|
44
|
+
|
45
|
+
app.get app.token_path do
|
46
|
+
payload = {
|
47
|
+
:code => params['code'],
|
48
|
+
:client_id => settings.client_id,
|
49
|
+
:client_secret => settings.client_secret
|
50
|
+
}
|
51
|
+
|
52
|
+
response = RestClient.post("#{settings.canvas_url}/login/oauth2/token", payload)
|
53
|
+
response = JSON.parse(response)
|
54
|
+
session['user_id'] = response['user']['id']
|
55
|
+
session['access_token'] = response['access_token']
|
56
|
+
|
57
|
+
oauth_callback(response) if self.respond_to?(:oauth_callback)
|
58
|
+
|
59
|
+
redirect params['state']
|
60
|
+
end
|
61
|
+
|
62
|
+
app.get app.logout_path do
|
63
|
+
if session['access_token']
|
64
|
+
delete_url = "#{settings.canvas_url}/login/oauth2/token"
|
65
|
+
delete_url += "&expire_sessions=1" if params['expire_sessions']
|
66
|
+
|
67
|
+
RestClient::Request.execute({
|
68
|
+
:method => :delete,
|
69
|
+
:url => delete_url,
|
70
|
+
:headers => {
|
71
|
+
:authorization => "Bearer #{session['access_token']}"
|
72
|
+
}
|
73
|
+
})
|
74
|
+
end
|
75
|
+
session.clear
|
76
|
+
redirect to(settings.logout_redirect)
|
77
|
+
end
|
78
|
+
|
79
|
+
# These two routes exist to prevent 404'ing with default options, but
|
80
|
+
# ideally they should be overridden by the app, or alternate paths given
|
81
|
+
app.get '/unauthorized' do
|
82
|
+
'Your canvas account unauthorized to view this resource'
|
83
|
+
end
|
84
|
+
|
85
|
+
app.get '/logged-out' do
|
86
|
+
"You have been logged out <a href='canvas-auth-login'>" \
|
87
|
+
"Click here</a> to log in again."
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
# Redirect unauthenticated/unauthorized users before hitting app routes
|
92
|
+
app.before do
|
93
|
+
current_path = "#{request.env['SCRIPT_NAME']}#{request.env['PATH_INFO']}"
|
94
|
+
if CanvasAuth.auth_path?(self.settings, current_path)
|
95
|
+
if session['user_id'].nil?
|
96
|
+
redirect "#{request.env['SCRIPT_NAME']}#{settings.login_path}?state=#{current_path}"
|
97
|
+
elsif self.respond_to?(:authorized) && !authorized
|
98
|
+
redirect "#{request.env['SCRIPT_NAME']}#{settings.unauthorized_redirect}"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Should the current path ask for authentication or is it public?
|
105
|
+
def self.auth_path?(app, current_path)
|
106
|
+
exempt_paths = [ app.login_path, app.token_path, app.logout_path,
|
107
|
+
app.logout_redirect, app.unauthorized_redirect ]
|
108
|
+
|
109
|
+
app.auth_paths.select{ |p| current_path.match(p) }.any? &&
|
110
|
+
!exempt_paths.include?(current_path)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.merge_defaults(app)
|
114
|
+
DEFAULT_SETTINGS.each do |key, value|
|
115
|
+
if !app.respond_to?(key)
|
116
|
+
app.set key, value
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
register CanvasAuth
|
123
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sinatra-canvas_auth
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Connor Ford
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-09-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.8'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.8'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sinatra
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.4'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.4'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rest-client
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.8'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.8'
|
69
|
+
description:
|
70
|
+
email: connor.ford@ucdenver.edu
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- README.md
|
76
|
+
- lib/sinatra/canvas_auth.rb
|
77
|
+
- lib/sinatra/canvas_auth/version.rb
|
78
|
+
homepage: https://github.com/cuonline
|
79
|
+
licenses:
|
80
|
+
- MIT
|
81
|
+
metadata: {}
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '0'
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubyforge_project:
|
98
|
+
rubygems_version: 2.5.1
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Canvas LMS OAuth flow for Sinatra
|
102
|
+
test_files: []
|