Skip to content

Commit 75b4cab

Browse files
authored
Get authentication, class membership, and roles via LTI (#40)
2 parents 20028a9 + 5f4e1c5 commit 75b4cab

31 files changed

+381
-45
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,6 @@ node_modules/
2424
app/assets/javascripts/react
2525
/public/packs
2626
/node_modules
27+
28+
localhost.crt
29+
localhost.key

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ group :development do
4848
gem 'letter_opener'
4949
gem 'web-console'
5050
gem 'bullet'
51+
gem 'awesome_print'
5152
end
5253

5354
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ GEM
6464
arel (7.1.1)
6565
authority (3.2.0)
6666
activesupport (>= 3.0.0)
67+
awesome_print (1.7.0)
6768
axiom-types (0.1.1)
6869
descendants_tracker (~> 0.0.4)
6970
ice_nine (~> 0.11.0)
@@ -376,6 +377,7 @@ DEPENDENCIES
376377
acts_as_list
377378
ahoy_matey
378379
authority
380+
awesome_print
379381
bourbon
380382
bullet
381383
byebug

Procfile.dev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
web: bin/rails s -p3000 -b0.0.0.0
1+
web: [ -z "$LOCALHOST_SSL" ] && bin/rails s -p3000 -b0.0.0.0 || bundle exec puma -b 'ssl://0.0.0.0:3000?key=/vagrant/localhost.key&cert=/vagrant/localhost.crt'
22
webpack: bin/webpack-dev-server

README.markdown

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,13 @@ can `vagrant plugin install vagrant-fsnotify`), and then
1212
2. In one terminal window or tmux pane: `vagrant ssh -c 'cd /vagrant && foreman start'`.
1313
6. In another, `vagrant fsnotify`
1414
6. Browse to http://localhost:3000/
15+
16+
## https://localhost
17+
18+
When developing the LTI tool provider components of Gala, it is useful to be able to use https with the development server. This is how to set that up.
19+
20+
1. Do this in the vagrant instance. Generate a self-signed certificate: `openssl req -new -newkey rsa:2048 -sha1 -days 365 -nodes -x509 -keyout localhost.key -out localhost.crt`
21+
2. Trust the certificate in your vagrant instance: `sudo cp localhost.crt /etc/ssl/cert && sudo cp localhost.key /etc/ssl/private && sudo c_rehash`
22+
3. Trust the certificate on your host machine using Keychain Access. Drag `localhost.crt` into the app, then Get Info and choose Always Trust.
23+
4. Start the development servers with `LOCALHOST_SSL=true foreman start`
24+
6. Browse to https://localhost:3000 (http will not work)
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class AuthenticationStrategies::ConfigController < ApplicationController
2+
3+
def lti
4+
end
5+
6+
end

app/controllers/authentication_strategies/omniauth_callbacks_controller.rb

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
class AuthenticationStrategies::OmniauthCallbacksController < Devise::OmniauthCallbacksController
22
before_action :set_authentication_strategy, except: [:failure]
3+
before_action :set_reader, except: [:failure]
4+
before_action :set_case, only: [:lti]
5+
before_action :set_group, only: [:lti]
36

47
def google
58
if @authentication_strategy.persisted?
6-
sign_in_and_redirect @authentication_strategy.reader, event: :authentication
9+
sign_in_and_redirect @reader, event: :authentication
710
else
811
session["devise.google_data"] = request.env["omniauth.auth"].except(:extra)
912
render 'devise/registrations/new', layout: "window"
@@ -12,7 +15,10 @@ def google
1215

1316
def lti
1417
if @authentication_strategy.persisted?
15-
sign_in_and_redirect @authentication_strategy.reader, event: :authentication
18+
sign_in @reader
19+
add_reader_to_group
20+
enroll_reader_in_case if @case
21+
redirect_to redirect_url
1622
else
1723
session["devise.lti_data"] = request.env["omniauth.auth"]
1824
render 'devise/registrations/new', layout: "window"
@@ -28,4 +34,40 @@ def set_authentication_strategy
2834
@authentication_strategy = AuthenticationStrategy.from_omniauth(request.env["omniauth.auth"])
2935
end
3036

37+
def set_reader
38+
@reader = @authentication_strategy.reader
39+
end
40+
41+
def set_case
42+
@case = Case.find_by_slug params[:case_slug]
43+
end
44+
45+
def set_group
46+
begin
47+
@group = Group.upsert context_id: params[:context_id], name: params[:context_title]
48+
rescue
49+
retry
50+
end
51+
end
52+
53+
def add_reader_to_group
54+
unless @reader.group_memberships.exists? group: @group
55+
@reader.group_memberships.create group: @group
56+
end
57+
end
58+
59+
def enroll_reader_in_case
60+
Enrollment.upsert reader_id: @reader.id,
61+
case_id: @case.id,
62+
status: Enrollment.status_from_lti_role(params[:ext_roles])
63+
end
64+
65+
def redirect_url
66+
if @case
67+
case_url @case
68+
else
69+
root_path
70+
end
71+
end
72+
3173
end

app/controllers/cases_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ def show
1616
authenticate_reader! unless @case.published
1717
authorize_action_for @case
1818

19-
render layout: 'application'
19+
render layout: 'with_header'
2020
end
2121

2222
def new

app/controllers/catalog_controller.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,13 @@ def home
99
@index = cases_in_catalog.select(&:in_index?).sort_by &:kicker
1010
render layout: "window"
1111
end
12+
13+
# LTI Assignment Selection wants to POST a ContentItemSelectionRequest
14+
def content_items
15+
I18n.locale = params[:launch_presentation_locale]
16+
@items = Case.where(published: true).sort_by(&:kicker)
17+
@return_url = params[:content_item_return_url]
18+
@data = params[:data]
19+
render layout: "embed"
20+
end
1221
end
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// @flow
2+
3+
import React from 'react'
4+
import { chooseContentItem } from 'shared/lti'
5+
6+
type ContentItem = {|
7+
kicker: string,
8+
title: string,
9+
dek: string,
10+
coverUrl: string,
11+
url: string,
12+
|}
13+
14+
type ContentItemsProps = {|
15+
items: ContentItem[],
16+
returnUrl: string,
17+
returnData: string,
18+
|}
19+
20+
const ContentItems = ({ items, returnUrl, returnData }: ContentItemsProps) => {
21+
const handleChooseContentItem = chooseContentItem.bind(
22+
undefined,
23+
returnUrl,
24+
returnData
25+
)
26+
return (
27+
<div className="catalog-cases">
28+
<div className="catalog-cases-index">
29+
{items.map((item: ContentItem, i: number) => (
30+
<ContentItemLink
31+
key={i}
32+
{...item}
33+
handleChooseContentItem={handleChooseContentItem}
34+
/>
35+
))}
36+
</div>
37+
</div>
38+
)
39+
}
40+
41+
export default ContentItems
42+
43+
type ContentItemProps = ContentItem & {|
44+
handleChooseContentItem: (string) => void,
45+
|}
46+
const ContentItemLink = (
47+
{
48+
kicker,
49+
title,
50+
dek,
51+
coverUrl,
52+
url,
53+
handleChooseContentItem,
54+
}: ContentItemProps
55+
) => {
56+
const handleClick = handleChooseContentItem.bind(undefined, url)
57+
return (
58+
<a
59+
tabIndex="0"
60+
className="BillboardTitle catalog-case catalog-content-item"
61+
style={{
62+
backgroundImage: `
63+
linear-gradient(rgba(0, 0, 0, 0.0), rgba(0, 0, 0, 0.5)),
64+
url(${coverUrl})`,
65+
}}
66+
onClick={handleClick}
67+
>
68+
<div className="catalog-case-credits">
69+
<h2>
70+
<span className="c-kicker">{kicker}</span>
71+
{title}
72+
</h2>
73+
<p style={{ display: 'none' }}>
74+
{dek}
75+
</p>
76+
</div>
77+
</a>
78+
)
79+
}

0 commit comments

Comments
 (0)