canvas_lti_third_party_cookies 0.3.3 → 1.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 +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>
|