canvas_lti_third_party_cookies 0.3.3 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +115 -42
- data/Rakefile +2 -0
- data/app/assets/images/canvas_lti_third_party_cookies/cookies.svg +581 -0
- data/app/assets/javascripts/canvas_lti_third_party_cookies/relaunch_on_login.js +77 -0
- data/app/assets/stylesheets/canvas_lti_third_party_cookies/relaunch_on_login.css +52 -0
- data/app/controllers/concerns/canvas_lti_third_party_cookies/relaunch_on_login.rb +84 -0
- data/app/views/canvas_lti_third_party_cookies/_cookie_message.erb +11 -0
- data/app/views/canvas_lti_third_party_cookies/relaunch_on_login.erb +49 -0
- data/lib/canvas_lti_third_party_cookies/engine.rb +18 -0
- data/lib/canvas_lti_third_party_cookies/version.rb +1 -1
- metadata +42 -17
- data/app/controllers/concerns/canvas_lti_third_party_cookies/safari_launch.rb +0 -76
- data/app/views/canvas_lti_third_party_cookies/full_window_launch.erb +0 -80
- data/app/views/canvas_lti_third_party_cookies/request_storage_access.erb +0 -115
@@ -0,0 +1,77 @@
|
|
1
|
+
const getRelaunchElement = (id) => document.getElementById(`relaunch-${id}`);
|
2
|
+
const hide = (el) => (el.style.display = "none");
|
3
|
+
const show = (el) => (el.style.display = "flex"); // normally this is 'block', but the containers need to be 'flex'
|
4
|
+
const COOKIE_NAME = "can_set_cookies";
|
5
|
+
|
6
|
+
const openNewWindow = ({ window_type, form_target, width, height }) => {
|
7
|
+
switch (window_type) {
|
8
|
+
case "popup": {
|
9
|
+
return window.open(
|
10
|
+
"",
|
11
|
+
form_target,
|
12
|
+
"toolbar=no,menubar=no,location=no,status=no,resizable,scrollbars," +
|
13
|
+
`width=${width},height=${height}`
|
14
|
+
);
|
15
|
+
}
|
16
|
+
case "new_window": {
|
17
|
+
return window.open("", form_target);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
};
|
21
|
+
|
22
|
+
const relaunchOnLogin = () => {
|
23
|
+
const { window_type, form_target, width, height } = window.ENV;
|
24
|
+
|
25
|
+
const successMessageEl = getRelaunchElement("success");
|
26
|
+
const retryMessageEl = getRelaunchElement("retry");
|
27
|
+
const relaunchFormEl = getRelaunchElement("form");
|
28
|
+
const retryButtonEl = getRelaunchElement("request");
|
29
|
+
|
30
|
+
// set a test cookie, both with and without same-site none
|
31
|
+
// this will work for local development and deployed environments
|
32
|
+
document.cookie = `${COOKIE_NAME}=true; path=/;`;
|
33
|
+
document.cookie = `${COOKIE_NAME}=true; path=/; samesite=none; secure`;
|
34
|
+
if (
|
35
|
+
document.cookie.split(";").some((cookie) => cookie.includes(COOKIE_NAME))
|
36
|
+
) {
|
37
|
+
// setting cookies works, remove test cookie and continue login flow inline
|
38
|
+
document.cookie = `${COOKIE_NAME}=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT`;
|
39
|
+
document.getElementById("redirect-form").submit();
|
40
|
+
return;
|
41
|
+
}
|
42
|
+
|
43
|
+
// open in new tab and restart login flow
|
44
|
+
show(successMessageEl);
|
45
|
+
const newWindow = openNewWindow({ window_type, form_target, width, height });
|
46
|
+
|
47
|
+
if (newWindow) {
|
48
|
+
// tool opened successfully, POST login form data to opened window
|
49
|
+
relaunchFormEl.submit();
|
50
|
+
return;
|
51
|
+
}
|
52
|
+
|
53
|
+
// tool open blocked, tell user to allow popups and try again
|
54
|
+
show(retryMessageEl);
|
55
|
+
hide(successMessageEl);
|
56
|
+
|
57
|
+
retryButtonEl.addEventListener("click", () => {
|
58
|
+
// open tool and POST login data again
|
59
|
+
openNewWindow({ window_type, form_target, width, height });
|
60
|
+
relaunchFormEl.submit();
|
61
|
+
|
62
|
+
show(successMessageEl);
|
63
|
+
hide(retryMessageEl);
|
64
|
+
});
|
65
|
+
};
|
66
|
+
|
67
|
+
// run on page load
|
68
|
+
document.addEventListener("DOMContentLoaded", relaunchOnLogin);
|
69
|
+
|
70
|
+
// exported for testing only
|
71
|
+
module.exports = {
|
72
|
+
COOKIE_NAME,
|
73
|
+
openNewWindow,
|
74
|
+
hide,
|
75
|
+
show,
|
76
|
+
relaunchOnLogin,
|
77
|
+
};
|
@@ -0,0 +1,52 @@
|
|
1
|
+
.flex-container {
|
2
|
+
display: none;
|
3
|
+
flex-direction: column;
|
4
|
+
height: 100%;
|
5
|
+
font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans",
|
6
|
+
"Helvetica Neue", Arial, sans-serif;
|
7
|
+
font-size: 1.1rem;
|
8
|
+
padding: 0 24px;
|
9
|
+
}
|
10
|
+
|
11
|
+
.flex-item {
|
12
|
+
margin-bottom: 10px;
|
13
|
+
text-align: center;
|
14
|
+
}
|
15
|
+
|
16
|
+
.flex-container div:first-child {
|
17
|
+
margin-top: 24px;
|
18
|
+
}
|
19
|
+
|
20
|
+
p {
|
21
|
+
margin: 0 0 0.5em 0;
|
22
|
+
}
|
23
|
+
|
24
|
+
button {
|
25
|
+
background: #008ee2;
|
26
|
+
color: #ffffff;
|
27
|
+
border: 1px solid;
|
28
|
+
border-color: #0079c1;
|
29
|
+
border-radius: 3px;
|
30
|
+
transition: background-color 0.2s ease-in-out;
|
31
|
+
display: inline-block;
|
32
|
+
position: relative;
|
33
|
+
padding: 8px 14px;
|
34
|
+
margin-bottom: 0;
|
35
|
+
font-size: 16px;
|
36
|
+
font-size: 1rem;
|
37
|
+
line-height: 20px;
|
38
|
+
text-align: center;
|
39
|
+
vertical-align: middle;
|
40
|
+
cursor: pointer;
|
41
|
+
text-decoration: none;
|
42
|
+
overflow: hidden;
|
43
|
+
text-shadow: none;
|
44
|
+
-webkit-user-select: none;
|
45
|
+
-moz-user-select: none;
|
46
|
+
user-select: none;
|
47
|
+
}
|
48
|
+
|
49
|
+
img {
|
50
|
+
width: 100px;
|
51
|
+
height: 100px;
|
52
|
+
}
|
@@ -0,0 +1,84 @@
|
|
1
|
+
module CanvasLtiThirdPartyCookies::RelaunchOnLogin
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
# this should replace your previous login render call, at the end of your login action.
|
5
|
+
#
|
6
|
+
# `redirect_url` (required): the authorization redirect URL to continue the login flow
|
7
|
+
#
|
8
|
+
# `redirect_data` (required): all form data required for the authorization redirect, which
|
9
|
+
# the previous login render call should have included in a form tag.
|
10
|
+
#
|
11
|
+
# `window_type`: (optional) Set to `:new_window` to open the tool in a
|
12
|
+
# new tab or window, or to `:popup` to open in a popup window.
|
13
|
+
# Defaults to `:new_window`.
|
14
|
+
#
|
15
|
+
# `width`: (optional) The width the popup window should be, in px. User
|
16
|
+
# has the discretion to ignore this. Only valid with window_type: popup.
|
17
|
+
# Defaults to 800px.
|
18
|
+
#
|
19
|
+
# `height`: (optional) The height the popup window should be, in px. User
|
20
|
+
# has the discretion to ignore this. Only valid with window_type: popup.
|
21
|
+
# Defaults to 600px.
|
22
|
+
#
|
23
|
+
# example:
|
24
|
+
# include CanvasLtiThirdPartyCookies::RelaunchOnLogin
|
25
|
+
# ...
|
26
|
+
# def login
|
27
|
+
# state, nonce = create_and_cache_state # handled elsewhere
|
28
|
+
# redirect_url = 'http://canvas.instructure.com/api/lti/authorize_redirect'
|
29
|
+
# redirect_data = {
|
30
|
+
# scope: 'openid',
|
31
|
+
# response_type: 'id_token',
|
32
|
+
# response_mode: 'form_post',
|
33
|
+
# prompt: 'none',
|
34
|
+
# redirect_uri: redirect_uri, # the launch url of the tool
|
35
|
+
# client_id: params.require(:client_id),
|
36
|
+
# login_hint: params.require(:login_hint),
|
37
|
+
# lti_message_hint: params.require(:lti_message_hint),
|
38
|
+
# state: state,
|
39
|
+
# nonce: nonce
|
40
|
+
# }
|
41
|
+
#
|
42
|
+
# relaunch_on_login(redirect_url, redirect_data)
|
43
|
+
# end
|
44
|
+
def relaunch_on_login(redirect_url, redirect_data, window_type: :new_window, width: 800, height: 600)
|
45
|
+
raise ArgumentError.new("window_type must be either :new_window or :popup") unless [:new_window, :popup].include? window_type
|
46
|
+
|
47
|
+
I18n.locale = calculate_locale
|
48
|
+
form_target = 'login_relaunch'
|
49
|
+
render(
|
50
|
+
'canvas_lti_third_party_cookies/relaunch_on_login',
|
51
|
+
locals: {
|
52
|
+
redirect_url: redirect_url,
|
53
|
+
redirect_data: redirect_data,
|
54
|
+
relaunch_url: request.url,
|
55
|
+
relaunch_data: params.permit(:canvas_region, :client_id, :iss, :login_hint, :lti_message_hint, :target_link_uri),
|
56
|
+
form_target: form_target,
|
57
|
+
window_type: window_type,
|
58
|
+
js_env: {
|
59
|
+
form_target: form_target,
|
60
|
+
window_type: window_type,
|
61
|
+
width: width,
|
62
|
+
height: height
|
63
|
+
}
|
64
|
+
}
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def calculate_locale
|
69
|
+
decoded_jwt = params[:lti_message_hint] ? JSON::JWT.decode(params[:lti_message_hint], :skip_verification) : {}
|
70
|
+
if decoded_jwt['canvas_locale']
|
71
|
+
# this is essentially the same logic as language_region_compatible_from below
|
72
|
+
# example: 'en-AU', 'da-x-k12', 'ru', 'zh-Hant'
|
73
|
+
full_locale = decoded_jwt['canvas_locale'].to_sym
|
74
|
+
return full_locale if I18n.available_locales.include?(full_locale)
|
75
|
+
|
76
|
+
# The exact locale is not available, let's trim it down if possible
|
77
|
+
# example: 'en', 'da', 'ru', 'zh'
|
78
|
+
trimmed_locale = decoded_jwt['canvas_locale'][0..1].to_sym
|
79
|
+
return trimmed_locale if I18n.available_locales.include?(trimmed_locale)
|
80
|
+
end
|
81
|
+
|
82
|
+
http_accept_language.language_region_compatible_from(I18n.available_locales) || I18n.default_locale
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
<div id="<%= id %>" class="flex-container">
|
2
|
+
<div class="flex-item">
|
3
|
+
<%= image_tag "canvas_lti_third_party_cookies/cookies.svg" %>
|
4
|
+
</div>
|
5
|
+
|
6
|
+
<div class="flex-item">
|
7
|
+
<h3><%= I18n.t "It looks like your browser doesn't like cookies." %></h3>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<%= yield %>
|
11
|
+
</div>
|
@@ -0,0 +1,49 @@
|
|
1
|
+
<%= stylesheet_link_tag 'canvas_lti_third_party_cookies/relaunch_on_login' %>
|
2
|
+
<%= javascript_tag do %>
|
3
|
+
window.ENV = <%= js_env.to_json.html_safe %>
|
4
|
+
<% end %>
|
5
|
+
<%= javascript_include_tag 'canvas_lti_third_party_cookies/relaunch_on_login' %>
|
6
|
+
|
7
|
+
<%# when submitted, continue to login flow step 2: redirect back to Canvas for authentication %>
|
8
|
+
<%= form_tag(redirect_url, method: :post, id: 'redirect-form') do %>
|
9
|
+
<% redirect_data.each do |k, v| %>
|
10
|
+
<%= hidden_field_tag(k, v) %>
|
11
|
+
<% end %>
|
12
|
+
<% end %>
|
13
|
+
|
14
|
+
<%# when submitted, replay login flow step 1 in a new window %>
|
15
|
+
<%# the target attribute tells the form to POST in the window matching that target, which is opened below %>
|
16
|
+
<%= form_tag(relaunch_url, method: :post, id: 'relaunch-form', target: form_target) do %>
|
17
|
+
<% relaunch_data.each do |k, v| %>
|
18
|
+
<%= hidden_field_tag(k, v) %>
|
19
|
+
<% end %>
|
20
|
+
<% end %>
|
21
|
+
|
22
|
+
<%# default message to display on new window launch; hidden by default %>
|
23
|
+
<%= render 'canvas_lti_third_party_cookies/cookie_message', id: 'relaunch-success' do %>
|
24
|
+
<div class="flex-item">
|
25
|
+
<p><%= I18n.t "Some browsers block third-party cookies by default, which this app relies on for launch and sign-in features." %></p>
|
26
|
+
<p><%= window_type == :new_window ?
|
27
|
+
I18n.t("This app has opened in a new tab, so that it can set its own cookies.") :
|
28
|
+
I18n.t("This app has opened in a popup window, so that it can set its own cookies.")
|
29
|
+
%></p>
|
30
|
+
</div>
|
31
|
+
<% end %>
|
32
|
+
|
33
|
+
<%# message displayed when popups are blocked; hidden by default %>
|
34
|
+
<%= render 'canvas_lti_third_party_cookies/cookie_message', id: 'relaunch-retry' do %>
|
35
|
+
<div class="flex-item">
|
36
|
+
<p><%= I18n.t "Some browsers block third-party cookies by default, which this app relies on for launch and sign-in features." %></p>
|
37
|
+
<p><%= window_type == :new_window ?
|
38
|
+
I18n.t("Launching this app in a new tab, separate from Canvas, will allow the app to set its own cookies.") :
|
39
|
+
I18n.t("Launching this app in a popup window, separate from Canvas, will allow the app to set its own cookies.")
|
40
|
+
%></p>
|
41
|
+
<p><%= I18n.t "To open the app, make sure your browser allows popups for this page and try again." %></p>
|
42
|
+
</div>
|
43
|
+
|
44
|
+
<div class="flex-item">
|
45
|
+
<button id="relaunch-request">
|
46
|
+
<%= window_type == :new_window ? I18n.t("Open in New Tab") : I18n.t("Open in Popup Window")%>
|
47
|
+
</button>
|
48
|
+
</div>
|
49
|
+
<% end %>
|
@@ -1,5 +1,23 @@
|
|
1
|
+
require 'i18nliner'
|
2
|
+
require 'http_accept_language'
|
3
|
+
require 'json/jwt'
|
4
|
+
|
1
5
|
module CanvasLtiThirdPartyCookies
|
2
6
|
class Engine < ::Rails::Engine
|
3
7
|
isolate_namespace CanvasLtiThirdPartyCookies
|
8
|
+
|
9
|
+
initializer "CanvasLtiThirdPartyCookies.assets.precompile" do |app|
|
10
|
+
app.config.assets.precompile += %w(
|
11
|
+
canvas_lti_third_party_cookies/relaunch_on_login.js
|
12
|
+
canvas_lti_third_party_cookies/relaunch_on_login.css
|
13
|
+
canvas_lti_third_party_cookies/cookies.svg
|
14
|
+
)
|
15
|
+
end
|
16
|
+
|
17
|
+
initializer "CanvasLtiThirdPartyCookies.i18n.locale" do |app|
|
18
|
+
app.config.i18n.load_path += Dir[root.join('config','locales','*.yml')]
|
19
|
+
app.config.i18n.default_locale = :en
|
20
|
+
app.config.i18n.fallbacks = true
|
21
|
+
end
|
4
22
|
end
|
5
23
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: canvas_lti_third_party_cookies
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xander Moffatt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -16,40 +16,62 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 6.
|
19
|
+
version: '6.1'
|
20
20
|
- - ">="
|
21
21
|
- !ruby/object:Gem::Version
|
22
|
-
version: 6.
|
22
|
+
version: 6.1.4.2
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
25
|
version_requirements: !ruby/object:Gem::Requirement
|
26
26
|
requirements:
|
27
27
|
- - "~>"
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: 6.
|
29
|
+
version: '6.1'
|
30
30
|
- - ">="
|
31
31
|
- !ruby/object:Gem::Version
|
32
|
-
version: 6.
|
32
|
+
version: 6.1.4.2
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
34
|
+
name: i18nliner
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version:
|
40
|
-
|
39
|
+
version: 0.1.2
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 0.1.2
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: json-jwt
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
41
52
|
- !ruby/object:Gem::Version
|
42
|
-
version:
|
53
|
+
version: 1.13.0
|
43
54
|
type: :runtime
|
44
55
|
prerelease: false
|
45
56
|
version_requirements: !ruby/object:Gem::Requirement
|
46
57
|
requirements:
|
47
58
|
- - "~>"
|
48
59
|
- !ruby/object:Gem::Version
|
49
|
-
version:
|
50
|
-
|
60
|
+
version: 1.13.0
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: http_accept_language
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: 2.1.1
|
68
|
+
type: :runtime
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
51
73
|
- !ruby/object:Gem::Version
|
52
|
-
version: 2.
|
74
|
+
version: 2.1.1
|
53
75
|
- !ruby/object:Gem::Dependency
|
54
76
|
name: sqlite3
|
55
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -75,9 +97,12 @@ files:
|
|
75
97
|
- LICENSE
|
76
98
|
- README.md
|
77
99
|
- Rakefile
|
78
|
-
- app/
|
79
|
-
- app/
|
80
|
-
- app/
|
100
|
+
- app/assets/images/canvas_lti_third_party_cookies/cookies.svg
|
101
|
+
- app/assets/javascripts/canvas_lti_third_party_cookies/relaunch_on_login.js
|
102
|
+
- app/assets/stylesheets/canvas_lti_third_party_cookies/relaunch_on_login.css
|
103
|
+
- app/controllers/concerns/canvas_lti_third_party_cookies/relaunch_on_login.rb
|
104
|
+
- app/views/canvas_lti_third_party_cookies/_cookie_message.erb
|
105
|
+
- app/views/canvas_lti_third_party_cookies/relaunch_on_login.erb
|
81
106
|
- app/views/layouts/application.html.erb
|
82
107
|
- lib/canvas_lti_third_party_cookies.rb
|
83
108
|
- lib/canvas_lti_third_party_cookies/engine.rb
|
@@ -101,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
126
|
- !ruby/object:Gem::Version
|
102
127
|
version: '0'
|
103
128
|
requirements: []
|
104
|
-
rubygems_version: 3.
|
129
|
+
rubygems_version: 3.2.32
|
105
130
|
signing_key:
|
106
131
|
specification_version: 4
|
107
132
|
summary: Allow LTI tools launched by Canvas to set 3rd party cookies in Safari 13.1+
|
@@ -1,76 +0,0 @@
|
|
1
|
-
require 'browser'
|
2
|
-
|
3
|
-
module CanvasLtiThirdPartyCookies::SafariLaunch
|
4
|
-
extend ActiveSupport::Concern
|
5
|
-
|
6
|
-
# this needs to be called as a before_action on the route that launches the tool
|
7
|
-
# and the tool is required to pass some parameters to this method.
|
8
|
-
# the `launch_url` parameter is required, which should be the route
|
9
|
-
# that launches the tool.
|
10
|
-
# the `launch_params` parameter is optional, and should contain
|
11
|
-
# all needed query parameters that the tool requires to launch.
|
12
|
-
# the `launch_data` parameter is optional, and should contain
|
13
|
-
# all needed form data that the tool requires to launch.
|
14
|
-
# example:
|
15
|
-
# include CanvasLtiThirdPartyCookies::SafariLaunch
|
16
|
-
# ...
|
17
|
-
# before_action -> {
|
18
|
-
# handle_safari_launch(launch_url: action_url, launch_params: { foo: bar }, launch_data: { foo: baz })
|
19
|
-
# }
|
20
|
-
def handle_safari_launch(launch_url:, launch_params: {}, launch_data: {})
|
21
|
-
return unless is_safari?
|
22
|
-
|
23
|
-
# Safari launch #4: Storage Access has been granted,
|
24
|
-
# so launch the app normally. Note that this is not an actual LTI launch, but
|
25
|
-
# just opaquely passing on the data from launch #3.
|
26
|
-
return if params[:storage_access_status].present?
|
27
|
-
|
28
|
-
# Safari launch #2: Full-window launch, solely for first-party user interaction.
|
29
|
-
# During a full-window launch, Canvas provides a :platform_redirect_url that
|
30
|
-
# will launch the tool again within an iframe in Canvas. (#3)
|
31
|
-
if params[:platform_redirect_url].present?
|
32
|
-
return render(
|
33
|
-
'canvas_lti_third_party_cookies/full_window_launch',
|
34
|
-
locals: { platform_redirect_url: params[:platform_redirect_url] }
|
35
|
-
)
|
36
|
-
end
|
37
|
-
|
38
|
-
# Safari launch #1: request Storage Access, then relaunch the tool. (#4)
|
39
|
-
# If request fails, request a full window launch instead. (#2)
|
40
|
-
# Safari launch #3: Relaunched by Canvas after full-window launch,
|
41
|
-
# request Storage Access and then relaunch the tool. (#4)
|
42
|
-
# Pass along any parameters provided by the tool that are needed to launch correctly,
|
43
|
-
# and tell the tool that it has Storage Access.
|
44
|
-
render(
|
45
|
-
'canvas_lti_third_party_cookies/request_storage_access',
|
46
|
-
locals: {
|
47
|
-
launch_url: launch_url,
|
48
|
-
relaunch_url: relaunch_url(launch_url, launch_params),
|
49
|
-
launch_data: launch_data.merge({ storage_access_status: "granted"})
|
50
|
-
}
|
51
|
-
)
|
52
|
-
end
|
53
|
-
|
54
|
-
# Safari launch #4 (described above) is actually an internal opaque redirect of launch #3
|
55
|
-
# and not a real Canvas LTI launch, so the id_token (and specifically the nonce inside)
|
56
|
-
# is exactly the same. Normally, ignoring the nonce is a Bad Idea since it can allow
|
57
|
-
# replay attacks, but for this specific situation (the request is an internal redirect)
|
58
|
-
# it's a sufficient hack.
|
59
|
-
def should_ignore_nonce?
|
60
|
-
referer = URI.parse(request.referer)
|
61
|
-
is_safari? && params[:storage_access_status] == "granted" && referer.host == request.host && referer.port == request.port
|
62
|
-
end
|
63
|
-
|
64
|
-
private
|
65
|
-
|
66
|
-
def is_safari?
|
67
|
-
browser = Browser.new(request.headers["User-Agent"])
|
68
|
-
# detect both MacOS and iOS Safari
|
69
|
-
browser.safari? || (browser.webkit? && browser.platform.ios?)
|
70
|
-
end
|
71
|
-
|
72
|
-
def relaunch_url(launch_url, launch_params)
|
73
|
-
return launch_url if launch_params.empty?
|
74
|
-
"#{launch_url}?#{launch_params.to_query}"
|
75
|
-
end
|
76
|
-
end
|
@@ -1,80 +0,0 @@
|
|
1
|
-
<%= javascript_tag do -%>
|
2
|
-
document.addEventListener("DOMContentLoaded", () => {
|
3
|
-
document.getElementById("redirect").addEventListener("click", () => {
|
4
|
-
window.location.replace("<%= platform_redirect_url %>");
|
5
|
-
});
|
6
|
-
});
|
7
|
-
<% end %>
|
8
|
-
<style type="text/css">
|
9
|
-
.flex-container {
|
10
|
-
display: flex;
|
11
|
-
flex-direction: column;
|
12
|
-
height: 100%;
|
13
|
-
font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif;
|
14
|
-
font-size: 1.1em;
|
15
|
-
padding: 0 75px 0 75px;
|
16
|
-
}
|
17
|
-
|
18
|
-
.flex-item {
|
19
|
-
margin-bottom: 10px;
|
20
|
-
text-align: center;
|
21
|
-
}
|
22
|
-
|
23
|
-
.first {
|
24
|
-
margin-top: 50px;
|
25
|
-
}
|
26
|
-
|
27
|
-
p {
|
28
|
-
margin: 0 0 0.5em 0;
|
29
|
-
}
|
30
|
-
|
31
|
-
button {
|
32
|
-
background: #008EE2;
|
33
|
-
color: #ffffff;
|
34
|
-
border: 1px solid;
|
35
|
-
border-color: #0079C1;
|
36
|
-
border-radius: 3px;
|
37
|
-
transition: background-color 0.2s ease-in-out;
|
38
|
-
display: inline-block;
|
39
|
-
position: relative;
|
40
|
-
padding: 8px 14px;
|
41
|
-
margin-bottom: 0;
|
42
|
-
font-size: 16px;
|
43
|
-
font-size: 1rem;
|
44
|
-
line-height: 20px;
|
45
|
-
text-align: center;
|
46
|
-
vertical-align: middle;
|
47
|
-
cursor: pointer;
|
48
|
-
text-decoration: none;
|
49
|
-
overflow: hidden;
|
50
|
-
text-shadow: none;
|
51
|
-
-webkit-user-select: none;
|
52
|
-
-moz-user-select: none;
|
53
|
-
}
|
54
|
-
|
55
|
-
#safari-logo {
|
56
|
-
width: 100px;
|
57
|
-
height: 100px;
|
58
|
-
}
|
59
|
-
</style>
|
60
|
-
|
61
|
-
<div class="flex-container">
|
62
|
-
<div class="flex-item first">
|
63
|
-
<img id="safari-logo" src="https://upload.wikimedia.org/wikipedia/commons/5/52/Safari_browser_logo.svg" alt="Safari Logo" />
|
64
|
-
</div>
|
65
|
-
|
66
|
-
<div class="flex-item">
|
67
|
-
<strong>It looks like you are using Safari.</strong>
|
68
|
-
</div>
|
69
|
-
|
70
|
-
<div class="flex-item">
|
71
|
-
<p>Occasionally, Safari requires you to launch this app outside of Canvas before logging in.</p>
|
72
|
-
<p>This setup is now complete, and Canvas can now relaunch this app.</p>
|
73
|
-
<p>In some cases, you may need to relaunch this app yourself.</p>
|
74
|
-
</div>
|
75
|
-
|
76
|
-
<div class="flex-item">
|
77
|
-
<button id="redirect">Relaunch App in Canvas</button>
|
78
|
-
</div>
|
79
|
-
<div>
|
80
|
-
</div>
|