Merge lp:~cjwatson/launchpad/htpasswd-salt into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18476
Proposed branch: lp:~cjwatson/launchpad/htpasswd-salt
Merge into: lp:launchpad
Diff against target: 102 lines (+33/-13)
2 files modified
lib/lp/archivepublisher/htaccess.py (+20/-4)
lib/lp/archivepublisher/tests/test_htaccess.py (+13/-9)
To merge this branch: bzr merge lp:~cjwatson/launchpad/htpasswd-salt
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+332013@code.launchpad.net

Commit message

Ensure that PPA .htpasswd salts are drawn from the correct alphabet.

Description of the change

This is all a horrible hack, but I think it'll do until we have a better authentication system.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/archivepublisher/htaccess.py'
2--- lib/lp/archivepublisher/htaccess.py 2016-07-13 16:42:34 +0000
3+++ lib/lp/archivepublisher/htaccess.py 2017-10-09 12:45:58 +0000
4@@ -1,6 +1,6 @@
5 #!/usr/bin/python
6 #
7-# Copyright 2010 Canonical Ltd. This software is licensed under the
8+# Copyright 2010-2017 Canonical Ltd. This software is licensed under the
9 # GNU Affero General Public License version 3 (see the file LICENSE).
10
11 """Writing of htaccess and htpasswd files."""
12@@ -13,7 +13,7 @@
13 'write_htpasswd',
14 ]
15
16-
17+import base64
18 import crypt
19 from operator import itemgetter
20 import os
21@@ -65,6 +65,21 @@
22 file.close()
23
24
25+# XXX cjwatson 2017-10-09: This whole mechanism of writing password files to
26+# disk (as opposed to e.g. using a WSGI authentication provider that checks
27+# passwords against the database) is terrible, but as long as we're using it
28+# we should use something like bcrypt rather than DES-based crypt.
29+def make_salt(s):
30+ """Produce a salt from an input string.
31+
32+ This ensures that salts are drawn from the correct alphabet
33+ ([./a-zA-Z0-9]).
34+ """
35+ # As long as the input string is at least one character long, there will
36+ # be no padding within the first two characters.
37+ return base64.b64encode((s or " ").encode("UTF-8"), altchars=b"./")[:2]
38+
39+
40 def htpasswd_credentials_for_archive(archive):
41 """Return credentials for an archive for use with write_htpasswd.
42
43@@ -94,10 +109,11 @@
44 for person_id, token_name, token in tokens:
45 if token_name:
46 # A named auth token.
47- output.append(('+' + token_name, token, token_name[:2]))
48+ output.append(('+' + token_name, token, make_salt(token_name)))
49 else:
50 # A subscription auth token.
51- output.append((names[person_id], token, names[person_id][:2]))
52+ output.append(
53+ (names[person_id], token, make_salt(names[person_id])))
54
55 # The first .htpasswd entry is the buildd_secret.
56 yield (BUILDD_USER_NAME, archive.buildd_secret, BUILDD_USER_NAME[:2])
57
58=== modified file 'lib/lp/archivepublisher/tests/test_htaccess.py'
59--- lib/lp/archivepublisher/tests/test_htaccess.py 2016-07-13 16:42:34 +0000
60+++ lib/lp/archivepublisher/tests/test_htaccess.py 2017-10-09 12:45:58 +0000
61@@ -1,4 +1,4 @@
62-# Copyright 2009-2010 Canonical Ltd. This software is licensed under the
63+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
64 # GNU Affero General Public License version 3 (see the file LICENSE).
65
66 """Test htaccess/htpasswd file generation. """
67@@ -111,23 +111,27 @@
68 self.ppa.buildd_secret = "geheim"
69 name12 = getUtility(IPersonSet).getByName("name12")
70 name16 = getUtility(IPersonSet).getByName("name16")
71+ hyphenated = self.factory.makePerson(name="a-b-c")
72 self.ppa.newSubscription(name12, self.ppa.owner)
73 self.ppa.newSubscription(name16, self.ppa.owner)
74+ self.ppa.newSubscription(hyphenated, self.ppa.owner)
75 first_created_token = self.ppa.newAuthToken(name16)
76 second_created_token = self.ppa.newAuthToken(name12)
77+ third_created_token = self.ppa.newAuthToken(hyphenated)
78 named_token_20 = self.ppa.newNamedAuthToken(u"name20", as_dict=False)
79 named_token_14 = self.ppa.newNamedAuthToken(u"name14", as_dict=False)
80 named_token_99 = self.ppa.newNamedAuthToken(u"name99", as_dict=False)
81 named_token_99.deactivate()
82
83+ expected_credentials = [
84+ ("buildd", "geheim", "bu"),
85+ ("+name14", named_token_14.token, "bm"),
86+ ("+name20", named_token_20.token, "bm"),
87+ ("a-b-c", third_created_token.token, "YS"),
88+ ("name12", second_created_token.token, "bm"),
89+ ("name16", first_created_token.token, "bm"),
90+ ]
91 credentials = list(htpasswd_credentials_for_archive(self.ppa))
92
93 # Use assertEqual instead of assertContentEqual to verify order.
94- self.assertEqual(
95- credentials, [
96- ("buildd", "geheim", "bu"),
97- ("+name14", named_token_14.token, "na"),
98- ("+name20", named_token_20.token, "na"),
99- ("name12", second_created_token.token, "na"),
100- ("name16", first_created_token.token, "na"),
101- ])
102+ self.assertEqual(expected_credentials, credentials)