547 lines
44 KiB
HTML
547 lines
44 KiB
HTML
<!DOCTYPE html>
|
|
<!--[if IE 8]><html class="no-js lt-ie9" lang="en" > <![endif]-->
|
|
<!--[if gt IE 8]><!--> <html class="no-js" lang="en" > <!--<![endif]-->
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
|
<link rel="canonical" href="https://blog.trentsonlinedocs.xyz/posts/test-qr-svg-django/">
|
|
<link rel="shortcut icon" href="../../img/favicon.ico">
|
|
<title>Test QRCODE Svg in Django - Trent's Blog</title>
|
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:400,700|Roboto+Slab:400,700|Inconsolata:400,700" />
|
|
|
|
<link rel="stylesheet" href="../../css/theme.css" />
|
|
<link rel="stylesheet" href="../../css/theme_extra.css" />
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/styles/github.min.css" />
|
|
<link href="../../extra.css" rel="stylesheet" />
|
|
|
|
<script>
|
|
// Current page data
|
|
var mkdocs_page_name = "Test QRCODE Svg in Django";
|
|
var mkdocs_page_input_path = "posts/test-qr-svg-django.md";
|
|
var mkdocs_page_url = "/posts/test-qr-svg-django/";
|
|
</script>
|
|
|
|
<script src="../../js/jquery-2.1.1.min.js" defer></script>
|
|
<script src="../../js/modernizr-2.8.3.min.js" defer></script>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/highlight.min.js"></script>
|
|
<script>hljs.initHighlightingOnLoad();</script>
|
|
</head>
|
|
|
|
<body class="wy-body-for-nav" role="document">
|
|
|
|
<div class="wy-grid-for-nav">
|
|
|
|
|
|
<nav data-toggle="wy-nav-shift" class="wy-nav-side stickynav">
|
|
<div class="wy-side-scroll">
|
|
<div class="wy-side-nav-search">
|
|
<a href="../.." class="icon icon-home"> Trent's Blog</a>
|
|
<div role="search">
|
|
<form id ="rtd-search-form" class="wy-form" action="../../search.html" method="get">
|
|
<input type="text" name="q" placeholder="Search docs" title="Type search term here" />
|
|
</form>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="wy-menu wy-menu-vertical" data-spy="affix" role="navigation" aria-label="main navigation">
|
|
<p class="caption"><span class="caption-text">RSS</span></p>
|
|
<ul>
|
|
<li class="toctree-l1"><a class="reference internal" href="../../rss/">RSS</a>
|
|
</li>
|
|
</ul>
|
|
<p class="caption"><span class="caption-text">Links</span></p>
|
|
<ul>
|
|
<li class="toctree-l1"><a class="reference internal" href="../../links/">Links</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://trentpalmer.org">TrentReads</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://blog.trentpalmer.org">AttentionSpanHistory</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://github.com/TrentSPalmer">GitHub</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://twitter.com/boringtrent">Twitter</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://www.facebook.com/trentspalmer">Facebook</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://docs.trentsonlinedocs.xyz/">TrentDocs</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://trentsonlinedocs.xyz/hugo-themes-report/hugo-themes-report.html">HugoThemesReport</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://play.google.com/store/apps/details?id=org.trentpalmer.libre_gps_parser">LibreGpsParser</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://concise-pdx.com/">ConcisePDX</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://trentspalmer.github.io/fcc-challenges/">FreeCodeCampChallenges</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="" href="https://trentpalmer.work/6a57bbe24d8244289610bf57533d6c6f/">DeviceLayout</a>
|
|
</li>
|
|
</ul>
|
|
<p class="caption"><span class="caption-text">Posts</span></p>
|
|
<ul class="current">
|
|
<li class="toctree-l1"><a class="reference internal" href="../trents-favorite-podcasts/">Trent's Favorite Podcasts</a>
|
|
</li>
|
|
<li class="toctree-l1 current"><a class="reference internal current" href="./">Test QRCODE Svg in Django</a>
|
|
<ul class="current">
|
|
<li class="toctree-l2"><a class="reference internal" href="#python-libraries-used">Python Libraries Used</a>
|
|
</li>
|
|
</ul>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../prosody-photo-uploads/">Prosody Photo Uploads</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../xmpp-apt-notifications/">Xmpp Apt Notifications</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../apache-virtual-hosts/">Apache Virtual Hosts</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../sendxmpp-handler-for-python-logging/">SENDXMPPHandler for Python Logging</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../instructions-for-tethering-from-phone/">Instructions For Tethering From Phone</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../lmde4-custom-partitions-disk-encryption/">LMDE4 Custom Partitions Disk Encryption</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../linux-move-cursor-with-keyboard/">Linux Move Cursor With Keyboard</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../simplified-raspberry-streaming/">Simplified Raspberry Streaming</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../clear-linux-encrypted-xfs-root/">Clear Linux Encrypted xfs Root</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../clear-linux-guest-virt-manager/">Clear Linux Guest Virt Manager</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../faster-partitioning-with-sgdisk/">Faster Partitioning With sgdisk</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../lmde3-xfs-full-disk-encryption/">LMDE3 xfs Full Disk Encryption</a>
|
|
</li>
|
|
<li class="toctree-l1"><a class="reference internal" href="../rewrite-hugo-themes-report-in-python/">Rewrite Hugo Themes Report in Python</a>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</nav>
|
|
|
|
<section data-toggle="wy-nav-shift" class="wy-nav-content-wrap">
|
|
|
|
|
|
<nav class="wy-nav-top" role="navigation" aria-label="top navigation">
|
|
<i data-toggle="wy-nav-top" class="fa fa-bars"></i>
|
|
<a href="../..">Trent's Blog</a>
|
|
</nav>
|
|
|
|
|
|
<div class="wy-nav-content">
|
|
<div class="rst-content">
|
|
<div role="navigation" aria-label="breadcrumbs navigation">
|
|
<ul class="wy-breadcrumbs">
|
|
<li><a href="../..">Docs</a> »</li>
|
|
|
|
|
|
|
|
<li>Posts »</li>
|
|
|
|
|
|
|
|
<li>Test QRCODE Svg in Django</li>
|
|
<li class="wy-breadcrumbs-aside">
|
|
|
|
</li>
|
|
</ul>
|
|
|
|
<hr/>
|
|
</div>
|
|
|
|
<div role="main">
|
|
<div class="section">
|
|
|
|
<p>date: 2021-04-19</p>
|
|
<h2 id="introduction"><strong>Introduction</strong></h2>
|
|
<p>I worked out a solution in django-testing, for testing a view that renders a qrcode
|
|
as an svg as an inline svg xml string.</p>
|
|
<p>In case you are not familiar with
|
|
<a href="https://en.wikipedia.org/wiki/Scalable_Vector_Graphics" target="_blank">svg</a>,
|
|
scalable vector graphics, it is an image that is
|
|
completely rendered from a
|
|
<a href="https://en.wikipedia.org/wiki/String_(computer_science)" target="_blank">string</a>
|
|
of text, or more accurately <a href="https://en.wikipedia.org/wiki/XML" target="_blank">XML</a>
|
|
text, unlike <a href="https://en.wikipedia.org/wiki/Portable_Network_Graphics" target="_blank">PNG</a>
|
|
or <a href="https://en.wikipedia.org/wiki/JPEG" target="_blank">JPG</a>, which are
|
|
<a href="https://en.wikipedia.org/wiki/Binary_file" target="_blank">binary file</a>s.</p>
|
|
<h3 id="python-libraries-used">Python Libraries Used</h3>
|
|
<ul>
|
|
<li><a href="https://pypi.org/project/beautifulsoup4/" target="_blank">BeautifulSoup</a>
|
|
to consume html</li>
|
|
<li><a href="https://pypi.org/project/CairoSVG/" target="_blank">CairoSVG</a>
|
|
to convert svg to png</li>
|
|
<li><a href="https://pypi.org/project/Pillow/" target="_blank">python-pillow</a>
|
|
to convert transparent png background to white</li>
|
|
<li><a href="https://pypi.org/project/opencv-python/" target="_blank">opencv-python</a>
|
|
to extract data from qrcode</li>
|
|
<li><a href="https://pypi.org/project/pyotp/" target="_blank">python-pyotp</a></li>
|
|
</ul>
|
|
<h2 id="form-screenshot"><strong>Form ScreenShot</strong></h2>
|
|
<p>This is the form we will be testing.</p>
|
|
<p>In real life you would confirm that you want to use
|
|
<a href="https://en.wikipedia.org/wiki/Multi-factor_authentication" target="_blank">two-factor-authentication</a>
|
|
by scanning the qrcode with an
|
|
<a href="https://play.google.com/store/apps/details?id=org.shadowice.flocke.andotp" target="_blank">authentication app</a>
|
|
on your smartphone, and then enter the
|
|
<a href="https://en.wikipedia.org/wiki/Time-based_One-Time_Password" target="_blank">time-based one-time password</a>.</p>
|
|
<p>totp codes are derived from two things and two things only. A secret key and a time such as the current time.
|
|
For instance, an authentication application can tell you what your totp code is, but of course
|
|
in this testing scenario we use
|
|
<a href="https://pypi.org/project/pyotp/" target="_blank">python-pyotp</a>, after extracting
|
|
the secret key from the qrcode.</p>
|
|
<div align="center">
|
|
<em>enable totp confirmation form</em>
|
|
</div>
|
|
|
|
<p><img alt=""ScreenShot of a confirmation form for enabling totp (in a django app)"" class="center" src="../../photos/IMG_screenshot_enable_totp_confirmation_form.jpg" /></p>
|
|
<h2 id="import-python-libraries"><strong>Import Python Libraries</strong></h2>
|
|
<div class="highlight"><pre><span></span><code><span class="c1"># tp/accounts/tests/test_enable_totp_view.py</span>
|
|
<span class="c1"># python manage.py test accounts.tests.test_enable_totp_view</span>
|
|
<span class="kn">from</span> <span class="nn">django.test</span> <span class="kn">import</span> <span class="n">TestCase</span>
|
|
<span class="kn">from</span> <span class="nn">django.contrib.auth.models</span> <span class="kn">import</span> <span class="n">User</span>
|
|
<span class="kn">from</span> <span class="nn">accounts.models</span> <span class="kn">import</span> <span class="n">Account</span>
|
|
<span class="kn">from</span> <span class="nn">django.urls</span> <span class="kn">import</span> <span class="n">reverse</span>
|
|
<span class="kn">from</span> <span class="nn">bs4</span> <span class="kn">import</span> <span class="n">BeautifulSoup</span>
|
|
<span class="kn">import</span> <span class="nn">cv2</span>
|
|
<span class="kn">from</span> <span class="nn">cairosvg</span> <span class="kn">import</span> <span class="n">svg2png</span>
|
|
<span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
|
|
<span class="kn">import</span> <span class="nn">pyotp</span>
|
|
<span class="kn">import</span> <span class="nn">pathlib</span>
|
|
</code></pre></div>
|
|
<p>The Account model has a one-to-one relationship with the Django built-in
|
|
User model. This is where we keep track of the totp secret key and
|
|
boolean value for having totp authentication enabled.</p>
|
|
<h2 id="setup-testcase"><strong>setUp TestCase</strong></h2>
|
|
<div class="highlight"><pre><span></span><code><span class="o">...</span>
|
|
<span class="kn">import</span> <span class="nn">pathlib</span>
|
|
|
|
|
|
<span class="k">class</span> <span class="nc">TestEnableTOTPViewTestCase</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
|
|
|
<span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
|
<span class="n">user_a</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
|
|
<span class="n">username</span><span class="o">=</span><span class="s1">'user_a'</span><span class="p">)</span>
|
|
<span class="n">user_a</span><span class="o">.</span><span class="n">set_password</span><span class="p">(</span><span class="s1">'password_user_a'</span><span class="p">)</span>
|
|
<span class="n">user_a</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
|
|
<span class="n">Account</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">user</span><span class="o">=</span><span class="n">user_a</span><span class="p">)</span>
|
|
</code></pre></div>
|
|
<p>We create a test user and save that in the test database.</p>
|
|
<h2 id="get-form"><strong>GET Form</strong></h2>
|
|
<p><div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">TestEnableTOTPViewTestCase</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="k">def</span> <span class="nf">test_enable_totp_view</span><span class="p">(</span><span class="bp">self</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">login</span><span class="p">(</span>
|
|
<span class="n">username</span><span class="o">=</span><span class="s1">'user_a'</span><span class="p">,</span>
|
|
<span class="n">password</span><span class="o">=</span><span class="s1">'password_user_a'</span>
|
|
<span class="p">)</span>
|
|
<span class="n">get_response</span> <span class="o">=</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="n">reverse</span><span class="p">(</span><span class="s1">'accounts:enable_totp'</span><span class="p">)</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEquals</span><span class="p">(</span>
|
|
<span class="n">get_response</span><span class="o">.</span><span class="n">status_code</span><span class="p">,</span> <span class="mi">200</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertTemplateUsed</span><span class="p">(</span>
|
|
<span class="n">get_response</span><span class="p">,</span> <span class="s1">'accounts/totp_form.html'</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEquals</span><span class="p">(</span>
|
|
<span class="n">get_response</span><span class="o">.</span><span class="n">request</span><span class="p">[</span><span class="s1">'PATH_INFO'</span><span class="p">],</span>
|
|
<span class="s1">'/accounts/enable-totp/'</span>
|
|
<span class="p">)</span>
|
|
</code></pre></div>
|
|
The TestCase requires two requests. In the first request we GET the form
|
|
so that we can consume the qrcode.</p>
|
|
<h2 id="consume-svg-with-beautifulsoup"><strong>Consume SVG with BeautifulSoup</strong></h2>
|
|
<p><div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">TestEnableTOTPViewTestCase</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="k">def</span> <span class="nf">test_enable_totp_view</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="n">soup</span> <span class="o">=</span> <span class="n">BeautifulSoup</span><span class="p">(</span>
|
|
<span class="n">get_response</span><span class="o">.</span><span class="n">content</span><span class="p">,</span> <span class="n">features</span><span class="o">=</span><span class="s2">"lxml"</span>
|
|
<span class="p">)</span>
|
|
<span class="n">svg_container</span> <span class="o">=</span> <span class="n">soup</span><span class="o">.</span><span class="n">find</span><span class="p">(</span>
|
|
<span class="s2">"div"</span><span class="p">,</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="s2">"svgcontainer"</span><span class="p">}</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertIsNotNone</span><span class="p">(</span><span class="n">svg_container</span><span class="p">)</span>
|
|
<span class="n">scsvg</span> <span class="o">=</span> <span class="n">svg_container</span><span class="o">.</span><span class="n">findChild</span><span class="p">(</span>
|
|
<span class="s2">"svg"</span><span class="p">,</span> <span class="n">recursive</span><span class="o">=</span><span class="kc">False</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertIsNotNone</span><span class="p">(</span><span class="n">scsvg</span><span class="p">)</span>
|
|
|
|
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">'qr.svg'</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
|
<span class="n">x_string</span> <span class="o">=</span> <span class="s2">"<?xml version='1.0'"</span>
|
|
<span class="n">x_string</span> <span class="o">+=</span> <span class="s2">" encoding='utf-8'?></span><span class="se">\n</span><span class="s2">"</span>
|
|
<span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">x_string</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">scsvg</span><span class="p">))</span>
|
|
</code></pre></div>
|
|
The inline xml for the svg comes to us as a child of a
|
|
<code>div</code> with an <code>id</code> of <em>svgcontainer</em>. We capture the xml of the svg in the variable
|
|
<code>scsvg</code>, and then write it out to disc.</p>
|
|
<h2 id="svg2png"><strong>svg2png</strong></h2>
|
|
<p><div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">TestEnableTOTPViewTestCase</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="k">def</span> <span class="nf">test_enable_totp_view</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="n">svg2png</span><span class="p">(</span>
|
|
<span class="n">url</span><span class="o">=</span><span class="s1">'qr.svg'</span><span class="p">,</span> <span class="n">write_to</span><span class="o">=</span><span class="s1">'qr.png'</span><span class="p">,</span>
|
|
<span class="n">scale</span><span class="o">=</span><span class="mi">8</span>
|
|
<span class="p">)</span>
|
|
</code></pre></div>
|
|
With svg2png from CairoSVG, we convert the svg to png format. Opencv seems
|
|
unable to consume the qrcode unless you scale it up.</p>
|
|
<h2 id="add-white-background"><strong>Add White Background</strong></h2>
|
|
<p><div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">TestEnableTOTPViewTestCase</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="k">def</span> <span class="nf">test_enable_totp_view</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="n">t_image</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s1">'qr.png'</span><span class="p">)</span>
|
|
<span class="n">t_image</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
|
|
<span class="n">background</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
|
|
<span class="s2">"RGB"</span><span class="p">,</span> <span class="n">t_image</span><span class="o">.</span><span class="n">size</span><span class="p">,</span>
|
|
<span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span>
|
|
<span class="p">)</span>
|
|
<span class="n">background</span><span class="o">.</span><span class="n">paste</span><span class="p">(</span>
|
|
<span class="n">t_image</span><span class="p">,</span>
|
|
<span class="n">mask</span><span class="o">=</span><span class="n">t_image</span><span class="o">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">3</span><span class="p">]</span>
|
|
<span class="p">)</span>
|
|
<span class="n">background</span><span class="o">.</span><span class="n">save</span><span class="p">(</span>
|
|
<span class="s1">'qr.jpg'</span><span class="p">,</span> <span class="s2">"JPEG"</span><span class="p">,</span>
|
|
<span class="n">quality</span><span class="o">=</span><span class="mi">100</span>
|
|
<span class="p">)</span>
|
|
</code></pre></div>
|
|
We use Image from python-pillow to change the background from transparent
|
|
to white. Opencv seems unable to consume the qrcode when it has a transparent
|
|
background.</p>
|
|
<h2 id="extract-data-from-qrcode"><strong>Extract Data From QRCODE</strong></h2>
|
|
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">TestEnableTOTPViewTestCase</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="k">def</span> <span class="nf">test_enable_totp_view</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="n">image</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s1">'qr.jpg'</span><span class="p">)</span>
|
|
<span class="n">qr_det</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">QRCodeDetector</span><span class="p">()</span>
|
|
<span class="n">qrdata</span> <span class="o">=</span> <span class="n">qr_det</span><span class="o">.</span><span class="n">detectAndDecode</span><span class="p">(</span><span class="n">image</span><span class="p">)</span>
|
|
<span class="n">totp_code</span> <span class="o">=</span> <span class="n">pyotp</span><span class="o">.</span><span class="n">TOTP</span><span class="p">(</span>
|
|
<span class="n">pyotp</span><span class="o">.</span><span class="n">parse_uri</span><span class="p">(</span><span class="n">qrdata</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="o">.</span><span class="n">secret</span>
|
|
<span class="p">)</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
|
|
</code></pre></div>
|
|
<ul>
|
|
<li><code>qrdata[0]</code> will be the <em>otpauth_uri</em>.</li>
|
|
<li><code>pyotp.parse_uri(qrdata[0]).secret</code> is the secret key.</li>
|
|
<li><code>totp_code</code> is the one-time password.</li>
|
|
</ul>
|
|
<h2 id="post-the-totp_code"><strong>POST the <code>totp_code</code></strong></h2>
|
|
<p><div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">TestEnableTOTPViewTestCase</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="k">def</span> <span class="nf">test_enable_totp_view</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
|
<span class="o">...</span>
|
|
<span class="n">totp_code</span> <span class="o">=</span> <span class="n">pyotp</span><span class="o">.</span><span class="n">TOTP</span><span class="p">(</span>
|
|
<span class="n">pyotp</span><span class="o">.</span><span class="n">parse_uri</span><span class="p">(</span><span class="n">qrdata</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="o">.</span><span class="n">secret</span>
|
|
<span class="p">)</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
|
|
<span class="n">response</span> <span class="o">=</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="n">reverse</span><span class="p">(</span><span class="s1">'accounts:enable_totp'</span><span class="p">),</span>
|
|
<span class="p">{</span><span class="s1">'totp_code'</span><span class="p">:</span> <span class="n">totp_code</span><span class="p">},</span>
|
|
<span class="n">follow</span><span class="o">=</span><span class="kc">True</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEquals</span><span class="p">(</span>
|
|
<span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">,</span> <span class="mi">200</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertTemplateUsed</span><span class="p">(</span>
|
|
<span class="n">response</span><span class="p">,</span> <span class="s1">'base_form.html'</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEquals</span><span class="p">(</span>
|
|
<span class="n">response</span><span class="o">.</span><span class="n">request</span><span class="p">[</span><span class="s1">'PATH_INFO'</span><span class="p">],</span>
|
|
<span class="s1">'/accounts/edit-profile/'</span>
|
|
<span class="p">)</span>
|
|
<span class="n">user_a</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
|
|
<span class="n">username</span><span class="o">=</span><span class="s1">'user_a'</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="n">user_a</span><span class="o">.</span><span class="n">account</span><span class="o">.</span><span class="n">use_totp</span><span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEquals</span><span class="p">(</span>
|
|
<span class="nb">len</span><span class="p">(</span><span class="n">user_a</span><span class="o">.</span><span class="n">account</span><span class="o">.</span><span class="n">totp_key</span><span class="p">),</span> <span class="mi">16</span>
|
|
<span class="p">)</span>
|
|
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">'qr.svg'</span><span class="p">)</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
|
|
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">'qr.png'</span><span class="p">)</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
|
|
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">'qr.jpg'</span><span class="p">)</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
|
|
</code></pre></div>
|
|
Post the <code>totp_code</code> back to the form, and then verify
|
|
that the database is updated, and then delete the image
|
|
files.</p>
|
|
<h2 id="complete-testcase"><strong>Complete TestCase</strong></h2>
|
|
<div class="highlight"><pre><span></span><code><span class="c1"># tp/accounts/tests/test_enable_totp_view.py</span>
|
|
<span class="c1"># python manage.py test accounts.tests.test_enable_totp_view</span>
|
|
<span class="kn">from</span> <span class="nn">django.test</span> <span class="kn">import</span> <span class="n">TestCase</span>
|
|
<span class="kn">from</span> <span class="nn">django.contrib.auth.models</span> <span class="kn">import</span> <span class="n">User</span>
|
|
<span class="kn">from</span> <span class="nn">accounts.models</span> <span class="kn">import</span> <span class="n">Account</span>
|
|
<span class="kn">from</span> <span class="nn">django.urls</span> <span class="kn">import</span> <span class="n">reverse</span>
|
|
<span class="kn">from</span> <span class="nn">bs4</span> <span class="kn">import</span> <span class="n">BeautifulSoup</span>
|
|
<span class="kn">import</span> <span class="nn">cv2</span>
|
|
<span class="kn">from</span> <span class="nn">cairosvg</span> <span class="kn">import</span> <span class="n">svg2png</span>
|
|
<span class="kn">from</span> <span class="nn">PIL</span> <span class="kn">import</span> <span class="n">Image</span>
|
|
<span class="kn">import</span> <span class="nn">pyotp</span>
|
|
|
|
|
|
<span class="k">class</span> <span class="nc">TestEnableTOTPViewTestCase</span><span class="p">(</span><span class="n">TestCase</span><span class="p">):</span>
|
|
|
|
<span class="c1"># setUP TestCase</span>
|
|
<span class="k">def</span> <span class="nf">setUp</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
|
|
<span class="n">user_a</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span>
|
|
<span class="n">username</span><span class="o">=</span><span class="s1">'user_a'</span><span class="p">)</span>
|
|
<span class="n">user_a</span><span class="o">.</span><span class="n">set_password</span><span class="p">(</span><span class="s1">'password_user_a'</span><span class="p">)</span>
|
|
<span class="n">user_a</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
|
|
<span class="n">Account</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">user</span><span class="o">=</span><span class="n">user_a</span><span class="p">)</span>
|
|
|
|
<span class="k">def</span> <span class="nf">test_enable_totp_view</span><span class="p">(</span><span class="bp">self</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">login</span><span class="p">(</span>
|
|
<span class="n">username</span><span class="o">=</span><span class="s1">'user_a'</span><span class="p">,</span>
|
|
<span class="n">password</span><span class="o">=</span><span class="s1">'password_user_a'</span>
|
|
<span class="p">)</span>
|
|
|
|
<span class="c1"># GET Form</span>
|
|
<span class="n">get_response</span> <span class="o">=</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="n">reverse</span><span class="p">(</span><span class="s1">'accounts:enable_totp'</span><span class="p">)</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEquals</span><span class="p">(</span>
|
|
<span class="n">get_response</span><span class="o">.</span><span class="n">status_code</span><span class="p">,</span> <span class="mi">200</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertTemplateUsed</span><span class="p">(</span>
|
|
<span class="n">get_response</span><span class="p">,</span> <span class="s1">'accounts/totp_form.html'</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEquals</span><span class="p">(</span>
|
|
<span class="n">get_response</span><span class="o">.</span><span class="n">request</span><span class="p">[</span><span class="s1">'PATH_INFO'</span><span class="p">],</span>
|
|
<span class="s1">'/accounts/enable-totp/'</span>
|
|
<span class="p">)</span>
|
|
|
|
<span class="c1"># Consume SVG with BeautifulSoup</span>
|
|
<span class="n">soup</span> <span class="o">=</span> <span class="n">BeautifulSoup</span><span class="p">(</span>
|
|
<span class="n">get_response</span><span class="o">.</span><span class="n">content</span><span class="p">,</span> <span class="n">features</span><span class="o">=</span><span class="s2">"lxml"</span>
|
|
<span class="p">)</span>
|
|
<span class="n">svg_container</span> <span class="o">=</span> <span class="n">soup</span><span class="o">.</span><span class="n">find</span><span class="p">(</span>
|
|
<span class="s2">"div"</span><span class="p">,</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="s2">"svgcontainer"</span><span class="p">}</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertIsNotNone</span><span class="p">(</span><span class="n">svg_container</span><span class="p">)</span>
|
|
<span class="n">scsvg</span> <span class="o">=</span> <span class="n">svg_container</span><span class="o">.</span><span class="n">findChild</span><span class="p">(</span>
|
|
<span class="s2">"svg"</span><span class="p">,</span> <span class="n">recursive</span><span class="o">=</span><span class="kc">False</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertIsNotNone</span><span class="p">(</span><span class="n">scsvg</span><span class="p">)</span>
|
|
|
|
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">'qr.svg'</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
|
|
<span class="n">x_string</span> <span class="o">=</span> <span class="s2">"<?xml version='1.0'"</span>
|
|
<span class="n">x_string</span> <span class="o">+=</span> <span class="s2">" encoding='utf-8'?></span><span class="se">\n</span><span class="s2">"</span>
|
|
<span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">x_string</span> <span class="o">+</span> <span class="nb">str</span><span class="p">(</span><span class="n">scsvg</span><span class="p">))</span>
|
|
|
|
<span class="c1"># svg2png</span>
|
|
<span class="n">svg2png</span><span class="p">(</span>
|
|
<span class="n">url</span><span class="o">=</span><span class="s1">'qr.svg'</span><span class="p">,</span> <span class="n">write_to</span><span class="o">=</span><span class="s1">'qr.png'</span><span class="p">,</span>
|
|
<span class="n">scale</span><span class="o">=</span><span class="mi">8</span>
|
|
<span class="p">)</span>
|
|
|
|
<span class="c1"># add white background</span>
|
|
<span class="n">t_image</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s1">'qr.png'</span><span class="p">)</span>
|
|
<span class="n">t_image</span><span class="o">.</span><span class="n">load</span><span class="p">()</span>
|
|
<span class="n">background</span> <span class="o">=</span> <span class="n">Image</span><span class="o">.</span><span class="n">new</span><span class="p">(</span>
|
|
<span class="s2">"RGB"</span><span class="p">,</span> <span class="n">t_image</span><span class="o">.</span><span class="n">size</span><span class="p">,</span>
|
|
<span class="p">(</span><span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">,</span> <span class="mi">255</span><span class="p">)</span>
|
|
<span class="p">)</span>
|
|
<span class="n">background</span><span class="o">.</span><span class="n">paste</span><span class="p">(</span>
|
|
<span class="n">t_image</span><span class="p">,</span>
|
|
<span class="n">mask</span><span class="o">=</span><span class="n">t_image</span><span class="o">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">3</span><span class="p">]</span>
|
|
<span class="p">)</span>
|
|
<span class="n">background</span><span class="o">.</span><span class="n">save</span><span class="p">(</span>
|
|
<span class="s1">'qr.jpg'</span><span class="p">,</span> <span class="s2">"JPEG"</span><span class="p">,</span>
|
|
<span class="n">quality</span><span class="o">=</span><span class="mi">100</span>
|
|
<span class="p">)</span>
|
|
|
|
<span class="c1"># extract data from qrcode</span>
|
|
<span class="n">image</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">imread</span><span class="p">(</span><span class="s1">'qr.jpg'</span><span class="p">)</span>
|
|
<span class="n">qr_det</span> <span class="o">=</span> <span class="n">cv2</span><span class="o">.</span><span class="n">QRCodeDetector</span><span class="p">()</span>
|
|
<span class="n">qrdata</span> <span class="o">=</span> <span class="n">qr_det</span><span class="o">.</span><span class="n">detectAndDecode</span><span class="p">(</span><span class="n">image</span><span class="p">)</span>
|
|
<span class="n">totp_code</span> <span class="o">=</span> <span class="n">pyotp</span><span class="o">.</span><span class="n">TOTP</span><span class="p">(</span>
|
|
<span class="n">pyotp</span><span class="o">.</span><span class="n">parse_uri</span><span class="p">(</span><span class="n">qrdata</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="o">.</span><span class="n">secret</span>
|
|
<span class="p">)</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
|
|
<span class="n">totp_code</span> <span class="o">=</span> <span class="n">pyotp</span><span class="o">.</span><span class="n">TOTP</span><span class="p">(</span>
|
|
<span class="n">pyotp</span><span class="o">.</span><span class="n">parse_uri</span><span class="p">(</span><span class="n">qrdata</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span><span class="o">.</span><span class="n">secret</span>
|
|
<span class="p">)</span><span class="o">.</span><span class="n">now</span><span class="p">()</span>
|
|
|
|
<span class="c1"># POST the `totp_code`</span>
|
|
<span class="n">response</span> <span class="o">=</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="n">reverse</span><span class="p">(</span><span class="s1">'accounts:enable_totp'</span><span class="p">),</span>
|
|
<span class="p">{</span><span class="s1">'totp_code'</span><span class="p">:</span> <span class="n">totp_code</span><span class="p">},</span>
|
|
<span class="n">follow</span><span class="o">=</span><span class="kc">True</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEquals</span><span class="p">(</span>
|
|
<span class="n">response</span><span class="o">.</span><span class="n">status_code</span><span class="p">,</span> <span class="mi">200</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertTemplateUsed</span><span class="p">(</span>
|
|
<span class="n">response</span><span class="p">,</span> <span class="s1">'base_form.html'</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEquals</span><span class="p">(</span>
|
|
<span class="n">response</span><span class="o">.</span><span class="n">request</span><span class="p">[</span><span class="s1">'PATH_INFO'</span><span class="p">],</span>
|
|
<span class="s1">'/accounts/edit-profile/'</span>
|
|
<span class="p">)</span>
|
|
<span class="n">user_a</span> <span class="o">=</span> <span class="n">User</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span>
|
|
<span class="n">username</span><span class="o">=</span><span class="s1">'user_a'</span>
|
|
<span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertTrue</span><span class="p">(</span><span class="n">user_a</span><span class="o">.</span><span class="n">account</span><span class="o">.</span><span class="n">use_totp</span><span class="p">)</span>
|
|
<span class="bp">self</span><span class="o">.</span><span class="n">assertEquals</span><span class="p">(</span>
|
|
<span class="nb">len</span><span class="p">(</span><span class="n">user_a</span><span class="o">.</span><span class="n">account</span><span class="o">.</span><span class="n">totp_key</span><span class="p">),</span> <span class="mi">16</span>
|
|
<span class="p">)</span>
|
|
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">'qr.svg'</span><span class="p">)</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
|
|
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">'qr.png'</span><span class="p">)</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
|
|
<span class="n">pathlib</span><span class="o">.</span><span class="n">Path</span><span class="p">(</span><span class="s1">'qr.jpg'</span><span class="p">)</span><span class="o">.</span><span class="n">unlink</span><span class="p">()</span>
|
|
</code></pre></div>
|
|
|
|
</div>
|
|
</div>
|
|
<footer>
|
|
|
|
<div class="rst-footer-buttons" role="navigation" aria-label="footer navigation">
|
|
|
|
<a href="../prosody-photo-uploads/" class="btn btn-neutral float-right" title="Prosody Photo Uploads">Next <span class="icon icon-circle-arrow-right"></span></a>
|
|
|
|
|
|
<a href="../trents-favorite-podcasts/" class="btn btn-neutral" title="Trent's Favorite Podcasts"><span class="icon icon-circle-arrow-left"></span> Previous</a>
|
|
|
|
</div>
|
|
|
|
|
|
<hr/>
|
|
|
|
<div role="contentinfo">
|
|
<!-- Copyright etc -->
|
|
|
|
</div>
|
|
|
|
Built with <a href="https://www.mkdocs.org/">MkDocs</a> using a <a href="https://github.com/snide/sphinx_rtd_theme">theme</a> provided by <a href="https://readthedocs.org">Read the Docs</a>.
|
|
</footer>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
</section>
|
|
|
|
</div>
|
|
|
|
<div class="rst-versions" role="note" aria-label="versions">
|
|
<span class="rst-current-version" data-toggle="rst-current-version">
|
|
|
|
|
|
<span><a href="../trents-favorite-podcasts/" style="color: #fcfcfc">« Previous</a></span>
|
|
|
|
|
|
<span><a href="../prosody-photo-uploads/" style="color: #fcfcfc">Next »</a></span>
|
|
|
|
</span>
|
|
</div>
|
|
<script>var base_url = '../..';</script>
|
|
<script src="../../js/theme_extra.js" defer></script>
|
|
<script src="../../js/theme.js" defer></script>
|
|
<script src="../../search/main.js" defer></script>
|
|
<script defer>
|
|
window.onload = function () {
|
|
SphinxRtdTheme.Navigation.enable(true);
|
|
};
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|