canvas_lti_third_party_cookies 0.2.1 → 0.4.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 +4 -4
- data/README.md +31 -30
- data/app/controllers/concerns/canvas_lti_third_party_cookies/safari_launch.rb +60 -0
- data/app/views/{lti_third_party_cookies/request_storage_access.erb → canvas_lti_third_party_cookies/prep_for_full_window_launch.erb} +23 -37
- data/lib/canvas_lti_third_party_cookies.rb +4 -0
- data/lib/canvas_lti_third_party_cookies/engine.rb +5 -0
- data/lib/canvas_lti_third_party_cookies/version.rb +3 -0
- metadata +7 -8
- data/app/controllers/concerns/lti_third_party_cookies/safari_launch.rb +0 -66
- data/app/views/lti_third_party_cookies/full_window_launch.erb +0 -79
- data/lib/lti_third_party_cookies.rb +0 -5
- data/lib/lti_third_party_cookies/engine.rb +0 -5
- data/lib/lti_third_party_cookies/version.rb +0 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8d232d4448ee62b75121b20f70143f70f75e0ea38a83408b3ed42bb51ce41247
|
4
|
+
data.tar.gz: 44605463e21036992d5e0dadc1ac7180384e3f4186917e0139fe586c9451c936
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 65a832d626b6062a7f4982417681a267bab2d49a6d070e4aef5e99b28d947fdb9d22ba73fc51badc1ceb4a9a7b59e45b3f08f8998fb1c2a286d6ec3bebd440f9
|
7
|
+
data.tar.gz: 47b3da8ab13ac2b3b9e9fae11ccaba87344b2cce53d98edbe6f96250c37c54e98210b6d0345180adbd57e1990516c89f6eed8a6a9304a8abcd2e9ae44a9fe4dc
|
data/README.md
CHANGED
@@ -1,52 +1,53 @@
|
|
1
1
|
# canvas_lti_third_party_cookies
|
2
2
|
|
3
|
-
Safari blocks all 3rd-party cookies by default, which breaks LTI tools that rely on setting cookies when launched in an iframe. Instead, it exposes a new API for getting user-permitted access to set cookies from an iframe, which
|
4
|
-
See this article for a detailed explanation: https://community.canvaslms.com/t5/Developers-Group/Safari-13-1-and-LTI-Integration/ba-p/273051
|
3
|
+
Safari blocks all 3rd-party cookies by default, which breaks LTI tools that rely on setting cookies when launched in an iframe. Instead, it exposes a new API for getting user-permitted access to set cookies from an iframe, which unfortunately doesn't completely work for LTI use cases.
|
4
|
+
See this article for a detailed explanation of the Storage Access API and previous attempts to launch LTI tools in Safari: https://community.canvaslms.com/t5/Developers-Group/Safari-13-1-and-LTI-Integration/ba-p/273051
|
5
5
|
|
6
|
-
|
7
|
-
which is responsible for some parts of this cookie dance, including launching the tool in a full-window 1st-party context, and providing a
|
8
|
-
redirect url for the relaunch after the full-window launch is complete.
|
9
|
-
|
10
|
-
This gem won't work without being launched from Canvas, or a Tool Consumer that implements the same `window.postMessage` listener as Canvas
|
6
|
+
The current workaround that this gem implements is to relaunch the tool in a new tab, new window, or popup window, where it can set first-party cookies to it's heart's content. This gem won't work without being launched from Canvas, or a Tool Consumer that implements the same `window.postMessage` listener as Canvas
|
11
7
|
does here: https://github.com/instructure/canvas-lms/blob/master/public/javascripts/lti/post_message/requestFullWindowLaunch.js
|
12
8
|
|
9
|
+
## Installation
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
# Set 3rd party cookies in Safari
|
14
|
+
gem 'canvas_lti_third_party_cookies'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
```bash
|
19
|
+
$ bundle install
|
20
|
+
```
|
21
|
+
|
13
22
|
## Usage
|
14
23
|
|
15
24
|
Choose the Rails controller action that's used to launch your tool and set cookies. Set the before_action callback
|
16
25
|
below to run on that action, and pass the data needed.
|
17
26
|
|
18
|
-
* the
|
19
|
-
|
20
|
-
* the
|
21
|
-
|
22
|
-
* the `launch_data` parameter is optional, and should contain
|
23
|
-
all needed form data that the tool requires to launch.
|
24
|
-
|
25
|
-
Usually, only query parameters *or* form data is needed, not both.
|
27
|
+
* `placement`: (required) Should be the Canvas placement that the tool was launched from, taken from the decoded id_token's `https://www.instructure.com/placement` claim.
|
28
|
+
* `window_type`: (optional) Set to `:new_window` to open the tool in a new tab or window, or to `:popup` to open in a popup window.Defaults to `:new_window`.
|
29
|
+
* `width`: (optional) The width the popup window should be, in px. User has the discretion to ignore this. Only valid with window_type: popup. Defaults to 800px.
|
30
|
+
* `height`: (optional) The height the popup window should be, in px. User has the discretion to ignore this. Only valid with window_type: popup. Defaults to 600px.
|
26
31
|
|
27
32
|
```ruby
|
28
|
-
include
|
33
|
+
include CanvasLtiThirdPartyCookies::SafariLaunch
|
29
34
|
#...
|
30
35
|
before_action -> {
|
31
|
-
|
36
|
+
placement = decoded_id_token['https://www.instructure.com/placement']
|
37
|
+
handle_safari_launch(placement: placement, window_type: :popup)
|
32
38
|
}
|
33
39
|
```
|
34
40
|
|
35
|
-
##
|
36
|
-
Add this line to your application's Gemfile:
|
37
|
-
|
38
|
-
```ruby
|
39
|
-
# Set 3rd party cookies in Safari
|
40
|
-
gem 'canvas_lti_third_party_cookies', '~> 0.2.0'
|
41
|
-
```
|
41
|
+
## Testing
|
42
42
|
|
43
|
-
And then execute:
|
44
43
|
```bash
|
45
|
-
$
|
44
|
+
$ rails test
|
46
45
|
```
|
47
46
|
|
48
|
-
##
|
47
|
+
## Publishing New Versions
|
49
48
|
|
50
|
-
|
51
|
-
|
52
|
-
|
49
|
+
1. Bump the version in `lib/canvas_lti_third_party_cookies/version.rb`.
|
50
|
+
2. Commit, push, and merge that change.
|
51
|
+
3. `rake install`
|
52
|
+
4. `gem push pkg/canvas_lti_third_party_cookies-<version>.gem`
|
53
|
+
- note that this will only work if you have access
|
@@ -0,0 +1,60 @@
|
|
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
|
+
#
|
9
|
+
# `placement`: (required) Should be the Canvas placement that
|
10
|
+
# the tool was launched from, taken from the decoded id_token's
|
11
|
+
# `https://www.instructure.com/placement` claim.
|
12
|
+
#
|
13
|
+
# `window_type`: (optional) Set to `:new_window` to open the tool in a
|
14
|
+
# new tab or window, or to `:popup` to open in a popup window.
|
15
|
+
# Defaults to `:new_window`.
|
16
|
+
#
|
17
|
+
# `width`: (optional) The width the popup window should be, in px. User
|
18
|
+
# has the discretion to ignore this. Only valid with window_type: popup.
|
19
|
+
# Defaults to 800px.
|
20
|
+
#
|
21
|
+
# `height`: (optional) The height the popup window should be, in px. User
|
22
|
+
# has the discretion to ignore this. Only valid with window_type: popup.
|
23
|
+
# Defaults to 600px.
|
24
|
+
#
|
25
|
+
# example:
|
26
|
+
# include CanvasLtiThirdPartyCookies::SafariLaunch
|
27
|
+
# ...
|
28
|
+
# before_action -> {
|
29
|
+
# placement = decoded_id_token['https://www.instructure.com/placement']
|
30
|
+
# handle_safari_launch(placement: placement, window_type: :popup)
|
31
|
+
# }
|
32
|
+
def handle_safari_launch(placement:, window_type: :new_window, width: nil, height: nil)
|
33
|
+
raise ArgumentError.new("window_type must be either :new_window or :popup") unless [:new_window, :popup].include? window_type
|
34
|
+
return unless is_safari?
|
35
|
+
return if is_full_window_launch?
|
36
|
+
|
37
|
+
render(
|
38
|
+
'canvas_lti_third_party_cookies/prep_for_full_window_launch',
|
39
|
+
locals: {
|
40
|
+
launch_url: request.base_url + request.fullpath,
|
41
|
+
placement: placement,
|
42
|
+
window_type: window_type.to_s,
|
43
|
+
width: width || 800,
|
44
|
+
height: height || 600
|
45
|
+
}
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def is_full_window_launch?
|
52
|
+
params[:full_win_launch_requested].present?
|
53
|
+
end
|
54
|
+
|
55
|
+
def is_safari?
|
56
|
+
browser = Browser.new(request.headers["User-Agent"])
|
57
|
+
# detect both MacOS and iOS Safari
|
58
|
+
browser.safari? || (browser.webkit? && browser.platform.ios?)
|
59
|
+
end
|
60
|
+
end
|
@@ -1,36 +1,25 @@
|
|
1
1
|
<%= javascript_tag do -%>
|
2
|
-
const requestStorageAccess = () => {
|
3
|
-
document
|
4
|
-
.requestStorageAccess()
|
5
|
-
.then(() => redirectToSetCookies())
|
6
|
-
.catch(() => requestFullWindowLaunch());
|
7
|
-
};
|
8
|
-
|
9
2
|
const requestFullWindowLaunch = () => {
|
3
|
+
console.log("clicked")
|
10
4
|
window.parent.postMessage(
|
11
5
|
{
|
12
6
|
messageType: "requestFullWindowLaunch",
|
13
|
-
data:
|
7
|
+
data: {
|
8
|
+
url: "<%= launch_url %>",
|
9
|
+
placement: "<%= placement %>",
|
10
|
+
launchType: "<%= window_type %>",
|
11
|
+
launchOptions: {
|
12
|
+
width: <%= width %>,
|
13
|
+
height: <%= height %>
|
14
|
+
}
|
15
|
+
}
|
14
16
|
},
|
15
17
|
"*"
|
16
18
|
);
|
17
19
|
};
|
18
|
-
|
19
|
-
const redirectToSetCookies = () => {
|
20
|
-
const form = document.getElementById("relaunch");
|
21
|
-
form.submit();
|
22
|
-
};
|
23
|
-
|
24
20
|
document.addEventListener("DOMContentLoaded", () => {
|
25
|
-
|
26
|
-
document
|
27
|
-
.hasStorageAccess()
|
28
|
-
.then((hasStorageAccess) => {
|
29
|
-
if (hasStorageAccess) {
|
30
|
-
redirectToSetCookies();
|
31
|
-
}
|
32
|
-
})
|
33
|
-
.catch((err) => console.error(err));
|
21
|
+
console.log("loaded")
|
22
|
+
document.getElementById("request").addEventListener("click", requestFullWindowLaunch)
|
34
23
|
});
|
35
24
|
<% end %>
|
36
25
|
<style type="text/css">
|
@@ -40,7 +29,7 @@
|
|
40
29
|
height: 100%;
|
41
30
|
font-family: "Segoe UI", Frutiger, "Frutiger Linotype", "Dejavu Sans", "Helvetica Neue", Arial, sans-serif;
|
42
31
|
font-size: 1.1em;
|
43
|
-
padding: 0
|
32
|
+
padding: 0 24px;
|
44
33
|
}
|
45
34
|
|
46
35
|
.flex-item {
|
@@ -48,8 +37,8 @@
|
|
48
37
|
text-align: center;
|
49
38
|
}
|
50
39
|
|
51
|
-
.first {
|
52
|
-
margin-top:
|
40
|
+
.flex-container div:first-child {
|
41
|
+
margin-top: 24px;
|
53
42
|
}
|
54
43
|
|
55
44
|
p {
|
@@ -87,7 +76,7 @@
|
|
87
76
|
</style>
|
88
77
|
|
89
78
|
<div class="flex-container">
|
90
|
-
<div class="flex-item
|
79
|
+
<div class="flex-item">
|
91
80
|
<img id="safari-logo" src="https://upload.wikimedia.org/wikipedia/commons/5/52/Safari_browser_logo.svg" alt="Safari Logo" />
|
92
81
|
</div>
|
93
82
|
|
@@ -96,18 +85,15 @@
|
|
96
85
|
</div>
|
97
86
|
|
98
87
|
<div class="flex-item">
|
99
|
-
<p>Safari
|
100
|
-
<p>
|
101
|
-
|
88
|
+
<p>Safari blocks third-party cookies by default, which this app relies on for sign-in features.</p>
|
89
|
+
<p>Launching this app in a <%if window_type == "popup"%>popup window<%else%>new tab<%end%>, separate from Canvas, will allow the app to set its own cookies.</p>
|
90
|
+
<%if window_type == "popup"%>
|
91
|
+
<p>If the app doesn't appear, make sure Safari is not blocking popups.</p>
|
92
|
+
<%end%>
|
102
93
|
</div>
|
103
94
|
|
104
95
|
<div class="flex-item">
|
105
|
-
<button id="request">
|
96
|
+
<button id="request">Open <%if window_type == "popup"%>Popup<%else%>in New Tab<%end%></button>
|
106
97
|
</div>
|
107
98
|
<div>
|
108
|
-
</div>
|
109
|
-
<form id="relaunch" method="POST" action="<%= relaunch_url %>">
|
110
|
-
<% launch_data.each do |key, value| -%>
|
111
|
-
<%= hidden_field_tag key, value %>
|
112
|
-
<% end -%>
|
113
|
-
</form>
|
99
|
+
</div>
|
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: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Xander Moffatt
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-03-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -75,13 +75,12 @@ files:
|
|
75
75
|
- LICENSE
|
76
76
|
- README.md
|
77
77
|
- Rakefile
|
78
|
-
- app/controllers/concerns/
|
78
|
+
- app/controllers/concerns/canvas_lti_third_party_cookies/safari_launch.rb
|
79
|
+
- app/views/canvas_lti_third_party_cookies/prep_for_full_window_launch.erb
|
79
80
|
- app/views/layouts/application.html.erb
|
80
|
-
-
|
81
|
-
-
|
82
|
-
- lib/
|
83
|
-
- lib/lti_third_party_cookies/engine.rb
|
84
|
-
- lib/lti_third_party_cookies/version.rb
|
81
|
+
- lib/canvas_lti_third_party_cookies.rb
|
82
|
+
- lib/canvas_lti_third_party_cookies/engine.rb
|
83
|
+
- lib/canvas_lti_third_party_cookies/version.rb
|
85
84
|
homepage: https://gerrit.instructure.com/#/admin/projects/lti_third_party_cookies
|
86
85
|
licenses:
|
87
86
|
- MIT
|
@@ -1,66 +0,0 @@
|
|
1
|
-
require 'browser'
|
2
|
-
|
3
|
-
module LtiThirdPartyCookies::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 LtiThirdPartyCookies::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
|
-
browser = Browser.new(request.headers["User-Agent"])
|
22
|
-
# detect both MacOS and iOS Safari
|
23
|
-
return unless browser.safari? || (browser.webkit? && browser.platform.ios?)
|
24
|
-
|
25
|
-
# Safari launch #4: Storage Access has been granted,
|
26
|
-
# so launch the app normally.
|
27
|
-
if params[:storage_access_status].present?
|
28
|
-
# remove from request directly instead of only from `params` so that
|
29
|
-
# the tool *really* doesn't have to worry about this being present
|
30
|
-
request.request_parameters.delete(:storage_access_status)
|
31
|
-
return
|
32
|
-
end
|
33
|
-
|
34
|
-
# Safari launch #2: Full-window launch, solely for first-party user interaction.
|
35
|
-
# During a full-window launch, Canvas provides a :platform_redirect_url that
|
36
|
-
# will launch the tool again within an iframe in Canvas. (#3)
|
37
|
-
if params[:platform_redirect_url].present?
|
38
|
-
return render(
|
39
|
-
'lti_third_party_cookies/full_window_launch',
|
40
|
-
locals: { platform_redirect_url: params[:platform_redirect_url] }
|
41
|
-
)
|
42
|
-
end
|
43
|
-
|
44
|
-
# Safari launch #1: request Storage Access, then relaunch the tool. (#4)
|
45
|
-
# If request fails, request a full window launch instead. (#2)
|
46
|
-
# Safari launch #3: Relaunched by Canvas after full-window launch,
|
47
|
-
# request Storage Access and then relaunch the tool. (#4)
|
48
|
-
# Pass along any parameters provided by the tool that are needed to launch correctly,
|
49
|
-
# and tell the tool that it has Storage Access.
|
50
|
-
render(
|
51
|
-
'lti_third_party_cookies/request_storage_access',
|
52
|
-
locals: {
|
53
|
-
launch_url: launch_url,
|
54
|
-
relaunch_url: relaunch_url(launch_url, launch_params),
|
55
|
-
launch_data: launch_data.merge({ storage_access_status: "granted"})
|
56
|
-
}
|
57
|
-
)
|
58
|
-
end
|
59
|
-
|
60
|
-
private
|
61
|
-
|
62
|
-
def relaunch_url(launch_url, launch_params)
|
63
|
-
return launch_url if launch_params.empty?
|
64
|
-
"#{launch_url}?#{launch_params.to_query}"
|
65
|
-
end
|
66
|
-
end
|
@@ -1,79 +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
|
-
</div>
|
74
|
-
|
75
|
-
<div class="flex-item">
|
76
|
-
<button id="redirect">Relaunch App in Canvas</button>
|
77
|
-
</div>
|
78
|
-
<div>
|
79
|
-
</div>
|