Allow committers to use their Trac credentials to force builds on the buildbots
authoraroben@apple.com <aroben@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Dec 2011 19:58:49 +0000 (19:58 +0000)
committeraroben@apple.com <aroben@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 1 Dec 2011 19:58:49 +0000 (19:58 +0000)
Fixes <http://webkit.org/b/73353>

A new class, CommitterAuth, handles authentication of WebKit committers. CommitterAuth uses
three files to do its job: a config file that contains a list of WebKit committer usernames,
an htdigest file that contains Trac credentials, and JSON file that gives the paths for
those two files.

Reviewed by Darin Adler.

* BuildSlaveSupport/build.webkit.org-config/committer_auth.py: Added.
(Error): Basic wrapper around Exception that we use for cases where we couldn't even check
whether credentials were valid or not.
(CommitterAuth.__init__): Just store the path to auth.json.
(CommitterAuth.auth_json): Load, parse, and return auth.json.
(CommitterAuth.auth_json_filename): Return the path to auth.json.
(CommitterAuth.authenticate): Return true if the user is a WebKit committer and their
credentials are valid Trac credentials. Return false otherwise or if an error occurred while
checking those conditions.
(CommitterAuth.is_webkit_committer): Return true if the user is a WebKit committer. Return
false otherwise or if an exception was thrown.
(CommitterAuth.is_webkit_trac_user): Return true if the username/password are present in the
Trac credentials htdigest file. Return false otherwise or if an exception was thrown.

(CommitterAuth.open_auth_json_file):
(CommitterAuth.open_trac_credentials_file):
(CommitterAuth.open_webkit_committers_file):
Open the specified file. These are mostly useful for testing purposes.

(CommitterAuth.trac_credentials_filename):
(CommitterAuth.webkit_committers_filename):
Return the path to the specified file by retrieving it from auth.json.

(CommitterAuth.webkit_committers): Load and parse the committers file and extract the list
of WebKit committers from it.

* BuildSlaveSupport/build.webkit.org-config/committer_auth_unittest.py: Added.
(CMStringIO.__enter__):
(CMStringIO.__exit__):
Helper class that makes it possible to use StringIO with the "with" statement.

(open_override): Helper context manager for overriding the global "open" function
temporarily.

(CommitterAuthTest.setUp): Set up a somewhat-mocked CommitterAuth that is used by most
tests.
(CommitterAuthTest.fake_open_function): Returns a function that can be used in place of
"open" to test that the expected path was opened.
(CommitterAuthTest.test_authentication_success): Test that committers can authenticate
successfully.
(CommitterAuthTest.test_committer_without_trac_credentials_fails): Test that committers who
somehow have no Trac account can't authenticate.

(CommitterAuthTest.test_fail_to_open_auth_json_file):
(CommitterAuthTest.test_fail_to_open_trac_credentials_file):
(CommitterAuthTest.test_fail_to_open_webkit_committers_file):
Test what happens when we can't open the three files we depend upon.

(CommitterAuthTest.test_implements_IAuth): Test that we fulfill buildbot's expectations for
an authentication class.

(CommitterAuthTest.test_invalid_auth_json_file):
(CommitterAuthTest.test_invalid_committers_file):
(CommitterAuthTest.test_invalid_trac_credentials_file):
(CommitterAuthTest.test_missing_auth_json_keys):
Test what happens when the three files we depend upon are invalid in some way.

(CommitterAuthTest.test_open_auth_json_file):
(CommitterAuthTest.test_open_trac_credentials_file):
(CommitterAuthTest.test_open_webkit_committers_file):
Test that we open the expected paths.

(CommitterAuthTest.test_trac_credentials_filename):
(CommitterAuthTest.test_webkit_committers_filename):
Test that we extract filenames out of auth.json correctly.

(CommitterAuthTest.test_non_committer_fails):
(CommitterAuthTest.test_unknown_user_fails):
(CommitterAuthTest.test_username_is_prefix_of_valid_user):
(CommitterAuthTest.test_wrong_password_fails):
Test various failed authentication attempts.

(CommitterAuthTest.test_webkit_committers): Test that we can parse the list of WebKit
committers out of the committers file correctly.

(CommitterAuthTest.fake_auth_json_file):
(CommitterAuthTest.invalid_auth_json_file):
(CommitterAuthTest.fake_committers_file):
(CommitterAuthTest.invalid_committers_file):
(CommitterAuthTest.fake_htdigest_file):
(CommitterAuthTest.invalid_htdigest_file):
Return various fake files for testing.

* BuildSlaveSupport/build.webkit.org-config/master.cfg: Specify an instance of CommitterAuth
to be used for authentication in the web interface, and specify that only authenticated
users may force builds.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@101695 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Tools/BuildSlaveSupport/build.webkit.org-config/committer_auth.py [new file with mode: 0644]
Tools/BuildSlaveSupport/build.webkit.org-config/committer_auth_unittest.py [new file with mode: 0755]
Tools/BuildSlaveSupport/build.webkit.org-config/master.cfg
Tools/ChangeLog

diff --git a/Tools/BuildSlaveSupport/build.webkit.org-config/committer_auth.py b/Tools/BuildSlaveSupport/build.webkit.org-config/committer_auth.py
new file mode 100644 (file)
index 0000000..6f4d360
--- /dev/null
@@ -0,0 +1,115 @@
+# Copyright (C) 2011 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""An implementation of buildbot.status.web.auth.IAuth for authenticating WebKit committers"""
+
+import ConfigParser
+import buildbot.status.web.auth
+import json
+import zope.interface
+
+from webkitpy.common.net.htdigestparser import HTDigestParser
+
+
+class Error(Exception):
+    pass
+
+
+class CommitterAuth(buildbot.status.web.auth.AuthBase):
+    zope.interface.implements(buildbot.status.web.auth.IAuth)
+
+    def __init__(self, auth_json_filename):
+        self._auth_json_filename = auth_json_filename
+
+    def auth_json(self):
+        try:
+            with self.open_auth_json_file() as f:
+                return json.load(f)
+        except IOError, e:
+            raise Error('Error opening auth.json file: {0}'.format(e.strerror))
+        except ValueError, e:
+            raise Error('Error parsing auth.json file: {0}'.format(e.message))
+
+    def auth_json_filename(self):
+        return self._auth_json_filename
+
+    def authenticate(self, username, password):
+        try:
+            return self.is_webkit_committer(username) and self.is_webkit_trac_user(username, password)
+        except Error, e:
+            self.err = e.message
+            return False
+
+    def is_webkit_committer(self, username):
+        try:
+            if username not in self.webkit_committers():
+                self.err = 'Invalid username/password'
+                return False
+            return True
+        except ConfigParser.Error:
+            raise Error('Error parsing WebKit committers file')
+        except IOError, e:
+            raise Error('Error opening WebKit committers file: {0}'.format(e.strerror))
+
+    def is_webkit_trac_user(self, username, password):
+        try:
+            with self.open_trac_credentials_file() as f:
+                htdigest = HTDigestParser(f)
+        except IOError, e:
+            raise Error('Error opening Trac credentials file: {0}'.format(e.strerror))
+
+        if not htdigest.entries():
+            raise Error('Error parsing Trac credentials file')
+
+        if not htdigest.authenticate(username, 'Mac OS Forge', password):
+            self.err = 'Invalid username/password'
+            return False
+
+        return True
+
+    # These three methods exist for ease of testing.
+    def open_auth_json_file(self):
+        return open(self.auth_json_filename())
+
+    def open_trac_credentials_file(self):
+        return open(self.trac_credentials_filename())
+
+    def open_webkit_committers_file(self):
+        return open(self.webkit_committers_filename())
+
+    def trac_credentials_filename(self):
+        try:
+            return self.auth_json()['trac_credentials']
+        except KeyError:
+            raise Error('auth.json file is missing "trac_credentials" key')
+
+    def webkit_committers(self):
+        config = ConfigParser.RawConfigParser()
+        with self.open_webkit_committers_file() as f:
+            config.readfp(f)
+        return config.get('groups', 'webkit').split(',')
+
+    def webkit_committers_filename(self):
+        try:
+            return self.auth_json()['webkit_committers']
+        except KeyError:
+            raise Error('auth.json file is missing "webkit_committers" key')
diff --git a/Tools/BuildSlaveSupport/build.webkit.org-config/committer_auth_unittest.py b/Tools/BuildSlaveSupport/build.webkit.org-config/committer_auth_unittest.py
new file mode 100755 (executable)
index 0000000..f9e169a
--- /dev/null
@@ -0,0 +1,220 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2011 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer.
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import StringIO
+import __builtin__
+import buildbot.status.web.auth
+import contextlib
+import os
+import unittest
+
+from committer_auth import CommitterAuth
+
+
+# This subclass of StringIO supports the context manager protocol so it works
+# with "with" statements, just like real files.
+class CMStringIO(StringIO.StringIO):
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exception, value, traceback):
+        self.close()
+
+
+@contextlib.contextmanager
+def open_override(func):
+    original_open = __builtin__.open
+    __builtin__.open = func
+    yield
+    __builtin__.open = original_open
+
+
+class CommitterAuthTest(unittest.TestCase):
+    def setUp(self):
+        self.auth = CommitterAuth('path/to/auth.json')
+        self.auth.open_auth_json_file = self.fake_auth_json_file
+        self.auth.open_webkit_committers_file = self.fake_committers_file
+        self.auth.open_trac_credentials_file = self.fake_htdigest_file
+
+    def fake_open_function(self, expected_filename):
+        def fake_open(name, mode='r'):
+            self.fake_open_was_called = True
+            self.assertEqual(expected_filename, name)
+        return fake_open
+
+    def test_authentication_success(self):
+        self.assertTrue(self.auth.authenticate('committer@webkit.org', 'committerpassword'))
+        self.assertEqual('', self.auth.errmsg())
+        self.assertTrue(self.auth.authenticate('committer2@example.com', 'committer2password'))
+        self.assertEqual('', self.auth.errmsg())
+
+    def test_committer_without_trac_credentials_fails(self):
+        self.assertFalse(self.auth.authenticate('committer3@webkit.org', 'committer3password'))
+        self.assertEqual('Invalid username/password', self.auth.errmsg())
+
+    def test_fail_to_open_auth_json_file(self):
+        def raise_IOError():
+            raise IOError(2, 'No such file or directory', 'path/to/auth.json')
+        auth = CommitterAuth('path/to/auth.json')
+        auth.open_auth_json_file = raise_IOError
+        self.assertFalse(auth.authenticate('committer@webkit.org', 'committerpassword'))
+        self.assertEqual('Error opening auth.json file: No such file or directory', auth.errmsg())
+
+    def test_fail_to_open_trac_credentials_file(self):
+        def raise_IOError():
+            raise IOError(2, 'No such file or directory', 'path/to/trac/credentials')
+        self.auth.open_trac_credentials_file = raise_IOError
+        self.assertFalse(self.auth.authenticate('committer@webkit.org', 'committerpassword'))
+        self.assertEqual('Error opening Trac credentials file: No such file or directory', self.auth.errmsg())
+
+    def test_fail_to_open_webkit_committers_file(self):
+        def raise_IOError():
+            raise IOError(2, 'No such file or directory', 'path/to/webkit/committers')
+        self.auth.open_webkit_committers_file = raise_IOError
+        self.assertFalse(self.auth.authenticate('committer@webkit.org', 'committerpassword'))
+        self.assertEqual('Error opening WebKit committers file: No such file or directory', self.auth.errmsg())
+
+    def test_implements_IAuth(self):
+        self.assertTrue(buildbot.status.web.auth.IAuth.implementedBy(CommitterAuth))
+
+    def test_invalid_auth_json_file(self):
+        auth = CommitterAuth('path/to/auth.json')
+        auth.open_auth_json_file = self.invalid_auth_json_file
+        self.assertFalse(auth.authenticate('committer@webkit.org', 'committerpassword'))
+        self.assertEqual('Error parsing auth.json file: No JSON object could be decoded', auth.errmsg())
+
+    def test_invalid_committers_file(self):
+        self.auth.open_webkit_committers_file = self.invalid_committers_file
+        self.assertFalse(self.auth.authenticate('committer@webkit.org', 'committerpassword'))
+        self.assertEqual('Error parsing WebKit committers file', self.auth.errmsg())
+
+    def test_invalid_trac_credentials_file(self):
+        self.auth.open_trac_credentials_file = self.invalid_htdigest_file
+        self.assertFalse(self.auth.authenticate('committer@webkit.org', 'committerpassword'))
+        self.assertEqual('Error parsing Trac credentials file', self.auth.errmsg())
+
+    def test_missing_auth_json_keys(self):
+        auth = CommitterAuth('path/to/auth.json')
+        auth.open_auth_json_file = lambda: CMStringIO('{ "trac_credentials": "path/to/trac/credentials" }')
+        self.assertFalse(auth.authenticate('committer@webkit.org', 'committerpassword'))
+        self.assertEqual('auth.json file is missing "webkit_committers" key', auth.errmsg())
+
+        auth.open_auth_json_file = lambda: CMStringIO('{ "webkit_committers": "path/to/webkit/committers" }')
+        auth.open_webkit_committers_file = self.fake_committers_file
+        self.assertFalse(auth.authenticate('committer@webkit.org', 'committerpassword'))
+        self.assertEqual('auth.json file is missing "trac_credentials" key', auth.errmsg())
+
+    def test_open_auth_json_file(self):
+        auth = CommitterAuth('path/to/auth.json')
+        self.fake_open_was_called = False
+        with open_override(self.fake_open_function(auth.auth_json_filename())):
+            auth.open_auth_json_file()
+        self.assertTrue(self.fake_open_was_called)
+
+    def test_open_trac_credentials_file(self):
+        auth = CommitterAuth('path/to/auth.json')
+        auth.trac_credentials_filename = lambda: 'trac credentials filename'
+        self.fake_open_was_called = False
+        with open_override(self.fake_open_function(auth.trac_credentials_filename())):
+            auth.open_trac_credentials_file()
+        self.assertTrue(self.fake_open_was_called)
+
+    def test_open_webkit_committers_file(self):
+        auth = CommitterAuth('path/to/auth.json')
+        auth.webkit_committers_filename = lambda: 'webkit committers filename'
+        self.fake_open_was_called = False
+        with open_override(self.fake_open_function(auth.webkit_committers_filename())):
+            auth.open_webkit_committers_file()
+        self.assertTrue(self.fake_open_was_called)
+
+    def test_non_committer_fails(self):
+        self.assertFalse(self.auth.authenticate('noncommitter@example.com', 'noncommitterpassword'))
+        self.assertEqual('Invalid username/password', self.auth.errmsg())
+
+    def test_trac_credentials_filename(self):
+        self.assertEqual('path/to/trac/credentials', self.auth.trac_credentials_filename())
+
+    def test_unknown_user_fails(self):
+        self.assertFalse(self.auth.authenticate('nobody@example.com', 'nobodypassword'))
+        self.assertEqual('Invalid username/password', self.auth.errmsg())
+
+    def test_username_is_prefix_of_valid_user(self):
+        self.assertFalse(self.auth.authenticate('committer@webkit.orgg', 'committerpassword'))
+        self.assertEqual('Invalid username/password', self.auth.errmsg())
+
+    def test_webkit_committers(self):
+        self.assertEqual(['committer@webkit.org', 'committer2@example.com', 'committer3@webkit.org'], self.auth.webkit_committers())
+
+    def test_webkit_committers_filename(self):
+        self.assertEqual('path/to/webkit/committers', self.auth.webkit_committers_filename())
+
+    def test_wrong_password_fails(self):
+        self.assertFalse(self.auth.authenticate('committer@webkit.org', 'wrongpassword'))
+        self.assertEqual('Invalid username/password', self.auth.errmsg())
+
+    def fake_auth_json_file(self):
+        return CMStringIO("""{
+    "trac_credentials": "path/to/trac/credentials",
+    "webkit_committers": "path/to/webkit/committers"
+}""")
+
+    def invalid_auth_json_file(self):
+        return CMStringIO('~!@#$%^&*()_+')
+
+    def fake_committers_file(self):
+        return CMStringIO("""[groups]
+group1 = user@example.com,user2@example.com
+group2 = user3@example.com
+
+group3 =
+group4 =
+
+webkit = committer@webkit.org,committer2@example.com,committer3@webkit.org
+
+[service:/]
+*    = r
+""")
+
+    def invalid_committers_file(self):
+        return CMStringIO("""[groups]
+
+[[groups2]
+""")
+
+    def fake_htdigest_file(self):
+        return CMStringIO("""committer@webkit.org:Mac OS Forge:761c8dcb7d9b5908007ed142f62fe73a
+committer2@example.com:Mac OS Forge:faeee69acc2e49af3a0dbb15bd593ef4
+noncommitter@example.com:Mac OS Forge:b99aa7ad32306a654ca4d57839fde9c1
+""")
+
+    def invalid_htdigest_file(self):
+        return CMStringIO("""committer@webkit.org:Mac OS Forge:761c8dcb7d9b5908007ed142f62fe73a
+committer2@example.com:Mac OS Forge:faeee69acc2e49af3a0dbb15bd593ef4
+noncommitter@example.com:Mac OS Forge:b99aa7ad32306a654ca4d57839fde9c1
+committer4@example.com:Mac OS Forge:::
+""")
+
+
+if __name__ == '__main__':
+    unittest.main()
index 96e471b..7096afd 100644 (file)
@@ -18,6 +18,7 @@ import re
 import simplejson
 import urllib
 
+from committer_auth import CommitterAuth
 from webkitpy.common.config import build as wkbuild
 from webkitpy.common.net.buildbot import BuildBot as wkbuildbot
 
@@ -28,8 +29,9 @@ c['change_source'] = PBChangeSource()
 
 # permissions for WebStatus
 authz = Authz(
-    forceBuild=False,
-    forceAllBuilds=False,
+    auth=CommitterAuth('auth.json'),
+    forceBuild='auth',
+    forceAllBuilds='auth',
     pingBuilder=True,
     gracefulShutdown=False,
     stopBuild=True,
index 10682c3..1da59a3 100644 (file)
@@ -1,5 +1,105 @@
 2011-12-01  Adam Roben  <aroben@apple.com>
 
+        Allow committers to use their Trac credentials to force builds on the buildbots
+
+        Fixes <http://webkit.org/b/73353>
+
+        A new class, CommitterAuth, handles authentication of WebKit committers. CommitterAuth uses
+        three files to do its job: a config file that contains a list of WebKit committer usernames,
+        an htdigest file that contains Trac credentials, and JSON file that gives the paths for
+        those two files.
+
+        Reviewed by Darin Adler.
+
+        * BuildSlaveSupport/build.webkit.org-config/committer_auth.py: Added.
+        (Error): Basic wrapper around Exception that we use for cases where we couldn't even check
+        whether credentials were valid or not.
+        (CommitterAuth.__init__): Just store the path to auth.json.
+        (CommitterAuth.auth_json): Load, parse, and return auth.json.
+        (CommitterAuth.auth_json_filename): Return the path to auth.json.
+        (CommitterAuth.authenticate): Return true if the user is a WebKit committer and their
+        credentials are valid Trac credentials. Return false otherwise or if an error occurred while
+        checking those conditions.
+        (CommitterAuth.is_webkit_committer): Return true if the user is a WebKit committer. Return
+        false otherwise or if an exception was thrown.
+        (CommitterAuth.is_webkit_trac_user): Return true if the username/password are present in the
+        Trac credentials htdigest file. Return false otherwise or if an exception was thrown.
+
+        (CommitterAuth.open_auth_json_file):
+        (CommitterAuth.open_trac_credentials_file):
+        (CommitterAuth.open_webkit_committers_file):
+        Open the specified file. These are mostly useful for testing purposes.
+
+        (CommitterAuth.trac_credentials_filename):
+        (CommitterAuth.webkit_committers_filename):
+        Return the path to the specified file by retrieving it from auth.json.
+
+        (CommitterAuth.webkit_committers): Load and parse the committers file and extract the list
+        of WebKit committers from it.
+
+        * BuildSlaveSupport/build.webkit.org-config/committer_auth_unittest.py: Added.
+        (CMStringIO.__enter__):
+        (CMStringIO.__exit__):
+        Helper class that makes it possible to use StringIO with the "with" statement.
+
+        (open_override): Helper context manager for overriding the global "open" function
+        temporarily.
+
+        (CommitterAuthTest.setUp): Set up a somewhat-mocked CommitterAuth that is used by most
+        tests.
+        (CommitterAuthTest.fake_open_function): Returns a function that can be used in place of
+        "open" to test that the expected path was opened.
+        (CommitterAuthTest.test_authentication_success): Test that committers can authenticate
+        successfully.
+        (CommitterAuthTest.test_committer_without_trac_credentials_fails): Test that committers who
+        somehow have no Trac account can't authenticate.
+
+        (CommitterAuthTest.test_fail_to_open_auth_json_file):
+        (CommitterAuthTest.test_fail_to_open_trac_credentials_file):
+        (CommitterAuthTest.test_fail_to_open_webkit_committers_file):
+        Test what happens when we can't open the three files we depend upon.
+
+        (CommitterAuthTest.test_implements_IAuth): Test that we fulfill buildbot's expectations for
+        an authentication class.
+
+        (CommitterAuthTest.test_invalid_auth_json_file):
+        (CommitterAuthTest.test_invalid_committers_file):
+        (CommitterAuthTest.test_invalid_trac_credentials_file):
+        (CommitterAuthTest.test_missing_auth_json_keys):
+        Test what happens when the three files we depend upon are invalid in some way.
+
+        (CommitterAuthTest.test_open_auth_json_file):
+        (CommitterAuthTest.test_open_trac_credentials_file):
+        (CommitterAuthTest.test_open_webkit_committers_file):
+        Test that we open the expected paths.
+
+        (CommitterAuthTest.test_trac_credentials_filename):
+        (CommitterAuthTest.test_webkit_committers_filename):
+        Test that we extract filenames out of auth.json correctly.
+
+        (CommitterAuthTest.test_non_committer_fails):
+        (CommitterAuthTest.test_unknown_user_fails):
+        (CommitterAuthTest.test_username_is_prefix_of_valid_user):
+        (CommitterAuthTest.test_wrong_password_fails):
+        Test various failed authentication attempts.
+
+        (CommitterAuthTest.test_webkit_committers): Test that we can parse the list of WebKit
+        committers out of the committers file correctly.
+
+        (CommitterAuthTest.fake_auth_json_file):
+        (CommitterAuthTest.invalid_auth_json_file):
+        (CommitterAuthTest.fake_committers_file):
+        (CommitterAuthTest.invalid_committers_file):
+        (CommitterAuthTest.fake_htdigest_file):
+        (CommitterAuthTest.invalid_htdigest_file):
+        Return various fake files for testing.
+
+        * BuildSlaveSupport/build.webkit.org-config/master.cfg: Specify an instance of CommitterAuth
+        to be used for authentication in the web interface, and specify that only authenticated
+        users may force builds.
+
+2011-12-01  Adam Roben  <aroben@apple.com>
+
         Add an HTDigestParser class to webkitpy
 
         Fixes <http://webkit.org/b/73575> webkitpy doesn't provide a way to parse htdigest files