<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>bug-fixes on Launchpad Blog</title>
    <link>https://blog.launchpad.net/tags/bug-fixes-2/</link>
    <description>News and updates from the Launchpad team</description>
    <generator>Hugo</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 20 Aug 2020 10:01:59 +0000</lastBuildDate>
    <atom:link href="https://blog.launchpad.net/feed/tags/bug-fixes-2/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Login regression for users with non-ASCII names</title>
      <link>https://blog.launchpad.net/general/login-regression-for-users-with-non-ascii-names/</link>
      <pubDate>Thu, 20 Aug 2020 10:01:59 +0000</pubDate>
      <dc:creator>Colin Watson</dc:creator>
      <guid isPermaLink="false">https://blog.launchpad.net/?p=4339</guid>
      <description>&lt;p&gt;On 2020-08-13, we deployed an update that caused users whose full names contain non-ASCII characters (which is of course &lt;a href=&#34;https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/&#34;&gt;very common&lt;/a&gt;) to be unable to log into Launchpad.  We heard about this serious regression from users on 2020-08-17, and rolled out a fix on 2020-08-18.  We&amp;rsquo;re sorry about this; it doesn&amp;rsquo;t meet the standards of both inclusion and quality that we set for ourselves.  This post aims to explain what happened, technical details of why it happened, and the steps we&amp;rsquo;ve taken to avoid it happening again.&lt;/p&gt;
&lt;p&gt;Launchpad still runs on Python 2.  This is &lt;a href=&#34;https://www.python.org/doc/sunset-python-2/&#34;&gt;a problem&lt;/a&gt;, and we&amp;rsquo;ve been gradually chipping away at it for the last couple of years.  With about three-quarters of a million lines of Python code in the main tree and over 200 dependencies, it&amp;rsquo;s a big job - but we&amp;rsquo;re well underway!&lt;/p&gt;
&lt;p&gt;Some of those dependencies have been difficult problems in their own right.  The one at issue here was &lt;a href=&#34;https://pypi.org/project/python-openid/&#34;&gt;python-openid&lt;/a&gt;, which we use as part of our login workflow, but which hasn&amp;rsquo;t been actively maintained for over ten years.  Fortunately, in this case we didn&amp;rsquo;t have to port it ourselves, because there were already a couple of forks featuring Python 3 support while preserving more or less the same interface: we chose &lt;a href=&#34;https://pypi.org/project/python-openid2/&#34;&gt;python-openid2&lt;/a&gt; on the grounds that it had done a good job of maintaining both Python 2 and 3 support in the same codebase, which we needed in order to arrange a practical transition, and that it was in itself well-maintained.  We worked with upstream to fix a couple of issues discovered by the Launchpad test suite that blocked us migrating to it (notably &lt;a href=&#34;https://github.com/ziima/python-openid/pull/41&#34;&gt;PR #41&lt;/a&gt;, although that was fixed as &lt;a href=&#34;https://github.com/ziima/python-openid/pull/43&#34;&gt;PR #43&lt;/a&gt; instead), and &lt;a href=&#34;https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/387907&#34;&gt;switched Launchpad over&lt;/a&gt; once python-openid2 3.2 was released.  So far, so good.&lt;/p&gt;
&lt;p&gt;One of the major reasons for much of the disruption in the Python 3 transition was to provide a clean separation between the concept of a sequence of bytes and a text string, which was often a problem for code that needed to handle Unicode: it&amp;rsquo;s all too common in Python 2 to have code that works on the ASCII domain (which can be represented either as &lt;code&gt;str&lt;/code&gt; or &lt;code&gt;unicode&lt;/code&gt;) but that fails on Unicode strings outside that subset.  Launchpad is less prone to that than many Python 2 applications because the &lt;a href=&#34;https://en.wikipedia.org/wiki/Object-relational_mapping&#34;&gt;ORM&lt;/a&gt; we use (&lt;a href=&#34;https://pypi.org/project/storm/&#34;&gt;Storm&lt;/a&gt;) has always been relatively strict about the boundary between bytes and text; nevertheless, having a stricter data model here is a good thing for us in the long term.  It might seem ironic that we ran into exactly such a bug as part of porting to Python 3; but then, we aren&amp;rsquo;t using the new interpreter yet.&lt;/p&gt;
&lt;p&gt;Launchpad uses the &lt;a href=&#34;https://openid.net/specs/openid-simple-registration-extension-1_0.html&#34;&gt;OpenID Simple Registration Extension&lt;/a&gt; in its login workflow.  It specifically requests the user&amp;rsquo;s full name from Canonical&amp;rsquo;s OpenID provider (&lt;a href=&#34;https://login.ubuntu.com/&#34;&gt;login.ubuntu.com&lt;/a&gt;, which we generally call &amp;ldquo;SSO&amp;rdquo;): this means that if the user has an SSO account but not yet a Launchpad account, we can create a Launchpad account for them without them needing to enter their name again.  That full name is encoded as a UTF-8 string, which in turn is URL-encoded using the usual %xx mechanism.  This means that if, say, your name is &lt;a href=&#34;https://en.wikipedia.org/wiki/Gr%C3%A1inne_N%C3%AD_Mh%C3%A1ille&#34;&gt;Gráinne Ní Mháille&lt;/a&gt;, it will show up in the OpenID response&amp;rsquo;s query string as &lt;code&gt;openid.sreg.fullname=Gr%C3%A1inne+N%C3%AD+Mh%C3%A1ille&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;python-openid2 uses its &lt;code&gt;openid.urinorm&lt;/code&gt; module to normalise parts of the response, decoding and re-encoding it to make sure comparisons work as expected; this is built on top of the URL handling code in Python&amp;rsquo;s standard library.  Now, unlike Python 3, Python 2&amp;rsquo;s &lt;a href=&#34;https://docs.python.org/2/library/urllib.html#urllib.urlencode&#34;&gt;urlencode&lt;/a&gt; has undocumented restrictions on values in the &lt;code&gt;query&lt;/code&gt; argument: if the &lt;code&gt;doseq&lt;/code&gt; argument is False (the default), then it converts values using &lt;code&gt;str(v)&lt;/code&gt;, while if it&amp;rsquo;s True then it converts Unicode values using &lt;code&gt;v.encode(&amp;quot;ASCII&amp;quot;, &amp;quot;replace&amp;quot;)&lt;/code&gt; (potentially losing information!).  In this case, &lt;code&gt;doseq&lt;/code&gt; is False, and the input given to it is always text (&lt;code&gt;unicode&lt;/code&gt; on Python 2): this works fine if the input is within the ASCII subset, but if it&amp;rsquo;s not:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; urlencode({u&amp;#39;openid.sreg.fullname&amp;#39;: u&amp;#39;Gráinne Ní Mháille&amp;#39;})
Traceback (most recent call last):
  File &amp;#34;&amp;lt;stdin&amp;gt;&amp;#34;, line 1, in &amp;lt;module&amp;gt;
  File &amp;#34;/usr/lib/python2.7/urllib.py&amp;#34;, line 1350, in urlencode
    v = quote_plus(str(v))
UnicodeEncodeError: &amp;#39;ascii&amp;#39; codec can&amp;#39;t encode character u&amp;#39;\xe1&amp;#39; in position 2: ordinal not in range(128)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The fix is that on Python 2 one must always pass values to &lt;code&gt;urlencode&lt;/code&gt; as bytes rather than text:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; urlencode({u&amp;#39;openid.sreg.fullname&amp;#39;: u&amp;#39;Gráinne Ní Mháille&amp;#39;.encode(&amp;#39;UTF-8&amp;#39;)})
&amp;#39;openid.sreg.fullname=Gr%C3%A1inne+N%C3%AD+Mh%C3%A1ille&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;We&amp;rsquo;ve sent &lt;a href=&#34;https://github.com/ziima/python-openid/pull/47&#34;&gt;PR #47&lt;/a&gt; to python-openid2 to implement this.  We&amp;rsquo;ve also made a temporary local fork of python-openid2 containing this patch and deployed it to Launchpad production.&lt;/p&gt;
&lt;p&gt;One thing to be clear about here: though the root cause was a bug in python-openid2, it&amp;rsquo;s &lt;em&gt;our&lt;/em&gt; responsibility to make sure it works correctly when integrated into Launchpad.&lt;/p&gt;
&lt;p&gt;We missed this bug because of a gap in testing: although we did test the full login workflow, we only did so with a test user whose full name was entirely ASCII.  We&amp;rsquo;ve &lt;a href=&#34;https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/389437&#34;&gt;closed this gap&lt;/a&gt; now, so we&amp;rsquo;ll catch it if a dependency regresses in future.&lt;/p&gt;
</description>
    </item>
    
  </channel>
</rss>
