[pre-commit.ci lite] apply automatic fixes
This commit is contained in:
parent
b3ae3117f9
commit
3d83d8138c
102 changed files with 26790 additions and 26749 deletions
|
|
@ -1,563 +1,304 @@
|
|||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" data-content_root="../">
|
||||
<html lang="en" data-content_root="./">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Test Coverage — Flask Documentation (3.2.x)</title>
|
||||
<link rel="stylesheet" type="text/css" href="../_static/pygments.css?v=6625fa76" />
|
||||
<link rel="stylesheet" type="text/css" href="../_static/flask.css?v=b87c8d14" />
|
||||
<script src="../_static/documentation_options.js?v=56528222"></script>
|
||||
<script src="../_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="../_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script data-project="flask" data-version="3.2.x" src="../_static/describe_version.js?v=fa7f30d0"></script>
|
||||
<link rel="icon" href="../_static/shortcut-icon.png"/>
|
||||
<link rel="index" title="Index" href="../genindex.html" />
|
||||
<link rel="search" title="Search" href="../search.html" />
|
||||
<link rel="next" title="Deploy to Production" href="deploy.html" />
|
||||
<link rel="prev" title="Make the Project Installable" href="install.html" />
|
||||
<title>Security Considerations — Flask Documentation (3.2.x)</title>
|
||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=6625fa76" />
|
||||
<link rel="stylesheet" type="text/css" href="_static/flask.css?v=b87c8d14" />
|
||||
<script src="_static/documentation_options.js?v=56528222"></script>
|
||||
<script src="_static/doctools.js?v=9bcbadda"></script>
|
||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
||||
<script data-project="flask" data-version="3.2.x" src="_static/describe_version.js?v=fa7f30d0"></script>
|
||||
<link rel="icon" href="_static/shortcut-icon.png"/>
|
||||
<link rel="index" title="Index" href="genindex.html" />
|
||||
<link rel="search" title="Search" href="search.html" />
|
||||
<link rel="next" title="Deploying to Production" href="deploying/index.html" />
|
||||
<link rel="prev" title="Single-Page Applications" href="patterns/singlepageapplications.html" />
|
||||
</head><body>
|
||||
<div class="related" role="navigation" aria-label="Related">
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li class="right" style="margin-right: 10px">
|
||||
<a href="../genindex.html" title="General Index"
|
||||
<a href="genindex.html" title="General Index"
|
||||
accesskey="I">index</a></li>
|
||||
<li class="right" >
|
||||
<a href="../py-modindex.html" title="Python Module Index"
|
||||
<a href="py-modindex.html" title="Python Module Index"
|
||||
>modules</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="deploy.html" title="Deploy to Production"
|
||||
<a href="deploying/index.html" title="Deploying to Production"
|
||||
accesskey="N">next</a> |</li>
|
||||
<li class="right" >
|
||||
<a href="install.html" title="Make the Project Installable"
|
||||
<a href="patterns/singlepageapplications.html" title="Single-Page Applications"
|
||||
accesskey="P">previous</a> |</li>
|
||||
<li class="nav-item nav-item-0"><a href="../index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-1"><a href="index.html" accesskey="U">Tutorial</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Test Coverage</a></li>
|
||||
<li class="nav-item nav-item-0"><a href="index.html">Flask Documentation (3.2.x)</a> »</li>
|
||||
<li class="nav-item nav-item-this"><a href="">Security Considerations</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="document">
|
||||
<div class="documentwrapper">
|
||||
<div class="bodywrapper">
|
||||
<div class="body" role="main">
|
||||
|
||||
<section id="test-coverage">
|
||||
<h1>Test Coverage<a class="headerlink" href="#test-coverage" title="Link to this heading">¶</a></h1>
|
||||
<p>Writing unit tests for your application lets you check that the code
|
||||
you wrote works the way you expect. Flask provides a test client that
|
||||
simulates requests to the application and returns the response data.</p>
|
||||
<p>You should test as much of your code as possible. Code in functions only
|
||||
runs when the function is called, and code in branches, such as <code class="docutils literal notranslate"><span class="pre">if</span></code>
|
||||
blocks, only runs when the condition is met. You want to make sure that
|
||||
each function is tested with data that covers each branch.</p>
|
||||
<p>The closer you get to 100% coverage, the more comfortable you can be
|
||||
that making a change won’t unexpectedly change other behavior. However,
|
||||
100% coverage doesn’t guarantee that your application doesn’t have bugs.
|
||||
In particular, it doesn’t test how the user interacts with the
|
||||
application in the browser. Despite this, test coverage is an important
|
||||
tool to use during development.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>This is being introduced late in the tutorial, but in your future
|
||||
projects you should test as you develop.</p>
|
||||
</div>
|
||||
<p>You’ll use <a class="reference external" href="https://pytest.readthedocs.io/">pytest</a> and <a class="reference external" href="https://coverage.readthedocs.io/">coverage</a> to test and measure your code.
|
||||
Install them both:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pip install pytest coverage
|
||||
</pre></div>
|
||||
</div>
|
||||
<section id="setup-and-fixtures">
|
||||
<h2>Setup and Fixtures<a class="headerlink" href="#setup-and-fixtures" title="Link to this heading">¶</a></h2>
|
||||
<p>The test code is located in the <code class="docutils literal notranslate"><span class="pre">tests</span></code> directory. This directory is
|
||||
<em>next to</em> the <code class="docutils literal notranslate"><span class="pre">flaskr</span></code> package, not inside it. The
|
||||
<code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code> file contains setup functions called <em>fixtures</em>
|
||||
that each test will use. Tests are in Python modules that start with
|
||||
<code class="docutils literal notranslate"><span class="pre">test_</span></code>, and each test function in those modules also starts with
|
||||
<code class="docutils literal notranslate"><span class="pre">test_</span></code>.</p>
|
||||
<p>Each test will create a new temporary database file and populate some
|
||||
data that will be used in the tests. Write a SQL file to insert that
|
||||
data.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id1">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/data.sql</span></code></span><a class="headerlink" href="#id1" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-sql notranslate"><div class="highlight"><pre><span></span><span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="k">user</span><span class="w"> </span><span class="p">(</span><span class="n">username</span><span class="p">,</span><span class="w"> </span><span class="n">password</span><span class="p">)</span>
|
||||
<span class="k">VALUES</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'test'</span><span class="p">,</span><span class="w"> </span><span class="s1">'pbkdf2:sha256:50000$TCI4GzcX$0de171a4f4dac32e3364c7ddc7c14f3e2fa61f2d17574483f7ffbb431b4acb2f'</span><span class="p">),</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'other'</span><span class="p">,</span><span class="w"> </span><span class="s1">'pbkdf2:sha256:50000$kJPKsz6N$d2d4784f1b030a9761f5ccaeeaca413f27f2ecb76d6168407af962ddce849f79'</span><span class="p">);</span>
|
||||
|
||||
<span class="k">INSERT</span><span class="w"> </span><span class="k">INTO</span><span class="w"> </span><span class="n">post</span><span class="w"> </span><span class="p">(</span><span class="n">title</span><span class="p">,</span><span class="w"> </span><span class="n">body</span><span class="p">,</span><span class="w"> </span><span class="n">author_id</span><span class="p">,</span><span class="w"> </span><span class="n">created</span><span class="p">)</span>
|
||||
<span class="k">VALUES</span>
|
||||
<span class="w"> </span><span class="p">(</span><span class="s1">'test title'</span><span class="p">,</span><span class="w"> </span><span class="s1">'test'</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">x</span><span class="s1">'0a'</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="s1">'body'</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="s1">'2018-01-01 00:00:00'</span><span class="p">);</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture will call the factory and pass <code class="docutils literal notranslate"><span class="pre">test_config</span></code> to
|
||||
configure the application and database for testing instead of using your
|
||||
local development configuration.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id2">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code></span><a class="headerlink" href="#id2" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">os</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">tempfile</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_app</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span><span class="p">,</span> <span class="n">init_db</span>
|
||||
|
||||
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">dirname</span><span class="p">(</span><span class="vm">__file__</span><span class="p">),</span> <span class="s1">'data.sql'</span><span class="p">),</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
||||
<span class="n">_data_sql</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf8'</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">app</span><span class="p">():</span>
|
||||
<span class="n">db_fd</span><span class="p">,</span> <span class="n">db_path</span> <span class="o">=</span> <span class="n">tempfile</span><span class="o">.</span><span class="n">mkstemp</span><span class="p">()</span>
|
||||
|
||||
<span class="n">app</span> <span class="o">=</span> <span class="n">create_app</span><span class="p">({</span>
|
||||
<span class="s1">'TESTING'</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||||
<span class="s1">'DATABASE'</span><span class="p">:</span> <span class="n">db_path</span><span class="p">,</span>
|
||||
<span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">init_db</span><span class="p">()</span>
|
||||
<span class="n">get_db</span><span class="p">()</span><span class="o">.</span><span class="n">executescript</span><span class="p">(</span><span class="n">_data_sql</span><span class="p">)</span>
|
||||
|
||||
<span class="k">yield</span> <span class="n">app</span>
|
||||
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">close</span><span class="p">(</span><span class="n">db_fd</span><span class="p">)</span>
|
||||
<span class="n">os</span><span class="o">.</span><span class="n">unlink</span><span class="p">(</span><span class="n">db_path</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">client</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">app</span><span class="o">.</span><span class="n">test_client</span><span class="p">()</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">runner</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">app</span><span class="o">.</span><span class="n">test_cli_runner</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference external" href="https://docs.python.org/3/library/tempfile.html#tempfile.mkstemp" title="(in Python v3.13)"><code class="xref py py-func docutils literal notranslate"><span class="pre">tempfile.mkstemp()</span></code></a> creates and opens a temporary file, returning
|
||||
the file descriptor and the path to it. The <code class="docutils literal notranslate"><span class="pre">DATABASE</span></code> path is
|
||||
overridden so it points to this temporary path instead of the instance
|
||||
folder. After setting the path, the database tables are created and the
|
||||
test data is inserted. After the test is over, the temporary file is
|
||||
closed and removed.</p>
|
||||
<p><a class="reference internal" href="../config.html#TESTING" title="TESTING"><code class="xref py py-data docutils literal notranslate"><span class="pre">TESTING</span></code></a> tells Flask that the app is in test mode. Flask changes
|
||||
some internal behavior so it’s easier to test, and other extensions can
|
||||
also use the flag to make testing them easier.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">client</span></code> fixture calls
|
||||
<a class="reference internal" href="../api.html#flask.Flask.test_client" title="flask.Flask.test_client"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.test_client()</span></code></a> with the application
|
||||
object created by the <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture. Tests will use the client to make
|
||||
requests to the application without running the server.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">runner</span></code> fixture is similar to <code class="docutils literal notranslate"><span class="pre">client</span></code>.
|
||||
<a class="reference internal" href="../api.html#flask.Flask.test_cli_runner" title="flask.Flask.test_cli_runner"><code class="xref py py-meth docutils literal notranslate"><span class="pre">app.test_cli_runner()</span></code></a> creates a runner
|
||||
that can call the Click commands registered with the application.</p>
|
||||
<p>Pytest uses fixtures by matching their function names with the names
|
||||
of arguments in the test functions. For example, the <code class="docutils literal notranslate"><span class="pre">test_hello</span></code>
|
||||
function you’ll write next takes a <code class="docutils literal notranslate"><span class="pre">client</span></code> argument. Pytest matches
|
||||
that with the <code class="docutils literal notranslate"><span class="pre">client</span></code> fixture function, calls it, and passes the
|
||||
returned value to the test function.</p>
|
||||
<section id="security-considerations">
|
||||
<h1>Security Considerations<a class="headerlink" href="#security-considerations" title="Link to this heading">¶</a></h1>
|
||||
<p>Web applications face many types of potential security problems, and it can be
|
||||
hard to get everything right, or even to know what “right” is in general. Flask
|
||||
tries to solve a few of these things by default, but there are other parts you
|
||||
may have to take care of yourself. Many of these solutions are tradeoffs, and
|
||||
will depend on each application’s specific needs and threat model. Many hosting
|
||||
platforms may take care of certain types of problems without the need for the
|
||||
Flask application to handle them.</p>
|
||||
<section id="resource-use">
|
||||
<h2>Resource Use<a class="headerlink" href="#resource-use" title="Link to this heading">¶</a></h2>
|
||||
<p>A common category of attacks is “Denial of Service” (DoS or DDoS). This is a
|
||||
very broad category, and different variants target different layers in a
|
||||
deployed application. In general, something is done to increase how much
|
||||
processing time or memory is used to handle each request, to the point where
|
||||
there are not enough resources to handle legitimate requests.</p>
|
||||
<p>Flask provides a few configuration options to handle resource use. They can
|
||||
also be set on individual requests to customize only that request. The
|
||||
documentation for each goes into more detail.</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference internal" href="config.html#MAX_CONTENT_LENGTH" title="MAX_CONTENT_LENGTH"><code class="xref py py-data docutils literal notranslate"><span class="pre">MAX_CONTENT_LENGTH</span></code></a> or <a class="reference internal" href="api.html#flask.Request.max_content_length" title="flask.Request.max_content_length"><code class="xref py py-attr docutils literal notranslate"><span class="pre">Request.max_content_length</span></code></a> controls
|
||||
how much data will be read from a request. It is not set by default,
|
||||
although it will still block truly unlimited streams unless the WSGI server
|
||||
indicates support.</p></li>
|
||||
<li><p><a class="reference internal" href="config.html#MAX_FORM_MEMORY_SIZE" title="MAX_FORM_MEMORY_SIZE"><code class="xref py py-data docutils literal notranslate"><span class="pre">MAX_FORM_MEMORY_SIZE</span></code></a> or <a class="reference internal" href="api.html#flask.Request.max_form_memory_size" title="flask.Request.max_form_memory_size"><code class="xref py py-attr docutils literal notranslate"><span class="pre">Request.max_form_memory_size</span></code></a>
|
||||
controls how large any non-file <code class="docutils literal notranslate"><span class="pre">multipart/form-data</span></code> field can be. It is
|
||||
set to 500kB by default.</p></li>
|
||||
<li><p><a class="reference internal" href="config.html#MAX_FORM_PARTS" title="MAX_FORM_PARTS"><code class="xref py py-data docutils literal notranslate"><span class="pre">MAX_FORM_PARTS</span></code></a> or <a class="reference internal" href="api.html#flask.Request.max_form_parts" title="flask.Request.max_form_parts"><code class="xref py py-attr docutils literal notranslate"><span class="pre">Request.max_form_parts</span></code></a> controls how many
|
||||
<code class="docutils literal notranslate"><span class="pre">multipart/form-data</span></code> fields can be parsed. It is set to 1000 by default.
|
||||
Combined with the default <code class="code docutils literal notranslate"><span class="pre">max_form_memory_size</span></code>, this means that a form
|
||||
will occupy at most 500MB of memory.</p></li>
|
||||
</ul>
|
||||
<p>Regardless of these settings, you should also review what settings are available
|
||||
from your operating system, container deployment (Docker etc), WSGI server, HTTP
|
||||
server, and hosting platform. They typically have ways to set process resource
|
||||
limits, timeouts, and other checks regardless of how Flask is configured.</p>
|
||||
</section>
|
||||
<section id="factory">
|
||||
<h2>Factory<a class="headerlink" href="#factory" title="Link to this heading">¶</a></h2>
|
||||
<p>There’s not much to test about the factory itself. Most of the code will
|
||||
be executed for each test already, so if something fails the other tests
|
||||
will notice.</p>
|
||||
<p>The only behavior that can change is passing test config. If config is
|
||||
not passed, there should be some default configuration, otherwise the
|
||||
configuration should be overridden.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id3">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_factory.py</span></code></span><a class="headerlink" href="#id3" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flaskr</span><span class="w"> </span><span class="kn">import</span> <span class="n">create_app</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_config</span><span class="p">():</span>
|
||||
<span class="k">assert</span> <span class="ow">not</span> <span class="n">create_app</span><span class="p">()</span><span class="o">.</span><span class="n">testing</span>
|
||||
<span class="k">assert</span> <span class="n">create_app</span><span class="p">({</span><span class="s1">'TESTING'</span><span class="p">:</span> <span class="kc">True</span><span class="p">})</span><span class="o">.</span><span class="n">testing</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_hello</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/hello'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span> <span class="o">==</span> <span class="sa">b</span><span class="s1">'Hello, World!'</span>
|
||||
<section id="cross-site-scripting-xss">
|
||||
<span id="security-xss"></span><h2>Cross-Site Scripting (XSS)<a class="headerlink" href="#cross-site-scripting-xss" title="Link to this heading">¶</a></h2>
|
||||
<p>Cross site scripting is the concept of injecting arbitrary HTML (and with
|
||||
it JavaScript) into the context of a website. To remedy this, developers
|
||||
have to properly escape text so that it cannot include arbitrary HTML
|
||||
tags. For more information on that have a look at the Wikipedia article
|
||||
on <a class="reference external" href="https://en.wikipedia.org/wiki/Cross-site_scripting">Cross-Site Scripting</a>.</p>
|
||||
<p>Flask configures Jinja2 to automatically escape all values unless
|
||||
explicitly told otherwise. This should rule out all XSS problems caused
|
||||
in templates, but there are still other places where you have to be
|
||||
careful:</p>
|
||||
<ul class="simple">
|
||||
<li><p>generating HTML without the help of Jinja2</p></li>
|
||||
<li><p>calling <code class="xref py py-class docutils literal notranslate"><span class="pre">Markup</span></code> on data submitted by users</p></li>
|
||||
<li><p>sending out HTML from uploaded files, never do that, use the
|
||||
<code class="docutils literal notranslate"><span class="pre">Content-Disposition:</span> <span class="pre">attachment</span></code> header to prevent that problem.</p></li>
|
||||
<li><p>sending out textfiles from uploaded files. Some browsers are using
|
||||
content-type guessing based on the first few bytes so users could
|
||||
trick a browser to execute HTML.</p></li>
|
||||
</ul>
|
||||
<p>Another thing that is very important are unquoted attributes. While
|
||||
Jinja2 can protect you from XSS issues by escaping HTML, there is one
|
||||
thing it cannot protect you from: XSS by attribute injection. To counter
|
||||
this possible attack vector, be sure to always quote your attributes with
|
||||
either double or single quotes when using Jinja expressions in them:</p>
|
||||
<div class="highlight-html+jinja notranslate"><div class="highlight"><pre><span></span><span class="p"><</span><span class="nt">input</span> <span class="na">value</span><span class="o">=</span><span class="s">"</span><span class="cp">{{</span> <span class="nv">value</span> <span class="cp">}}</span><span class="s">"</span><span class="p">></span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Why is this necessary? Because if you would not be doing that, an
|
||||
attacker could easily inject custom JavaScript handlers. For example an
|
||||
attacker could inject this piece of HTML+JavaScript:</p>
|
||||
<div class="highlight-html notranslate"><div class="highlight"><pre><span></span>onmouseover=alert(document.cookie)
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You added the <code class="docutils literal notranslate"><span class="pre">hello</span></code> route as an example when writing the factory at
|
||||
the beginning of the tutorial. It returns “Hello, World!”, so the test
|
||||
checks that the response data matches.</p>
|
||||
<p>When the user would then move with the mouse over the input, the cookie
|
||||
would be presented to the user in an alert window. But instead of showing
|
||||
the cookie to the user, a good attacker might also execute any other
|
||||
JavaScript code. In combination with CSS injections the attacker might
|
||||
even make the element fill out the entire page so that the user would
|
||||
just have to have the mouse anywhere on the page to trigger the attack.</p>
|
||||
<p>There is one class of XSS issues that Jinja’s escaping does not protect
|
||||
against. The <code class="docutils literal notranslate"><span class="pre">a</span></code> tag’s <code class="docutils literal notranslate"><span class="pre">href</span></code> attribute can contain a <code class="code docutils literal notranslate"><span class="pre">javascript:</span></code> URI,
|
||||
which the browser will execute when clicked if not secured properly.</p>
|
||||
<div class="highlight-html notranslate"><div class="highlight"><pre><span></span><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ value }}"</span><span class="p">></span>click here<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
<span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"javascript:alert('unsafe');"</span><span class="p">></span>click here<span class="p"></</span><span class="nt">a</span><span class="p">></span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>To prevent this, you’ll need to set the <a class="reference internal" href="#security-csp"><span class="std std-ref">Content Security Policy (CSP)</span></a> response header.</p>
|
||||
</section>
|
||||
<section id="database">
|
||||
<h2>Database<a class="headerlink" href="#database" title="Link to this heading">¶</a></h2>
|
||||
<p>Within an application context, <code class="docutils literal notranslate"><span class="pre">get_db</span></code> should return the same
|
||||
connection each time it’s called. After the context, the connection
|
||||
should be closed.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id4">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_db.py</span></code></span><a class="headerlink" href="#id4" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">sqlite3</span>
|
||||
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_get_close_db</span><span class="p">(</span><span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">db</span> <span class="ow">is</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">pytest</span><span class="o">.</span><span class="n">raises</span><span class="p">(</span><span class="n">sqlite3</span><span class="o">.</span><span class="n">ProgrammingError</span><span class="p">)</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT 1'</span><span class="p">)</span>
|
||||
|
||||
<span class="k">assert</span> <span class="s1">'closed'</span> <span class="ow">in</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">init-db</span></code> command should call the <code class="docutils literal notranslate"><span class="pre">init_db</span></code> function and output
|
||||
a message.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id5">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_db.py</span></code></span><a class="headerlink" href="#id5" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_init_db_command</span><span class="p">(</span><span class="n">runner</span><span class="p">,</span> <span class="n">monkeypatch</span><span class="p">):</span>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">Recorder</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
|
||||
<span class="n">called</span> <span class="o">=</span> <span class="kc">False</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">fake_init_db</span><span class="p">():</span>
|
||||
<span class="n">Recorder</span><span class="o">.</span><span class="n">called</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
|
||||
<span class="n">monkeypatch</span><span class="o">.</span><span class="n">setattr</span><span class="p">(</span><span class="s1">'flaskr.db.init_db'</span><span class="p">,</span> <span class="n">fake_init_db</span><span class="p">)</span>
|
||||
<span class="n">result</span> <span class="o">=</span> <span class="n">runner</span><span class="o">.</span><span class="n">invoke</span><span class="p">(</span><span class="n">args</span><span class="o">=</span><span class="p">[</span><span class="s1">'init-db'</span><span class="p">])</span>
|
||||
<span class="k">assert</span> <span class="s1">'Initialized'</span> <span class="ow">in</span> <span class="n">result</span><span class="o">.</span><span class="n">output</span>
|
||||
<span class="k">assert</span> <span class="n">Recorder</span><span class="o">.</span><span class="n">called</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>This test uses Pytest’s <code class="docutils literal notranslate"><span class="pre">monkeypatch</span></code> fixture to replace the
|
||||
<code class="docutils literal notranslate"><span class="pre">init_db</span></code> function with one that records that it’s been called. The
|
||||
<code class="docutils literal notranslate"><span class="pre">runner</span></code> fixture you wrote above is used to call the <code class="docutils literal notranslate"><span class="pre">init-db</span></code>
|
||||
command by name.</p>
|
||||
<section id="cross-site-request-forgery-csrf">
|
||||
<h2>Cross-Site Request Forgery (CSRF)<a class="headerlink" href="#cross-site-request-forgery-csrf" title="Link to this heading">¶</a></h2>
|
||||
<p>Another big problem is CSRF. This is a very complex topic and I won’t
|
||||
outline it here in detail just mention what it is and how to theoretically
|
||||
prevent it.</p>
|
||||
<p>If your authentication information is stored in cookies, you have implicit
|
||||
state management. The state of “being logged in” is controlled by a
|
||||
cookie, and that cookie is sent with each request to a page.
|
||||
Unfortunately that includes requests triggered by 3rd party sites. If you
|
||||
don’t keep that in mind, some people might be able to trick your
|
||||
application’s users with social engineering to do stupid things without
|
||||
them knowing.</p>
|
||||
<p>Say you have a specific URL that, when you sent <code class="docutils literal notranslate"><span class="pre">POST</span></code> requests to will
|
||||
delete a user’s profile (say <code class="docutils literal notranslate"><span class="pre">http://example.com/user/delete</span></code>). If an
|
||||
attacker now creates a page that sends a post request to that page with
|
||||
some JavaScript they just have to trick some users to load that page and
|
||||
their profiles will end up being deleted.</p>
|
||||
<p>Imagine you were to run Facebook with millions of concurrent users and
|
||||
someone would send out links to images of little kittens. When users
|
||||
would go to that page, their profiles would get deleted while they are
|
||||
looking at images of fluffy cats.</p>
|
||||
<p>How can you prevent that? Basically for each request that modifies
|
||||
content on the server you would have to either use a one-time token and
|
||||
store that in the cookie <strong>and</strong> also transmit it with the form data.
|
||||
After receiving the data on the server again, you would then have to
|
||||
compare the two tokens and ensure they are equal.</p>
|
||||
<p>Why does Flask not do that for you? The ideal place for this to happen is
|
||||
the form validation framework, which does not exist in Flask.</p>
|
||||
</section>
|
||||
<section id="authentication">
|
||||
<h2>Authentication<a class="headerlink" href="#authentication" title="Link to this heading">¶</a></h2>
|
||||
<p>For most of the views, a user needs to be logged in. The easiest way to
|
||||
do this in tests is to make a <code class="docutils literal notranslate"><span class="pre">POST</span></code> request to the <code class="docutils literal notranslate"><span class="pre">login</span></code> view
|
||||
with the client. Rather than writing that out every time, you can write
|
||||
a class with methods to do that, and use a fixture to pass it the client
|
||||
for each test.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id6">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/conftest.py</span></code></span><a class="headerlink" href="#id6" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">class</span><span class="w"> </span><span class="nc">AuthActions</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">client</span><span class="p">):</span>
|
||||
<span class="bp">self</span><span class="o">.</span><span class="n">_client</span> <span class="o">=</span> <span class="n">client</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">username</span><span class="o">=</span><span class="s1">'test'</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="s1">'test'</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/login'</span><span class="p">,</span>
|
||||
<span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="n">username</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="n">password</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">logout</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/logout'</span><span class="p">)</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">fixture</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">auth</span><span class="p">(</span><span class="n">client</span><span class="p">):</span>
|
||||
<span class="k">return</span> <span class="n">AuthActions</span><span class="p">(</span><span class="n">client</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>With the <code class="docutils literal notranslate"><span class="pre">auth</span></code> fixture, you can call <code class="docutils literal notranslate"><span class="pre">auth.login()</span></code> in a test to
|
||||
log in as the <code class="docutils literal notranslate"><span class="pre">test</span></code> user, which was inserted as part of the test
|
||||
data in the <code class="docutils literal notranslate"><span class="pre">app</span></code> fixture.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">register</span></code> view should render successfully on <code class="docutils literal notranslate"><span class="pre">GET</span></code>. On <code class="docutils literal notranslate"><span class="pre">POST</span></code>
|
||||
with valid form data, it should redirect to the login URL and the user’s
|
||||
data should be in the database. Invalid data should display error
|
||||
messages.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id7">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id7" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">g</span><span class="p">,</span> <span class="n">session</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_register</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/register'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/register'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="s1">'a'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="s1">'a'</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/auth/login"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="k">assert</span> <span class="n">get_db</span><span class="p">()</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span>
|
||||
<span class="s2">"SELECT * FROM user WHERE username = 'a'"</span><span class="p">,</span>
|
||||
<span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span> <span class="ow">is</span> <span class="ow">not</span> <span class="kc">None</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">((</span><span class="s1">'username'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">,</span> <span class="s1">'message'</span><span class="p">),</span> <span class="p">(</span>
|
||||
<span class="p">(</span><span class="s1">''</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Username is required.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">''</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Password is required.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'test'</span><span class="p">,</span> <span class="s1">'test'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'already registered'</span><span class="p">),</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_register_validate_input</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span>
|
||||
<span class="s1">'/auth/register'</span><span class="p">,</span>
|
||||
<span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'username'</span><span class="p">:</span> <span class="n">username</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">:</span> <span class="n">password</span><span class="p">}</span>
|
||||
<span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p><a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/test/#werkzeug.test.Client.get" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">client.get()</span></code></a> makes a <code class="docutils literal notranslate"><span class="pre">GET</span></code> request
|
||||
and returns the <a class="reference internal" href="../api.html#flask.Response" title="flask.Response"><code class="xref py py-class docutils literal notranslate"><span class="pre">Response</span></code></a> object returned by Flask. Similarly,
|
||||
<a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/test/#werkzeug.test.Client.post" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">client.post()</span></code></a> makes a <code class="docutils literal notranslate"><span class="pre">POST</span></code>
|
||||
request, converting the <code class="docutils literal notranslate"><span class="pre">data</span></code> dict into form data.</p>
|
||||
<p>To test that the page renders successfully, a simple request is made and
|
||||
checked for a <code class="docutils literal notranslate"><span class="pre">200</span> <span class="pre">OK</span></code> <a class="reference internal" href="../api.html#flask.Response.status_code" title="flask.Response.status_code"><code class="xref py py-attr docutils literal notranslate"><span class="pre">status_code</span></code></a>. If
|
||||
rendering failed, Flask would return a <code class="docutils literal notranslate"><span class="pre">500</span> <span class="pre">Internal</span> <span class="pre">Server</span> <span class="pre">Error</span></code>
|
||||
code.</p>
|
||||
<p><code class="xref py py-attr docutils literal notranslate"><span class="pre">headers</span></code> will have a <code class="docutils literal notranslate"><span class="pre">Location</span></code> header with the login
|
||||
URL when the register view redirects to the login view.</p>
|
||||
<p><a class="reference internal" href="../api.html#flask.Response.data" title="flask.Response.data"><code class="xref py py-attr docutils literal notranslate"><span class="pre">data</span></code></a> contains the body of the response as bytes. If
|
||||
you expect a certain value to render on the page, check that it’s in
|
||||
<code class="docutils literal notranslate"><span class="pre">data</span></code>. Bytes must be compared to bytes. If you want to compare text,
|
||||
use <a class="reference external" href="https://werkzeug.palletsprojects.com/en/stable/wrappers/#werkzeug.wrappers.Response.get_data" title="(in Werkzeug v3.1.x)"><code class="xref py py-meth docutils literal notranslate"><span class="pre">get_data(as_text=True)</span></code></a>
|
||||
instead.</p>
|
||||
<p><code class="docutils literal notranslate"><span class="pre">pytest.mark.parametrize</span></code> tells Pytest to run the same test function
|
||||
with different arguments. You use it here to test different invalid
|
||||
input and error messages without writing the same code three times.</p>
|
||||
<p>The tests for the <code class="docutils literal notranslate"><span class="pre">login</span></code> view are very similar to those for
|
||||
<code class="docutils literal notranslate"><span class="pre">register</span></code>. Rather than testing the data in the database,
|
||||
<a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> should have <code class="docutils literal notranslate"><span class="pre">user_id</span></code> set after logging in.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id8">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id8" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_login</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/auth/login'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">client</span><span class="p">:</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">session</span><span class="p">[</span><span class="s1">'user_id'</span><span class="p">]</span> <span class="o">==</span> <span class="mi">1</span>
|
||||
<span class="k">assert</span> <span class="n">g</span><span class="o">.</span><span class="n">user</span><span class="p">[</span><span class="s1">'username'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'test'</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">((</span><span class="s1">'username'</span><span class="p">,</span> <span class="s1">'password'</span><span class="p">,</span> <span class="s1">'message'</span><span class="p">),</span> <span class="p">(</span>
|
||||
<span class="p">(</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">'test'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Incorrect username.'</span><span class="p">),</span>
|
||||
<span class="p">(</span><span class="s1">'test'</span><span class="p">,</span> <span class="s1">'a'</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'Incorrect password.'</span><span class="p">),</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_login_validate_input</span><span class="p">(</span><span class="n">auth</span><span class="p">,</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">,</span> <span class="n">message</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">message</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>Using <code class="docutils literal notranslate"><span class="pre">client</span></code> in a <code class="docutils literal notranslate"><span class="pre">with</span></code> block allows accessing context variables
|
||||
such as <a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> after the response is returned. Normally,
|
||||
accessing <code class="docutils literal notranslate"><span class="pre">session</span></code> outside of a request would raise an error.</p>
|
||||
<p>Testing <code class="docutils literal notranslate"><span class="pre">logout</span></code> is the opposite of <code class="docutils literal notranslate"><span class="pre">login</span></code>. <a class="reference internal" href="../api.html#flask.session" title="flask.session"><code class="xref py py-data docutils literal notranslate"><span class="pre">session</span></code></a> should
|
||||
not contain <code class="docutils literal notranslate"><span class="pre">user_id</span></code> after logging out.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id9">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_auth.py</span></code></span><a class="headerlink" href="#id9" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_logout</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">client</span><span class="p">:</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">logout</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="s1">'user_id'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">session</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<section id="json-security">
|
||||
<span id="security-json"></span><h2>JSON Security<a class="headerlink" href="#json-security" title="Link to this heading">¶</a></h2>
|
||||
<p>In Flask 0.10 and lower, <code class="xref py py-func docutils literal notranslate"><span class="pre">jsonify()</span></code> did not serialize top-level
|
||||
arrays to JSON. This was because of a security vulnerability in ECMAScript 4.</p>
|
||||
<p>ECMAScript 5 closed this vulnerability, so only extremely old browsers are
|
||||
still vulnerable. All of these browsers have <a class="reference external" href="https://github.com/pallets/flask/issues/248#issuecomment-59934857">other more serious
|
||||
vulnerabilities</a>, so
|
||||
this behavior was changed and <code class="xref py py-func docutils literal notranslate"><span class="pre">jsonify()</span></code> now supports serializing
|
||||
arrays.</p>
|
||||
</section>
|
||||
<section id="blog">
|
||||
<h2>Blog<a class="headerlink" href="#blog" title="Link to this heading">¶</a></h2>
|
||||
<p>All the blog views use the <code class="docutils literal notranslate"><span class="pre">auth</span></code> fixture you wrote earlier. Call
|
||||
<code class="docutils literal notranslate"><span class="pre">auth.login()</span></code> and subsequent requests from the client will be logged
|
||||
in as the <code class="docutils literal notranslate"><span class="pre">test</span></code> user.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">index</span></code> view should display information about the post that was
|
||||
added with the test data. When logged in as the author, there should be
|
||||
a link to edit the post.</p>
|
||||
<p>You can also test some more authentication behavior while testing the
|
||||
<code class="docutils literal notranslate"><span class="pre">index</span></code> view. When not logged in, each page shows links to log in or
|
||||
register. When logged in, there’s a link to log out.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id10">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id10" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">pytest</span>
|
||||
<span class="kn">from</span><span class="w"> </span><span class="nn">flaskr.db</span><span class="w"> </span><span class="kn">import</span> <span class="n">get_db</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_index</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s2">"Log In"</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s2">"Register"</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'Log Out'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'test title'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'by test on 2018-01-01'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'test</span><span class="se">\n</span><span class="s1">body'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'href="/1/update"'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
<section id="security-headers">
|
||||
<h2>Security Headers<a class="headerlink" href="#security-headers" title="Link to this heading">¶</a></h2>
|
||||
<p>Browsers recognize various response headers in order to control security. We
|
||||
recommend reviewing each of the headers below for use in your application.
|
||||
The <a class="reference external" href="https://github.com/GoogleCloudPlatform/flask-talisman">Flask-Talisman</a> extension can be used to manage HTTPS and the security
|
||||
headers for you.</p>
|
||||
<section id="http-strict-transport-security-hsts">
|
||||
<h3>HTTP Strict Transport Security (HSTS)<a class="headerlink" href="#http-strict-transport-security-hsts" title="Link to this heading">¶</a></h3>
|
||||
<p>Tells the browser to convert all HTTP requests to HTTPS, preventing
|
||||
man-in-the-middle (MITM) attacks.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Strict-Transport-Security'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'max-age=31536000; includeSubDomains'</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>A user must be logged in to access the <code class="docutils literal notranslate"><span class="pre">create</span></code>, <code class="docutils literal notranslate"><span class="pre">update</span></code>, and
|
||||
<code class="docutils literal notranslate"><span class="pre">delete</span></code> views. The logged in user must be the author of the post to
|
||||
access <code class="docutils literal notranslate"><span class="pre">update</span></code> and <code class="docutils literal notranslate"><span class="pre">delete</span></code>, otherwise a <code class="docutils literal notranslate"><span class="pre">403</span> <span class="pre">Forbidden</span></code> status
|
||||
is returned. If a <code class="docutils literal notranslate"><span class="pre">post</span></code> with the given <code class="docutils literal notranslate"><span class="pre">id</span></code> doesn’t exist,
|
||||
<code class="docutils literal notranslate"><span class="pre">update</span></code> and <code class="docutils literal notranslate"><span class="pre">delete</span></code> should return <code class="docutils literal notranslate"><span class="pre">404</span> <span class="pre">Not</span> <span class="pre">Found</span></code>.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id11">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id11" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/create'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/update'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/delete'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_login_required</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/auth/login"</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_author_required</span><span class="p">(</span><span class="n">app</span><span class="p">,</span> <span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">):</span>
|
||||
<span class="c1"># change the post author to another user</span>
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'UPDATE post SET author_id = 2 WHERE id = 1'</span><span class="p">)</span>
|
||||
<span class="n">db</span><span class="o">.</span><span class="n">commit</span><span class="p">()</span>
|
||||
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="c1"># current user can't modify other user's post</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">403</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/delete'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">403</span>
|
||||
<span class="c1"># current user doesn't see edit link</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'href="/1/update"'</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/'</span><span class="p">)</span><span class="o">.</span><span class="n">data</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/2/update'</span><span class="p">,</span>
|
||||
<span class="s1">'/2/delete'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_exists_required</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">404</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">create</span></code> and <code class="docutils literal notranslate"><span class="pre">update</span></code> views should render and return a
|
||||
<code class="docutils literal notranslate"><span class="pre">200</span> <span class="pre">OK</span></code> status for a <code class="docutils literal notranslate"><span class="pre">GET</span></code> request. When valid data is sent in a
|
||||
<code class="docutils literal notranslate"><span class="pre">POST</span></code> request, <code class="docutils literal notranslate"><span class="pre">create</span></code> should insert the new post data into the
|
||||
database, and <code class="docutils literal notranslate"><span class="pre">update</span></code> should modify the existing data. Both pages
|
||||
should show an error message on invalid data.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id12">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id12" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_create</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/create'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/create'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">'created'</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">count</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT COUNT(id) FROM post'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()[</span><span class="mi">0</span><span class="p">]</span>
|
||||
<span class="k">assert</span> <span class="n">count</span> <span class="o">==</span> <span class="mi">2</span>
|
||||
|
||||
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_update</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">client</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">)</span><span class="o">.</span><span class="n">status_code</span> <span class="o">==</span> <span class="mi">200</span>
|
||||
<span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/update'</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">'updated'</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">post</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT * FROM post WHERE id = 1'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">post</span><span class="p">[</span><span class="s1">'title'</span><span class="p">]</span> <span class="o">==</span> <span class="s1">'updated'</span>
|
||||
|
||||
|
||||
<span class="nd">@pytest</span><span class="o">.</span><span class="n">mark</span><span class="o">.</span><span class="n">parametrize</span><span class="p">(</span><span class="s1">'path'</span><span class="p">,</span> <span class="p">(</span>
|
||||
<span class="s1">'/create'</span><span class="p">,</span>
|
||||
<span class="s1">'/1/update'</span><span class="p">,</span>
|
||||
<span class="p">))</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">test_create_update_validate</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">path</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="p">{</span><span class="s1">'title'</span><span class="p">:</span> <span class="s1">''</span><span class="p">,</span> <span class="s1">'body'</span><span class="p">:</span> <span class="s1">''</span><span class="p">})</span>
|
||||
<span class="k">assert</span> <span class="sa">b</span><span class="s1">'Title is required.'</span> <span class="ow">in</span> <span class="n">response</span><span class="o">.</span><span class="n">data</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">delete</span></code> view should redirect to the index URL and the post should
|
||||
no longer exist in the database.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id13">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">tests/test_blog.py</span></code></span><a class="headerlink" href="#id13" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">test_delete</span><span class="p">(</span><span class="n">client</span><span class="p">,</span> <span class="n">auth</span><span class="p">,</span> <span class="n">app</span><span class="p">):</span>
|
||||
<span class="n">auth</span><span class="o">.</span><span class="n">login</span><span class="p">()</span>
|
||||
<span class="n">response</span> <span class="o">=</span> <span class="n">client</span><span class="o">.</span><span class="n">post</span><span class="p">(</span><span class="s1">'/1/delete'</span><span class="p">)</span>
|
||||
<span class="k">assert</span> <span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Location"</span><span class="p">]</span> <span class="o">==</span> <span class="s2">"/"</span>
|
||||
|
||||
<span class="k">with</span> <span class="n">app</span><span class="o">.</span><span class="n">app_context</span><span class="p">():</span>
|
||||
<span class="n">db</span> <span class="o">=</span> <span class="n">get_db</span><span class="p">()</span>
|
||||
<span class="n">post</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT * FROM post WHERE id = 1'</span><span class="p">)</span><span class="o">.</span><span class="n">fetchone</span><span class="p">()</span>
|
||||
<span class="k">assert</span> <span class="n">post</span> <span class="ow">is</span> <span class="kc">None</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="running-the-tests">
|
||||
<h2>Running the Tests<a class="headerlink" href="#running-the-tests" title="Link to this heading">¶</a></h2>
|
||||
<p>Some extra configuration, which is not required but makes running tests with coverage
|
||||
less verbose, can be added to the project’s <code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code> file.</p>
|
||||
<div class="literal-block-wrapper docutils container" id="id14">
|
||||
<div class="code-block-caption"><span class="caption-text"><code class="docutils literal notranslate"><span class="pre">pyproject.toml</span></code></span><a class="headerlink" href="#id14" title="Link to this code">¶</a></div>
|
||||
<div class="highlight-toml notranslate"><div class="highlight"><pre><span></span><span class="k">[tool.pytest.ini_options]</span>
|
||||
<span class="n">testpaths</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"tests"</span><span class="p">]</span>
|
||||
|
||||
<span class="k">[tool.coverage.run]</span>
|
||||
<span class="n">branch</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span>
|
||||
<span class="n">source</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">"flaskr"</span><span class="p">]</span>
|
||||
<section id="content-security-policy-csp">
|
||||
<span id="security-csp"></span><h3>Content Security Policy (CSP)<a class="headerlink" href="#content-security-policy-csp" title="Link to this heading">¶</a></h3>
|
||||
<p>Tell the browser where it can load various types of resource from. This header
|
||||
should be used whenever possible, but requires some work to define the correct
|
||||
policy for your site. A very strict policy would be:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'Content-Security-Policy'</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"default-src 'self'"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</div>
|
||||
<p>To run the tests, use the <code class="docutils literal notranslate"><span class="pre">pytest</span></code> command. It will find and run all
|
||||
the test functions you’ve written.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ pytest
|
||||
|
||||
========================= test session starts ==========================
|
||||
platform linux -- Python 3.6.4, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
|
||||
rootdir: /home/user/Projects/flask-tutorial
|
||||
collected 23 items
|
||||
|
||||
tests/test_auth.py ........ [ 34%]
|
||||
tests/test_blog.py ............ [ 86%]
|
||||
tests/test_db.py .. [ 95%]
|
||||
tests/test_factory.py .. [100%]
|
||||
|
||||
====================== 24 passed in 0.64 seconds =======================
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://csp.withgoogle.com/docs/index.html">https://csp.withgoogle.com/docs/index.html</a></p></li>
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="x-content-type-options">
|
||||
<h3>X-Content-Type-Options<a class="headerlink" href="#x-content-type-options" title="Link to this heading">¶</a></h3>
|
||||
<p>Forces the browser to honor the response content type instead of trying to
|
||||
detect it, which can be abused to generate a cross-site scripting (XSS)
|
||||
attack.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'X-Content-Type-Options'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'nosniff'</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If any tests fail, pytest will show the error that was raised. You can
|
||||
run <code class="docutils literal notranslate"><span class="pre">pytest</span> <span class="pre">-v</span></code> to get a list of each test function rather than dots.</p>
|
||||
<p>To measure the code coverage of your tests, use the <code class="docutils literal notranslate"><span class="pre">coverage</span></code> command
|
||||
to run pytest instead of running it directly.</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage run -m pytest
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="x-frame-options">
|
||||
<h3>X-Frame-Options<a class="headerlink" href="#x-frame-options" title="Link to this heading">¶</a></h3>
|
||||
<p>Prevents external sites from embedding your site in an <code class="docutils literal notranslate"><span class="pre">iframe</span></code>. This
|
||||
prevents a class of attacks where clicks in the outer frame can be translated
|
||||
invisibly to clicks on your page’s elements. This is also known as
|
||||
“clickjacking”.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">response</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s1">'X-Frame-Options'</span><span class="p">]</span> <span class="o">=</span> <span class="s1">'SAMEORIGIN'</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can either view a simple coverage report in the terminal:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage report
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
<section id="set-cookie-options">
|
||||
<span id="security-cookie"></span><h3>Set-Cookie options<a class="headerlink" href="#set-cookie-options" title="Link to this heading">¶</a></h3>
|
||||
<p>These options can be added to a <code class="docutils literal notranslate"><span class="pre">Set-Cookie</span></code> header to improve their
|
||||
security. Flask has configuration options to set these on the session cookie.
|
||||
They can be set on other cookies too.</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">Secure</span></code> limits cookies to HTTPS traffic only.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">HttpOnly</span></code> protects the contents of cookies from being read with
|
||||
JavaScript.</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">SameSite</span></code> restricts how cookies are sent with requests from
|
||||
external sites. Can be set to <code class="docutils literal notranslate"><span class="pre">'Lax'</span></code> (recommended) or <code class="docutils literal notranslate"><span class="pre">'Strict'</span></code>.
|
||||
<code class="docutils literal notranslate"><span class="pre">Lax</span></code> prevents sending cookies with CSRF-prone requests from
|
||||
external sites, such as submitting a form. <code class="docutils literal notranslate"><span class="pre">Strict</span></code> prevents sending
|
||||
cookies with all external requests, including following regular links.</p></li>
|
||||
</ul>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">update</span><span class="p">(</span>
|
||||
<span class="n">SESSION_COOKIE_SECURE</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">SESSION_COOKIE_HTTPONLY</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">SESSION_COOKIE_SAMESITE</span><span class="o">=</span><span class="s1">'Lax'</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
Name Stmts Miss Branch BrPart Cover
|
||||
------------------------------------------------------
|
||||
flaskr/__init__.py 21 0 2 0 100%
|
||||
flaskr/auth.py 54 0 22 0 100%
|
||||
flaskr/blog.py 54 0 16 0 100%
|
||||
flaskr/db.py 24 0 4 0 100%
|
||||
------------------------------------------------------
|
||||
TOTAL 153 0 44 0 100%
|
||||
<span class="n">response</span><span class="o">.</span><span class="n">set_cookie</span><span class="p">(</span><span class="s1">'username'</span><span class="p">,</span> <span class="s1">'flask'</span><span class="p">,</span> <span class="n">secure</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">httponly</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">samesite</span><span class="o">=</span><span class="s1">'Lax'</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>An HTML report allows you to see which lines were covered in each file:</p>
|
||||
<div class="highlight-none notranslate"><div class="highlight"><pre><span></span>$ coverage html
|
||||
<p>Specifying <code class="docutils literal notranslate"><span class="pre">Expires</span></code> or <code class="docutils literal notranslate"><span class="pre">Max-Age</span></code> options, will remove the cookie after
|
||||
the given time, or the current time plus the age, respectively. If neither
|
||||
option is set, the cookie will be removed when the browser is closed.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># cookie expires after 10 minutes</span>
|
||||
<span class="n">response</span><span class="o">.</span><span class="n">set_cookie</span><span class="p">(</span><span class="s1">'snakes'</span><span class="p">,</span> <span class="s1">'3'</span><span class="p">,</span> <span class="n">max_age</span><span class="o">=</span><span class="mi">600</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This generates files in the <code class="docutils literal notranslate"><span class="pre">htmlcov</span></code> directory. Open
|
||||
<code class="docutils literal notranslate"><span class="pre">htmlcov/index.html</span></code> in your browser to see the report.</p>
|
||||
<p>Continue to <a class="reference internal" href="deploy.html"><span class="doc">Deploy to Production</span></a>.</p>
|
||||
<p>For the session cookie, if <a class="reference internal" href="api.html#flask.session.permanent" title="flask.session.permanent"><code class="xref py py-attr docutils literal notranslate"><span class="pre">session.permanent</span></code></a>
|
||||
is set, then <a class="reference internal" href="config.html#PERMANENT_SESSION_LIFETIME" title="PERMANENT_SESSION_LIFETIME"><code class="xref py py-data docutils literal notranslate"><span class="pre">PERMANENT_SESSION_LIFETIME</span></code></a> is used to set the expiration.
|
||||
Flask’s default cookie implementation validates that the cryptographic
|
||||
signature is not older than this value. Lowering this value may help mitigate
|
||||
replay attacks, where intercepted cookies can be sent at a later time.</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">app</span><span class="o">.</span><span class="n">config</span><span class="o">.</span><span class="n">update</span><span class="p">(</span>
|
||||
<span class="n">PERMANENT_SESSION_LIFETIME</span><span class="o">=</span><span class="mi">600</span>
|
||||
<span class="p">)</span>
|
||||
|
||||
<span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s1">'/login'</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s1">'POST'</span><span class="p">])</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">():</span>
|
||||
<span class="o">...</span>
|
||||
<span class="n">session</span><span class="o">.</span><span class="n">clear</span><span class="p">()</span>
|
||||
<span class="n">session</span><span class="p">[</span><span class="s1">'user_id'</span><span class="p">]</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="n">id</span>
|
||||
<span class="n">session</span><span class="o">.</span><span class="n">permanent</span> <span class="o">=</span> <span class="kc">True</span>
|
||||
<span class="o">...</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Use <code class="xref py py-class docutils literal notranslate"><span class="pre">itsdangerous.TimedSerializer</span></code> to sign and validate other cookie
|
||||
values (or any values that need secure signatures).</p>
|
||||
<ul class="simple">
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies</a></p></li>
|
||||
<li><p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie">https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie</a></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
<section id="copy-paste-to-terminal">
|
||||
<h2>Copy/Paste to Terminal<a class="headerlink" href="#copy-paste-to-terminal" title="Link to this heading">¶</a></h2>
|
||||
<p>Hidden characters such as the backspace character (<code class="docutils literal notranslate"><span class="pre">\b</span></code>, <code class="docutils literal notranslate"><span class="pre">^H</span></code>) can
|
||||
cause text to render differently in HTML than how it is interpreted if
|
||||
<a class="reference external" href="https://security.stackexchange.com/q/39118">pasted into a terminal</a>.</p>
|
||||
<p>For example, <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">y\bose\bm\bi\bt\be\b</span></code> renders as
|
||||
<code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">yosemite</span></code> in HTML, but the backspaces are applied when pasted
|
||||
into a terminal, and it becomes <code class="docutils literal notranslate"><span class="pre">import</span> <span class="pre">os</span></code>.</p>
|
||||
<p>If you expect users to copy and paste untrusted code from your site,
|
||||
such as from comments posted by users on a technical blog, consider
|
||||
applying extra filtering, such as replacing all <code class="docutils literal notranslate"><span class="pre">\b</span></code> characters.</p>
|
||||
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="n">body</span> <span class="o">=</span> <span class="n">body</span><span class="o">.</span><span class="n">replace</span><span class="p">(</span><span class="s2">"</span><span class="se">\b</span><span class="s2">"</span><span class="p">,</span> <span class="s2">""</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Most modern terminals will warn about and remove hidden characters when
|
||||
pasting, so this isn’t strictly necessary. It’s also possible to craft
|
||||
dangerous commands in other ways that aren’t possible to filter.
|
||||
Depending on your site’s use case, it may be good to show a warning
|
||||
about copying code in general.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
|
@ -569,41 +310,45 @@ TOTAL 153 0 44 0 100%
|
|||
<span id="sidebar-top"></span>
|
||||
<div class="sphinxsidebar" role="navigation" aria-label="Main">
|
||||
<div class="sphinxsidebarwrapper">
|
||||
|
||||
|
||||
<p class="logo"><a href="../index.html">
|
||||
<img class="logo" src="../_static/flask-vertical.png" alt="Logo of Flask"/>
|
||||
|
||||
|
||||
<p class="logo"><a href="index.html">
|
||||
<img class="logo" src="_static/flask-vertical.png" alt="Logo of Flask"/>
|
||||
</a></p>
|
||||
|
||||
|
||||
|
||||
<h3>Contents</h3>
|
||||
<ul>
|
||||
<li><a class="reference internal" href="#">Test Coverage</a><ul>
|
||||
<li><a class="reference internal" href="#setup-and-fixtures">Setup and Fixtures</a></li>
|
||||
<li><a class="reference internal" href="#factory">Factory</a></li>
|
||||
<li><a class="reference internal" href="#database">Database</a></li>
|
||||
<li><a class="reference internal" href="#authentication">Authentication</a></li>
|
||||
<li><a class="reference internal" href="#blog">Blog</a></li>
|
||||
<li><a class="reference internal" href="#running-the-tests">Running the Tests</a></li>
|
||||
<li><a class="reference internal" href="#">Security Considerations</a><ul>
|
||||
<li><a class="reference internal" href="#resource-use">Resource Use</a></li>
|
||||
<li><a class="reference internal" href="#cross-site-scripting-xss">Cross-Site Scripting (XSS)</a></li>
|
||||
<li><a class="reference internal" href="#cross-site-request-forgery-csrf">Cross-Site Request Forgery (CSRF)</a></li>
|
||||
<li><a class="reference internal" href="#json-security">JSON Security</a></li>
|
||||
<li><a class="reference internal" href="#security-headers">Security Headers</a><ul>
|
||||
<li><a class="reference internal" href="#http-strict-transport-security-hsts">HTTP Strict Transport Security (HSTS)</a></li>
|
||||
<li><a class="reference internal" href="#content-security-policy-csp">Content Security Policy (CSP)</a></li>
|
||||
<li><a class="reference internal" href="#x-content-type-options">X-Content-Type-Options</a></li>
|
||||
<li><a class="reference internal" href="#x-frame-options">X-Frame-Options</a></li>
|
||||
<li><a class="reference internal" href="#set-cookie-options">Set-Cookie options</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a class="reference internal" href="#copy-paste-to-terminal">Copy/Paste to Terminal</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h3>Navigation</h3>
|
||||
<ul>
|
||||
<li><a href="../index.html">Overview</a>
|
||||
<li><a href="index.html">Overview</a>
|
||||
<ul>
|
||||
<li><a href="index.html">Tutorial</a>
|
||||
<ul>
|
||||
<li>Previous: <a href="install.html" title="previous chapter">Make the Project Installable</a>
|
||||
<li>Next: <a href="deploy.html" title="next chapter">Deploy to Production</a></ul>
|
||||
</li>
|
||||
<li>Previous: <a href="patterns/singlepageapplications.html" title="previous chapter">Single-Page Applications</a>
|
||||
<li>Next: <a href="deploying/index.html" title="next chapter">Deploying to Production</a>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<search id="searchbox" style="display: none" role="search">
|
||||
<h3 id="searchlabel">Quick search</h3>
|
||||
<div class="searchformwrapper">
|
||||
<form class="search" action="../search.html" method="get">
|
||||
<form class="search" action="search.html" method="get">
|
||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
||||
<input type="submit" value="Go" />
|
||||
</form>
|
||||
|
|
@ -619,4 +364,4 @@ TOTAL 153 0 44 0 100%
|
|||
Created using <a href="https://www.sphinx-doc.org/">Sphinx</a> 8.1.3.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue