Skip to content

Commit

Permalink
Merge pull request #58 from stlk/feature/3rd-party-cookies-check
Browse files Browse the repository at this point in the history
Introduce 3rd party cookie check
  • Loading branch information
gavinballard committed Mar 24, 2020
2 parents 8291cbb + 516e0a9 commit 5eda794
Show file tree
Hide file tree
Showing 7 changed files with 219 additions and 20 deletions.
1 change: 0 additions & 1 deletion .travis.yml
Expand Up @@ -3,7 +3,6 @@ sudo: false
language: python

python:
- "3.4"
- "3.5"
- "3.6"
- "3.7"
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
@@ -1,6 +1,13 @@
# Change Log
All notable changes to this project will be documented in this file.

## Unreleased
### Added
- Detection if third party cookies are allowed

### Removed
- Dropped support for Python 3.4

## 0.9.0 - 2019-12-22
### Added
- Support for Django 3 by removing Python 2 support
Expand Down
131 changes: 131 additions & 0 deletions shopify_auth/templates/shopify_auth/check_cookies.html
@@ -0,0 +1,131 @@
<link rel="stylesheet" href="https://sdks.shopifycdn.com/polaris/2.0.0/polaris.min.css" />

<div class="Polaris-Page">
<div class="Polaris-Page__Content">
<div class="Polaris-Layout">
<div class="Polaris-Layout__Section">
<div class="Polaris-Stack Polaris-Stack--vertical">
<div class="Polaris-Stack__Item">
<div class="Polaris-EmptyState">
<div class="Polaris-EmptyState__Section">
<div class="Polaris-EmptyState__DetailsContainer">
<div class="Polaris-EmptyState__DetailsX">

<div id="checking-cookies">
<div class="Polaris-TextContainer">
<h1 class="Polaris-DisplayText Polaris-DisplayText--sizeMedium">
Verifying your cookie configuration...
</h1>
</div>
</div>

<div id="cookies-disabled" style="display: none;">
<div class="Polaris-TextContainer">
<h1 class="Polaris-DisplayText Polaris-DisplayText--sizeSmall">
It looks like you have blocked third-party cookies and we can't authenticate you.
<br />
How to fix it? 🛠️
</h1>
<div class="Polaris-EmptyState__Content">
<ul>
<li>Try refreshing this page in your browser</li>
<li>Try to access our app in a different browser</li>
<li>Allow third-party cookies in your browser</li>
</ul>
</div>
</div>
</div>

<div id="check-failed" style="display: none;">
<div class="Polaris-TextContainer">
<h1 class="Polaris-DisplayText Polaris-DisplayText--sizeMedium">
Sorry. There was a problem verifying your cookie configuration.
</h1>
<div class="Polaris-EmptyState__Content">
<p>
Something went wrong detecting third-party cookies.
<br />
This happens occasionally. Please try reloading the page.
</p>
</div>
</div>
<div class="Polaris-EmptyState__Actions">
<div class="Polaris-Stack Polaris-Stack--alignmentCenter">
<div class="Polaris-Stack__Item">
<button type="button" onclick="document.getElementById('login').submit();" class="Polaris-Button Polaris-Button--primary Polaris-Button--sizeLarge">
<span class="Polaris-Button__Content">
<span class="Polaris-Button__Icon"></span>
<span>Try login anyway</span>
</span>
</button>
</div>
</div>
</div>
</div>

</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

<form id="login" action="{{login_url}}" method="POST">
{% csrf_token %}
<input name="shop" value="{{shop}}" type="hidden" />
</form>

<script>
window.detect = {
cookies_third_party: {
enabled: function () {
return (
"enabled" == window.cookies_third_party ||
("disabled" != window.cookies_third_party && null)
);
},
cookie_has_now_been_set_by_third_party: function () {
cookie_check_script_element = document.createElement("script")
cookie_check_script_element.setAttribute("src", "{{check_cookie_url}}")
body_object = document.getElementsByTagName("body")[0]
body_object.appendChild(cookie_check_script_element);
},
cookies_test_finished: function (e) {
window.cookies_third_party = e ? "enabled" : "disabled";
}
},
}

document.addEventListener("DOMContentLoaded", function (event) {

window.cookies_third_party = "unknown"
window.detect.cookies_third_party.cookie_has_now_been_set_by_third_party();

window.cookie_check_interval = setInterval(function () {
if (window.detect.cookies_third_party.enabled() == true) {
document.getElementById("login").submit();
clearInterval(window.cookie_check_interval);
}
else if (window.detect.cookies_third_party.enabled() == false) {
document.getElementById("checking-cookies").style.display = "none";
document.getElementById("cookies-disabled").style.display = "block";
document.getElementById("check-failed").style.display = "none";
clearInterval(window.cookie_check_interval);
}
}, 50);

setTimeout(function () {
if (window.cookies_third_party === "unknown") {
document.getElementById("checking-cookies").style.display = "none";
document.getElementById("cookies-disabled").style.display = "none";
document.getElementById("check-failed").style.display = "block";
}
clearInterval(window.cookie_check_interval);
}, 4000);
});

</script>
56 changes: 43 additions & 13 deletions shopify_auth/templates/shopify_auth/login.html
@@ -1,15 +1,45 @@
<h1>Log In</h1>
<link rel="stylesheet" href="https://sdks.shopifycdn.com/polaris/2.0.0/polaris.min.css" />

<form method="POST">
{% csrf_token %}
<div class="Polaris-EmptyState">
<div class="Polaris-EmptyState__Section">
<div class="Polaris-EmptyState__DetailsContainer">
<div class="Polaris-EmptyState__Details">
<div class="Polaris-TextContainer">
<p class="Polaris-DisplayText Polaris-DisplayText--sizeMedium">{{SHOPIFY_APP_NAME}}</p>
<div class="Polaris-EmptyState__Content">
<p>
Enter your shop domain to log in or install this app.
</p>
</div>
</div>
<div class="Polaris-EmptyState__Actions">
<form method="POST">
{% csrf_token %}
<div style="margin-bottom: 2rem;">
<div class="Polaris-Labelled__LabelWrapper">
<div class="Polaris-Label">
<label for="shop" class="Polaris-Label__Text">Shop URL</label>
</div>
</div>
<div class="Polaris-TextField">
<input id="shop" name="shop" class="Polaris-TextField__Input" type="text" />
<div class="Polaris-TextField__Backdrop"></div>
</div>
</div>
<div class="Polaris-ButtonGroup">
<div class="Polaris-ButtonGroup__Item">
<button type="submit"
class="Polaris-Button Polaris-Button--primary Polaris-Button--sizeLarge ">
<span class="Polaris-Button__Content">
<span>Install</span>
</span>
</button>
</div>
</div>

<p>
<label for="shop">Shop URL</label>
</p>

<p>
<input id="shop" name="shop" type="text" />
</p>

<input type="submit" value="Install" />
</form>
</form>
</div>
</div>
</div>
</div>
</div>
16 changes: 13 additions & 3 deletions shopify_auth/tests/test_views.py
Expand Up @@ -29,9 +29,6 @@ def test_authenticate_view(self):
response = self.client.post('/authenticate/')
self.assertEqual(response.status_code, 302)

response = self.client.get('/?shop=test.myshopify.com')
self.assertContains(response, 'window.top.location.href = "https://test.myshopify.com/admin/oauth/authorize')

# Dev mode so token does not need to be valid
settings.SHOPIFY_APP_DEV_MODE = True
response = self.client.get('/authenticate/?shop=test.myshopify.com')
Expand All @@ -40,6 +37,15 @@ def test_authenticate_view(self):
self.assertEqual(self.client.session['_auth_user_backend'], 'shopify_auth.backends.ShopUserBackend')
self.assertIsNot(self.client.session['_auth_user_hash'], None)

def test_shows_cookie_check_for_embedded_app_with_shop_param(self):
response = self.client.get('/?shop=test.myshopify.com')
self.assertTemplateUsed(response, "shopify_auth/check_cookies.html")

def test_authenticates_standalone_app_with_shop_param(self):
with self.settings(SHOPIFY_APP_IS_EMBEDDED=False):
response = self.client.get('/?shop=test.myshopify.com')
self.assertEqual(response.status_code, 302)

def test_redirect_to_view(self):
"""
Test that return_address is persisted through login flow.
Expand All @@ -51,3 +57,7 @@ def test_redirect_to_view(self):
response = self.client.get('/authenticate/?shop=test.myshopify.com')
self.assertEqual(response.status_code, 302)
self.assertEqual(response.url, 'other-view')

def test_check_cookie(self):
response = self.client.get('/check-cookie')
self.assertContains(response, "window.detect.cookies_third_party.cookies_test_finished(false);")
1 change: 1 addition & 0 deletions shopify_auth/urls.py
Expand Up @@ -6,4 +6,5 @@
url(r'^finalize/$', views.finalize),
url(r'^authenticate/$', views.authenticate),
url(r'^$', views.login),
url(r'^check-cookie$', views.check_cookie),
]
27 changes: 24 additions & 3 deletions shopify_auth/views.py
Expand Up @@ -3,7 +3,7 @@
from django import VERSION as DJANGO_VERSION
from django.conf import settings
from django.contrib import auth
from django.http.response import HttpResponseRedirect
from django.http.response import HttpResponseRedirect, HttpResponse
from django.shortcuts import render, resolve_url

from .decorators import anonymous_required
Expand All @@ -14,6 +14,7 @@
from django.core.urlresolvers import reverse

SESSION_REDIRECT_FIELD_NAME = 'shopify_auth_next'
THIRD_PARTY_COOKIE_NAME = 'a_third_party_cookie'

def get_return_address(request):
return request.session.get(SESSION_REDIRECT_FIELD_NAME) or request.GET.get(auth.REDIRECT_FIELD_NAME) or resolve_url(settings.LOGIN_REDIRECT_URL)
Expand All @@ -25,15 +26,35 @@ def login(request, *args, **kwargs):
# as a result of submitting the login form.
shop = request.POST.get('shop', request.GET.get('shop'))

# If the shop parameter has already been provided, attempt to authenticate immediately.
# If the merchant is authenticating from Shopify Admin, make sure cookies work.
if shop:
if settings.SHOPIFY_APP_IS_EMBEDDED:
response = render(request, "shopify_auth/check_cookies.html", {
'SHOPIFY_APP_NAME': settings.SHOPIFY_APP_NAME,
'shop': shop,
'login_url': reverse(authenticate),
'check_cookie_url': reverse(check_cookie),
})
response.set_cookie(THIRD_PARTY_COOKIE_NAME, 'true', secure=True)
return response

# If the shop parameter has already been provided, attempt to authenticate immediately.
return authenticate(request, *args, **kwargs)

return render(request, "shopify_auth/login.html", {
'SHOPIFY_APP_NAME': settings.SHOPIFY_APP_NAME
'SHOPIFY_APP_NAME': settings.SHOPIFY_APP_NAME,
'shop': shop,
})


def check_cookie(request):
is_cookie_present = request.COOKIES.get(THIRD_PARTY_COOKIE_NAME, 'false')
return HttpResponse(
'window.detect.cookies_third_party.cookies_test_finished({});'.format(is_cookie_present),
content_type='application/javascript; charset=UTF-8;'
)


@anonymous_required
def authenticate(request, *args, **kwargs):
shop = request.POST.get('shop', request.GET.get('shop'))
Expand Down

0 comments on commit 5eda794

Please sign in to comment.