blog.notmyidea.org/implementing-cors-in-cornice.html
2019-11-17 19:15:12 +01:00

399 lines
No EOL
27 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1">
<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />
<title>Implementing CORS in Cornice - Alexis - Carnets en ligne</title>
<meta charset="utf-8" />
<link href="https://blog.notmyidea.org/feeds/all.atom.xml" type="application/atom+xml" rel="alternate" title="Alexis - Carnets en ligne Full Atom Feed" />
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/poole.css"/>
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/syntax.css"/>
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/lanyon.css"/>
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=PT+Serif:400,400italic,700%7CPT+Sans:400">
<link rel="stylesheet" href="https://blog.notmyidea.org/theme/css/styles.css"/>
<style>
h1 {
font-family: "Avant Garde", Avantgarde, "Century Gothic", CenturyGothic, "AppleGothic", sans-serif;
padding: 80px 50px;
text-align: center;
text-transform: uppercase;
text-rendering: optimizeLegibility;
color: #202020;
letter-spacing: .1em;
text-shadow:
-1px -1px 1px #111,
2px 2px 1px #eaeaea;
}
#main {
text-align: justify;
text-justify: inter-word;
}
#main h1 {
padding: 10px;
}
.post-headline {
padding: 15px;
}
</style>
</head>
<body>
<!-- Target for toggling the sidebar `.sidebar-checkbox` is for regular
styles, `#sidebar-checkbox` for behavior. -->
<input type="checkbox" class="sidebar-checkbox" id="sidebar-checkbox">
<!-- Toggleable sidebar -->
<div class="sidebar" id="sidebar">
<div class="sidebar-item">
<div class="profile">
<img src="https://blog.notmyidea.org/theme/img/profile.png"/>
</div>
</div>
<nav class="sidebar-nav">
<a class="sidebar-nav-item" href="/">Articles</a>
<a class="sidebar-nav-item" href="https://www.vieuxsinge.com">Brasserie du Vieux Singe</a>
<a class="sidebar-nav-item" href="http://blog.notmyidea.org/pages/about.html">A propos</a>
<a class="sidebar-nav-item" href="https://twitter.com/ametaireau">Messages courts</a>
<a class="sidebar-nav-item" href="https://github.com/almet">Code</a>
</nav>
</div> <div class="wrap">
<div class="masthead">
<div class="container">
<h3 class="masthead-title">
<a href="https://blog.notmyidea.org/" title="Home">Alexis - Carnets en ligne</a>
</h3>
</div>
</div>
<div class="container content">
<div id="main" class="posts">
<h1 class="post-title">Implementing CORS in Cornice</h1>
<span class="post-date">04 février 2013, dans <a class="no-color" href="category/technologie.html">Technologie</a></span>
<img id="illustration" src="" />
<div class="post article">
<div id="toc_container">
<div class="toc">
<ul>
<li><a href="#implementing-cors-in-cornice">Implementing CORS in Cornice</a><ul>
<li><a href="#you-want-an-icecream-go-ask-your-dad-first">You want an icecream? Go ask your dad first.</a></li>
<li><a href="#a-word-about-security">A word about security</a></li>
<li><a href="#how-this-is-different-from-jsonp">How this is different from JSONP?</a></li>
<li><a href="#using-cors-in-cornice">Using CORS in Cornice</a></li>
</ul>
</li>
<li><a href="#and-then-you-do-something-with-it">and then you do something with it</a><ul>
<li><a href="#headers">Headers</a></li>
<li><a href="#comparison-with-other-implementations">Comparison with other implementations</a></li>
<li><a href="#resources">Resources</a></li>
</ul>
</li>
</ul>
</div>
</div>
<h1>🌟</h1>
<div class="note">
<div class="admonition-title">
Note
</div>
I'm cross-posting [on the mozilla services
weblog](https://blog.mozilla.org/services/). Since this is the first
time we're doing that, I though it could be useful to point you there.
Check it out and expect more technical articles there in the future.
</div>
<p>For security reasons, it's not possible to do cross-domain requests. In
other words, if you have a page served from the domain lolnet.org, it
will not be possible for it to get data from notmyidea.org.</p>
<p>Well, it's possible, using tricks and techniques like
<a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a>, but that doesn't work all
the time (see <a href="#how-this-is-different-from-jsonp">the section below</a>). I
remember myself doing some simple proxies on my domain server to be able
to query other's API.</p>
<p>Thankfully, there is a nicer way to do this, namely, "Cross Origin
Resource-Sharing", or <a href="http://www.w3.org/TR/cors/">CORS</a>.</p>
<h2 id="you-want-an-icecream-go-ask-your-dad-first">You want an icecream? Go ask your dad first.</h2>
<p>If you want to use CORS, you need the API you're querying to support it;
on the server side.</p>
<p>The HTTP server need to answer to the OPTIONS verb, and with the
appropriate response headers.</p>
<p>OPTIONS is sent as what the authors of the spec call a "preflight
request"; just before doing a request to the API, the <em>User-Agent</em> (the
browser most of the time) asks the permission to the resource, with an
OPTIONS call.</p>
<p>The server answers, and tell what is available and what isn't:</p>
<p><img alt="The CORS flow (from the HTML5 CORS tutorial)" src="images/cors_flow.png"></p>
<ul>
<li>
<p>1a. The User-Agent, rather than doing the call directly, asks the
server, the API, the permission to do the request. It does so with
the following headers:</p>
<ul>
<li><strong>Access-Control-Request-Headers</strong>, contains the headers the
User-Agent want to access.</li>
<li><strong>Access-Control-Request-Method</strong> contains the method the
User-Agent want to access.</li>
</ul>
</li>
<li>
<p>1b. The API answers what is authorized:</p>
<ul>
<li><strong>Access-Control-Allow-Origin</strong> the origin that's accepted. Can
be * or the domain name.</li>
<li><strong>Access-Control-Allow-Methods</strong> a <em>list</em> of allowed methods.
This can be cached. Note than the request asks permission for
one method and the server should return a list of accepted
methods.</li>
<li><strong>Access-Allow-Headers</strong> a list of allowed headers, for all of
the methods, since this can be cached as well.</li>
</ul>
</li>
<li>
<ol>
<li>The User-Agent can do the "normal" request.</li>
</ol>
</li>
</ul>
<p>So, if you want to access the /icecream resource, and do a PUT there,
you'll have the following flow:</p>
<div class="highlight"><pre><span></span><span class="o">&gt;</span> <span class="k">OPTIONS</span> <span class="o">/</span><span class="n">icecream</span>
<span class="o">&gt;</span> <span class="k">Access</span><span class="o">-</span><span class="n">Control</span><span class="o">-</span><span class="n">Request</span><span class="o">-</span><span class="n">Methods</span> <span class="o">=</span> <span class="n">PUT</span>
<span class="o">&gt;</span> <span class="n">Origin</span><span class="p">:</span> <span class="n">notmyidea</span><span class="p">.</span><span class="n">org</span>
<span class="o">&lt;</span> <span class="k">Access</span><span class="o">-</span><span class="n">Control</span><span class="o">-</span><span class="n">Allow</span><span class="o">-</span><span class="n">Origin</span> <span class="o">=</span> <span class="n">notmyidea</span><span class="p">.</span><span class="n">org</span>
<span class="o">&lt;</span> <span class="k">Access</span><span class="o">-</span><span class="n">Control</span><span class="o">-</span><span class="n">Allow</span><span class="o">-</span><span class="n">Methods</span> <span class="o">=</span> <span class="n">PUT</span><span class="p">,</span><span class="k">GET</span><span class="p">,</span><span class="k">DELETE</span>
<span class="mi">200</span> <span class="n">OK</span>
</pre></div>
<p>You can see that we have an Origin Header in the request, as well as a
Access-Control-Request-Methods. We're here asking if we have the right,
as notmyidea.org, to do a PUT request on /icecream.</p>
<p>And the server tells us that we can do that, as well as GET and DELETE.</p>
<p>I'll not cover all the details of the CORS specification here, but bear
in mind than with CORS, you can control what are the authorized methods,
headers, origins, and if the client is allowed to send authentication
information or not.</p>
<h2 id="a-word-about-security">A word about security</h2>
<p>CORS is not an answer for every cross-domain call you want to do,
because you need to control the service you want to call. For instance,
if you want to build a feed reader and access the feeds on different
domains, you can be pretty much sure that the servers will not implement
CORS, so you'll need to write a proxy yourself, to provide this.</p>
<p>Secondly, if misunderstood, CORS can be insecure, and cause problems.
Because the rules apply when a client wants to do a request to a server,
you need to be extra careful about who you're authorizing.</p>
<p>An incorrectly secured CORS server can be accessed by a malicious client
very easily, bypassing network security. For instance, if you host a
server on an intranet that is only available from behind a VPN but
accepts every cross-origin call. A bad guy can inject javascript into
the browser of a user who has access to your protected server and make
calls to your service, which is probably not what you want.</p>
<h2 id="how-this-is-different-from-jsonp">How this is different from JSONP?</h2>
<p>You may know the <a href="http://en.wikipedia.org/wiki/JSONP">JSONP</a> protocol.
JSONP allows cross origin, but for a particular use case, and does have
some drawbacks (for instance, it's not possible to do DELETEs or PUTs
with JSONP).</p>
<p>JSONP exploits the fact that it is possible to get information from
another domain when you are asking for javascript code, using the
\&lt;script> element.</p>
<blockquote>
<p>Exploiting the open policy for \&lt;script> elements, some pages use
them to retrieve JavaScript code that operates on dynamically
generated JSON-formatted data from other origins. This usage pattern
is known as JSONP. Requests for JSONP retrieve not JSON, but arbitrary
JavaScript code. They are evaluated by the JavaScript interpreter, not
parsed by a JSON parser.</p>
</blockquote>
<h2 id="using-cors-in-cornice">Using CORS in Cornice</h2>
<p>Okay, things are hopefully clearer about CORS, let's see how we
implemented it on the server-side.</p>
<p>Cornice is a toolkit that lets you define resources in python and takes
care of the heavy lifting for you, so I wanted it to take care of the
CORS support as well.</p>
<p>In Cornice, you define a service like this:</p>
<p>``` sourceCode python
from cornice import Service</p>
<p>foobar = Service(name="foobar", path="/foobar")</p>
<h1 id="and-then-you-do-something-with-it">and then you do something with it</h1>
<p>@foobar.get()
def get_foobar(request):
# do something with the request.</p>
<div class="highlight"><pre><span></span><span class="k">To</span><span class="w"> </span><span class="k">add</span><span class="w"> </span><span class="n">CORS</span><span class="w"> </span><span class="n">support</span><span class="w"> </span><span class="k">to</span><span class="w"> </span><span class="n">this</span><span class="w"> </span><span class="n">resource</span><span class="p">,</span><span class="w"> </span><span class="n">you</span><span class="w"> </span><span class="n">can</span><span class="w"> </span><span class="k">go</span><span class="w"> </span><span class="n">this</span><span class="w"> </span><span class="n">way</span><span class="p">,</span><span class="w"> </span><span class="k">with</span><span class="w"> </span><span class="n">the</span><span class="w"></span>
<span class="n">cors</span><span class="err">\</span><span class="n">_origins</span><span class="w"> </span><span class="k">parameter</span><span class="err">:</span><span class="w"></span>
<span class="err">```</span><span class="w"> </span><span class="n">sourceCode</span><span class="w"> </span><span class="n">python</span><span class="w"></span>
<span class="n">foobar</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">Service</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">&#39;foobar&#39;</span><span class="p">,</span><span class="w"> </span><span class="k">path</span><span class="o">=</span><span class="s1">&#39;/foobar&#39;</span><span class="p">,</span><span class="w"> </span><span class="n">cors_origins</span><span class="o">=</span><span class="p">(</span><span class="s1">&#39;*&#39;</span><span class="p">,))</span><span class="w"></span>
</pre></div>
<p>Ta-da! You have enabled CORS for your service. <strong>Be aware that you're
authorizing anyone to query your server, that may not be what you
want.</strong></p>
<p>Of course, you can specify a list of origins you trust, and you don't
need to stick with *, which means "authorize everyone".</p>
<h3 id="headers">Headers</h3>
<p>You can define the headers you want to expose for the service:</p>
<p>``` sourceCode python
foobar = Service(name='foobar', path='/foobar', cors_origins=('*',))</p>
<p>@foobar.get(cors_headers=('X-My-Header', 'Content-Type'))
def get_foobars_please(request):
return "some foobar for you"</p>
<div class="highlight"><pre><span></span><span class="nv">I</span><span class="s1">&#39;</span><span class="s">ve done some testing and it wasn</span><span class="s1">&#39;</span><span class="nv">t</span> <span class="nv">working</span> <span class="nv">on</span> <span class="nv">Chrome</span> <span class="nv">because</span> <span class="nv">I</span> <span class="nv">wasn</span><span class="s1">&#39;</span><span class="s">t</span>
<span class="nv">handling</span> <span class="nv">the</span> <span class="nv">headers</span> <span class="nv">the</span> <span class="nv">right</span> <span class="nv">way</span> <span class="ss">(</span><span class="nv">The</span> <span class="nv">missing</span> <span class="nv">one</span> <span class="nv">was</span> <span class="nv">Content</span><span class="o">-</span><span class="nv">Type</span>,
<span class="nv">that</span> <span class="nv">Chrome</span> <span class="nv">was</span> <span class="nv">asking</span> <span class="k">for</span><span class="ss">)</span>. <span class="nv">With</span> <span class="nv">my</span> <span class="nv">first</span> <span class="nv">version</span> <span class="nv">of</span> <span class="nv">the</span>
<span class="nv">implementation</span>, <span class="nv">I</span> <span class="nv">needed</span> <span class="nv">the</span> <span class="nv">service</span> <span class="nv">implementers</span> <span class="nv">to</span> <span class="nv">explicitely</span> <span class="nv">list</span>
<span class="nv">all</span> <span class="nv">the</span> <span class="nv">headers</span> <span class="nv">that</span> <span class="nv">should</span> <span class="nv">be</span> <span class="nv">exposed</span>. <span class="k">While</span> <span class="nv">this</span> <span class="nv">improves</span> <span class="nv">security</span>, <span class="nv">it</span>
<span class="nv">can</span> <span class="nv">be</span> <span class="nv">frustrating</span> <span class="k">while</span> <span class="nv">developing</span>.
<span class="nv">So</span> <span class="nv">I</span> <span class="nv">introduced</span> <span class="nv">an</span> <span class="nv">expose</span>\<span class="nv">_all</span>\<span class="nv">_headers</span> <span class="nv">flag</span>, <span class="nv">which</span> <span class="nv">is</span> <span class="nv">set</span> <span class="nv">to</span> <span class="nv">True</span> <span class="nv">by</span>
<span class="nv">default</span>, <span class="k">if</span> <span class="nv">the</span> <span class="nv">service</span> <span class="nv">supports</span> <span class="nv">CORS</span>.
### <span class="nv">Cookies</span> <span class="o">/</span> <span class="nv">Credentials</span>
<span class="nv">By</span> <span class="nv">default</span>, <span class="nv">the</span> <span class="nv">requests</span> <span class="nv">you</span> <span class="k">do</span> <span class="nv">to</span> <span class="nv">your</span> <span class="nv">API</span> <span class="nv">endpoint</span> <span class="nv">don</span><span class="s1">&#39;</span><span class="s">t include the</span>
<span class="nv">credential</span> <span class="nv">information</span> <span class="k">for</span> <span class="nv">security</span> <span class="nv">reasons</span>. <span class="k">If</span> <span class="nv">you</span> <span class="nv">really</span> <span class="nv">want</span> <span class="nv">to</span> <span class="k">do</span>
<span class="nv">that</span>, <span class="nv">you</span> <span class="nv">need</span> <span class="nv">to</span> <span class="nv">enable</span> <span class="nv">it</span> <span class="nv">using</span> <span class="nv">the</span> <span class="nv">cors</span>\<span class="nv">_credentials</span> <span class="nv">parameter</span>. <span class="nv">You</span>
<span class="nv">can</span> <span class="nv">activate</span> <span class="nv">this</span> <span class="nv">one</span> <span class="nv">on</span> <span class="nv">a</span> <span class="nv">per</span><span class="o">-</span><span class="nv">service</span> <span class="nv">basis</span> <span class="nv">or</span> <span class="nv">on</span> <span class="nv">a</span> <span class="nv">per</span><span class="o">-</span><span class="nv">method</span> <span class="nv">basis</span>.
### <span class="nv">Caching</span>
<span class="nv">When</span> <span class="nv">you</span> <span class="k">do</span> <span class="nv">a</span> <span class="nv">preflight</span> <span class="nv">request</span>, <span class="nv">the</span> <span class="nv">information</span> <span class="nv">returned</span> <span class="nv">by</span> <span class="nv">the</span> <span class="nv">server</span>
<span class="nv">can</span> <span class="nv">be</span> <span class="nv">cached</span> <span class="nv">by</span> <span class="nv">the</span> <span class="nv">User</span><span class="o">-</span><span class="nv">Agent</span> <span class="nv">so</span> <span class="nv">that</span> <span class="nv">it</span><span class="s1">&#39;</span><span class="s">s not redone before each</span>
<span class="nv">actual</span> <span class="nv">call</span>.
<span class="nv">The</span> <span class="nv">caching</span> <span class="nv">period</span> <span class="nv">is</span> <span class="nv">defined</span> <span class="nv">by</span> <span class="nv">the</span> <span class="nv">server</span>, <span class="nv">using</span> <span class="nv">the</span>
<span class="nv">Access</span><span class="o">-</span><span class="nv">Control</span><span class="o">-</span><span class="nv">Max</span><span class="o">-</span><span class="nv">Age</span> <span class="nv">header</span>. <span class="nv">You</span> <span class="nv">can</span> <span class="nv">configure</span> <span class="nv">this</span> <span class="nv">timing</span> <span class="nv">using</span> <span class="nv">the</span>
<span class="nv">cors</span>\<span class="nv">_max</span>\<span class="nv">_age</span> <span class="nv">parameter</span>.
### <span class="nv">Simplifying</span> <span class="nv">the</span> <span class="nv">API</span>
<span class="nv">We</span> <span class="nv">have</span> <span class="nv">cors</span>\<span class="nv">_headers</span>, <span class="nv">cors</span>\<span class="nv">_enabled</span>, <span class="nv">cors</span>\<span class="nv">_origins</span>, <span class="nv">cors</span>\<span class="nv">_credentials</span>,
<span class="nv">cors</span>\<span class="nv">_max</span>\<span class="nv">_age</span>, <span class="nv">cors</span>\<span class="nv">_expose</span>\<span class="nv">_all</span>\<span class="nv">_headers</span><span class="nv">a</span> <span class="nv">fair</span> <span class="nv">number</span> <span class="nv">of</span>
<span class="nv">parameters</span>. <span class="k">If</span> <span class="nv">you</span> <span class="nv">want</span> <span class="nv">to</span> <span class="nv">have</span> <span class="nv">a</span> <span class="nv">specific</span> <span class="nv">CORS</span><span class="o">-</span><span class="nv">policy</span> <span class="k">for</span> <span class="nv">your</span>
<span class="nv">services</span>, <span class="nv">that</span> <span class="nv">can</span> <span class="nv">be</span> <span class="nv">a</span> <span class="nv">bit</span> <span class="nv">tedious</span> <span class="nv">to</span> <span class="nv">pass</span> <span class="nv">these</span> <span class="nv">to</span> <span class="nv">your</span> <span class="nv">services</span> <span class="nv">all</span>
<span class="nv">the</span> <span class="nv">time</span>.
<span class="nv">I</span> <span class="nv">introduced</span> <span class="nv">another</span> <span class="nv">way</span> <span class="nv">to</span> <span class="nv">pass</span> <span class="nv">the</span> <span class="nv">CORS</span> <span class="nv">policy</span>, <span class="nv">so</span> <span class="nv">you</span> <span class="nv">can</span> <span class="k">do</span>
<span class="nv">something</span> <span class="nv">like</span> <span class="nv">that</span>:
``` <span class="nv">sourceCode</span> <span class="nv">python</span>
<span class="nv">policy</span> <span class="o">=</span> <span class="nv">dict</span><span class="ss">(</span><span class="nv">enabled</span><span class="o">=</span><span class="nv">False</span>,
<span class="nv">headers</span><span class="o">=</span><span class="ss">(</span><span class="s1">&#39;</span><span class="s">X-My-Header</span><span class="s1">&#39;</span>, <span class="s1">&#39;</span><span class="s">Content-Type</span><span class="s1">&#39;</span><span class="ss">)</span>,
<span class="nv">origins</span><span class="o">=</span><span class="ss">(</span><span class="s1">&#39;</span><span class="s">*.notmyidea.org</span><span class="s1">&#39;</span><span class="ss">)</span>,
<span class="nv">credentials</span><span class="o">=</span><span class="nv">True</span>,
<span class="nv">max_age</span><span class="o">=</span><span class="mi">42</span><span class="ss">)</span>
<span class="nv">foobar</span> <span class="o">=</span> <span class="nv">Service</span><span class="ss">(</span><span class="nv">name</span><span class="o">=</span><span class="s1">&#39;</span><span class="s">foobar</span><span class="s1">&#39;</span>, <span class="nv">path</span><span class="o">=</span><span class="s1">&#39;</span><span class="s">/foobar</span><span class="s1">&#39;</span>, <span class="nv">cors_policy</span><span class="o">=</span><span class="nv">policy</span><span class="ss">)</span>
</pre></div>
<h2 id="comparison-with-other-implementations">Comparison with other implementations</h2>
<p>I was curious to have a look at other implementations of CORS, in django
for instance, and I found <a href="https://gist.github.com/426829.js">a gist about
it</a>.</p>
<p>Basically, this adds a middleware that adds the "rights" headers to the
answer, depending on the request.</p>
<p>While this approach works, it's not implementing the specification
completely. You need to add support for all the resources at once.</p>
<p>We can think about a nice way to implement this specifying a definition
of what's supposed to be exposed via CORS and what shouldn't directly in
your settings. In my opinion, CORS support should be handled at the
service definition level, except for the list of authorized hosts.
Otherwise, you don't know exactly what's going on when you look at the
definition of the service.</p>
<h2 id="resources">Resources</h2>
<p>There are a number of good resources that can be useful to you if you
want to either understand how CORS works, or if you want to implement it
yourself.</p>
<ul>
<li><a href="http://enable-cors.org/">http://enable-cors.org/</a> is useful to get started when you don't
know anything about CORS.</li>
<li>There is a W3C wiki page containing information that may be useful
about clients, common pitfalls etc:
<a href="http://www.w3.org/wiki/CORS_Enabled">http://www.w3.org/wiki/CORS_Enabled</a></li>
<li><em>HTML5 rocks</em> has a tutorial explaining how to implement CORS, with
<a href="http://www.html5rocks.com/en/tutorials/cors/#toc-adding-cors-support-to-the-server">a nice section about the
server-side</a>.</li>
<li>Be sure to have a look at the <a href="http://caniuse.com/#search=cors">clients support-matrix for this
feature</a>.</li>
<li>About security, <a href="https://code.google.com/p/html5security/wiki/CrossOriginRequestSecurity">check out this
page</a></li>
<li>If you want to have a look at the implementation code, check <a href="https://github.com/mozilla-services/cornice/pull/98/files">on
github</a></li>
</ul>
<p>Of course, the W3C specification is the best resource to rely on. This
specification isn't hard to read, so you may want to go through it.
Especially the <a href="http://www.w3.org/TR/cors/#resource-processing-model">"resource processing model"
section</a></p>
</div>
</div>
</div>
<label for="sidebar-checkbox" class="sidebar-toggle"></label>
<script>
(function(document) {
var i = 0;
// snip empty header rows since markdown can't
var rows = document.querySelectorAll('tr');
for(i=0; i<rows.length; i++) {
var ths = rows[i].querySelectorAll('th');
var rowlen = rows[i].children.length;
if (ths.length > 0 && ths.length === rowlen) {
rows[i].remove();
}
}
})(document);
</script>
<script>
/* Lanyon & Poole are Copyright (c) 2014 Mark Otto. Adapted to Pelican 20141223 and extended a bit by @thomaswilley */
(function(document) {
var toggle = document.querySelector('.sidebar-toggle');
var sidebar = document.querySelector('#sidebar');
var checkbox = document.querySelector('#sidebar-checkbox');
document.addEventListener('click', function(e) {
var target = e.target;
if(!checkbox.checked ||
sidebar.contains(target) ||
(target === checkbox || target === toggle)) return;
checkbox.checked = false;
}, false);
})(document);
</script>
<!-- Piwik -->
<script type="text/javascript">
var _paq = _paq || [];
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//tracker.notmyidea.org/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', 3]);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<noscript><p><img src="//tracker.notmyidea.org/piwik.php?idsite=3" style="border:0;" alt="" /></p></noscript>
<!-- End Piwik Code -->
</div>
</body>
</html>