canvas_lti_third_party_cookies 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1333ca5982732fe82a7282906c08a49efdc753d3b3ea2d87e927189917a71de
4
- data.tar.gz: 013f64730fdca2e2490b22d7b145ccc642037ad8c30c6893156958bc1686dd2c
3
+ metadata.gz: 8d232d4448ee62b75121b20f70143f70f75e0ea38a83408b3ed42bb51ce41247
4
+ data.tar.gz: 44605463e21036992d5e0dadc1ac7180384e3f4186917e0139fe586c9451c936
5
5
  SHA512:
6
- metadata.gz: 845e6dde86192f515409d5769c2018867384e032e866694e999b8b0d37f02cfd17e5e5e8a04720a66c58aa610a9fd29d2cd9e89b9441c1b5a8a1ff7b8674318a
7
- data.tar.gz: ce10870144e786fbc0c8100e1428aeaa449da381378ddd282fe8087fc40f225c5bef22a35e66ba46a780c2daeb86bf84161c14ff87727f69f17dc9eaf62b7901
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 works though requires jumping through some fun hoops.
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
- This gem wraps the Safari cookie dance in a Rails engine that can be easily used with one before_action callback. It is built for use with Canvas,
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 `launch_url` parameter is required, which should be the route
19
- that launches the tool.
20
- * the `launch_params` parameter is optional, and should contain
21
- all needed query parameters that the tool requires to launch.
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 LtiThirdPartyCookies::SafariLaunch
33
+ include CanvasLtiThirdPartyCookies::SafariLaunch
29
34
  #...
30
35
  before_action -> {
31
- handle_safari_launch(launch_url: action_url, launch_params: { foo: bar }, launch_data: { foo: baz })
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
- ## Installation
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
- $ bundle install
44
+ $ rails test
46
45
  ```
47
46
 
48
- ## Testing
47
+ ## Publishing New Versions
49
48
 
50
- ```bash
51
- $ rails test
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: "<%= launch_url %>",
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
- document.getElementById("request").addEventListener("click", requestStorageAccess)
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 75px 0 75px;
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: 50px;
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 first">
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 requires your interaction with this app before logging you in.</p>
100
- <p>A dialog may appear asking you to allow this app to use cookies while browsing Canvas.</p>
101
- <p>For the best experience, click Allow.</p>
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">Continue to App</button>
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>
@@ -0,0 +1,4 @@
1
+ require "canvas_lti_third_party_cookies/engine"
2
+
3
+ module CanvasLtiThirdPartyCookies
4
+ end
@@ -0,0 +1,5 @@
1
+ module CanvasLtiThirdPartyCookies
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace CanvasLtiThirdPartyCookies
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module CanvasLtiThirdPartyCookies
2
+ VERSION = '0.4.0'
3
+ 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.2.1
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-02-11 00:00:00.000000000 Z
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/lti_third_party_cookies/safari_launch.rb
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
- - app/views/lti_third_party_cookies/full_window_launch.erb
81
- - app/views/lti_third_party_cookies/request_storage_access.erb
82
- - lib/lti_third_party_cookies.rb
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>
@@ -1,5 +0,0 @@
1
- require "lti_third_party_cookies/engine"
2
- require_relative "../app/controllers/concerns/lti_third_party_cookies/safari_launch"
3
-
4
- module LtiThirdPartyCookies
5
- end
@@ -1,5 +0,0 @@
1
- module LtiThirdPartyCookies
2
- class Engine < ::Rails::Engine
3
- isolate_namespace LtiThirdPartyCookies
4
- end
5
- end
@@ -1,3 +0,0 @@
1
- module LtiThirdPartyCookies
2
- VERSION = '0.2.1'
3
- end