Project import generated by Copybara.

GitOrigin-RevId: d9e9e3fb4e31372ec1fb43b178994ca78fa8fe70
diff --git a/framework/test/monorailrequest_test.py b/framework/test/monorailrequest_test.py
new file mode 100644
index 0000000..fcd30c3
--- /dev/null
+++ b/framework/test/monorailrequest_test.py
@@ -0,0 +1,613 @@
+# Copyright 2016 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://developers.google.com/open-source/licenses/bsd
+
+"""Unit tests for the monorailrequest module."""
+from __future__ import print_function
+from __future__ import division
+from __future__ import absolute_import
+
+import endpoints
+import mock
+import re
+import unittest
+
+import mox
+import six
+
+from google.appengine.api import oauth
+from google.appengine.api import users
+
+import webapp2
+
+from framework import exceptions
+from framework import monorailrequest
+from framework import permissions
+from proto import project_pb2
+from proto import tracker_pb2
+from services import service_manager
+from testing import fake
+from testing import testing_helpers
+from tracker import tracker_constants
+
+
+class HostportReTest(unittest.TestCase):
+
+  def testGood(self):
+    test_data = [
+      'localhost:8080',
+      'app.appspot.com',
+      'bugs-staging.chromium.org',
+      'vers10n-h3x-dot-app-id.appspot.com',
+      ]
+    for hostport in test_data:
+      self.assertTrue(monorailrequest._HOSTPORT_RE.match(hostport),
+                      msg='Incorrectly rejected %r' % hostport)
+
+  def testBad(self):
+    test_data = [
+      '',
+      ' ',
+      '\t',
+      '\n',
+      '\'',
+      '"',
+      'version"cruft-dot-app-id.appspot.com',
+      '\nother header',
+      'version&cruft-dot-app-id.appspot.com',
+      ]
+    for hostport in test_data:
+      self.assertFalse(monorailrequest._HOSTPORT_RE.match(hostport),
+                       msg='Incorrectly accepted %r' % hostport)
+
+
+class MonorailApiRequestUnitTest(unittest.TestCase):
+
+  def setUp(self):
+    self.cnxn = 'fake cnxn'
+    self.services = service_manager.Services(
+        config=fake.ConfigService(),
+        issue=fake.IssueService(),
+        project=fake.ProjectService(),
+        user=fake.UserService(),
+        usergroup=fake.UserGroupService())
+    self.project = self.services.project.TestAddProject(
+        'proj', project_id=789)
+    self.services.user.TestAddUser('requester@example.com', 111)
+    self.issue = fake.MakeTestIssue(
+        789, 1, 'sum', 'New', 111)
+    self.services.issue.TestAddIssue(self.issue)
+
+    self.patcher_1 = mock.patch('endpoints.get_current_user')
+    self.mock_endpoints_gcu = self.patcher_1.start()
+    self.mock_endpoints_gcu.return_value = None
+    self.patcher_2 = mock.patch('google.appengine.api.oauth.get_current_user')
+    self.mock_oauth_gcu = self.patcher_2.start()
+    self.mock_oauth_gcu.return_value = testing_helpers.Blank(
+        email=lambda: 'requester@example.com')
+
+  def tearDown(self):
+    mock.patch.stopall()
+
+  def testInit_NoProjectIssueOrViewedUser(self):
+    request = testing_helpers.Blank()
+    mar = monorailrequest.MonorailApiRequest(
+        request, self.services, cnxn=self.cnxn)
+    self.assertIsNone(mar.project)
+    self.assertIsNone(mar.issue)
+
+  def testInit_WithProject(self):
+    request = testing_helpers.Blank(projectId='proj')
+    mar = monorailrequest.MonorailApiRequest(
+        request, self.services, cnxn=self.cnxn)
+    self.assertEqual(self.project, mar.project)
+    self.assertIsNone(mar.issue)
+
+  def testInit_WithProjectAndIssue(self):
+    request = testing_helpers.Blank(
+        projectId='proj', issueId=1)
+    mar = monorailrequest.MonorailApiRequest(
+        request, self.services, cnxn=self.cnxn)
+    self.assertEqual(self.project, mar.project)
+    self.assertEqual(self.issue, mar.issue)
+
+  def testGetParam_Normal(self):
+    request = testing_helpers.Blank(q='owner:me')
+    mar = monorailrequest.MonorailApiRequest(
+        request, self.services, cnxn=self.cnxn)
+    self.assertEqual(None, mar.GetParam('unknown'))
+    self.assertEqual(100, mar.GetParam('num'))
+    self.assertEqual('owner:me', mar.GetParam('q'))
+
+    request = testing_helpers.Blank(q='owner:me', maxResults=200)
+    mar = monorailrequest.MonorailApiRequest(
+        request, self.services, cnxn=self.cnxn)
+    self.assertEqual(200, mar.GetParam('num'))
+
+
+class MonorailRequestUnitTest(unittest.TestCase):
+
+  def setUp(self):
+    self.services = service_manager.Services(
+        project=fake.ProjectService(),
+        user=fake.UserService(),
+        usergroup=fake.UserGroupService(),
+        features=fake.FeaturesService())
+    self.project = self.services.project.TestAddProject('proj')
+    self.hotlist = self.services.features.TestAddHotlist(
+        'TestHotlist', owner_ids=[111])
+    self.services.user.TestAddUser('jrobbins@example.com', 111)
+
+    self.mox = mox.Mox()
+    self.mox.StubOutWithMock(users, 'get_current_user')
+    users.get_current_user().AndReturn(None)
+    self.mox.ReplayAll()
+
+  def tearDown(self):
+    self.mox.UnsetStubs()
+
+  def testGetIntParam_ConvertsQueryParamToInt(self):
+    notice_id = 12345
+    mr = testing_helpers.MakeMonorailRequest(
+        path='/foo?notice=%s' % notice_id)
+
+    value = mr.GetIntParam('notice')
+    self.assertTrue(isinstance(value, int))
+    self.assertEqual(notice_id, value)
+
+  def testGetIntParam_ConvertsQueryParamToLong(self):
+    notice_id = 12345678901234567890
+    mr = testing_helpers.MakeMonorailRequest(
+        path='/foo?notice=%s' % notice_id)
+
+    value = mr.GetIntParam('notice')
+    self.assertTrue(isinstance(value, six.integer_types))
+    self.assertEqual(notice_id, value)
+
+  def testGetIntListParam_NoParam(self):
+    mr = monorailrequest.MonorailRequest(self.services)
+    mr.ParseRequest(webapp2.Request.blank('servlet'), self.services)
+    self.assertEqual(mr.GetIntListParam('ids'), None)
+    self.assertEqual(mr.GetIntListParam('ids', default_value=['test']),
+                      ['test'])
+
+  def testGetIntListParam_OneValue(self):
+    mr = monorailrequest.MonorailRequest(self.services)
+    mr.ParseRequest(webapp2.Request.blank('servlet?ids=11'), self.services)
+    self.assertEqual(mr.GetIntListParam('ids'), [11])
+    self.assertEqual(mr.GetIntListParam('ids', default_value=['test']),
+                      [11])
+
+  def testGetIntListParam_MultiValue(self):
+    mr = monorailrequest.MonorailRequest(self.services)
+    mr.ParseRequest(
+        webapp2.Request.blank('servlet?ids=21,22,23'), self.services)
+    self.assertEqual(mr.GetIntListParam('ids'), [21, 22, 23])
+    self.assertEqual(mr.GetIntListParam('ids', default_value=['test']),
+                      [21, 22, 23])
+
+  def testGetIntListParam_BogusValue(self):
+    mr = monorailrequest.MonorailRequest(self.services)
+    with self.assertRaises(exceptions.InputException):
+      mr.ParseRequest(
+          webapp2.Request.blank('servlet?ids=not_an_int'), self.services)
+
+  def testGetIntListParam_Malformed(self):
+    mr = monorailrequest.MonorailRequest(self.services)
+    with self.assertRaises(exceptions.InputException):
+      mr.ParseRequest(
+          webapp2.Request.blank('servlet?ids=31,32,,'), self.services)
+
+  def testDefaultValuesNoUrl(self):
+    """If request has no param, default param values should be used."""
+    mr = monorailrequest.MonorailRequest(self.services)
+    mr.ParseRequest(webapp2.Request.blank('servlet'), self.services)
+    self.assertEqual(mr.GetParam('r', 3), 3)
+    self.assertEqual(mr.GetIntParam('r', 3), 3)
+    self.assertEqual(mr.GetPositiveIntParam('r', 3), 3)
+    self.assertEqual(mr.GetIntListParam('r', [3, 4]), [3, 4])
+
+  def _MRWithMockRequest(
+      self, path, headers=None, *mr_args, **mr_kwargs):
+    request = webapp2.Request.blank(path, headers=headers)
+    mr = monorailrequest.MonorailRequest(self.services, *mr_args, **mr_kwargs)
+    mr.ParseRequest(request, self.services)
+    return mr
+
+  def testParseQueryParameters(self):
+    mr = self._MRWithMockRequest(
+        '/p/proj/issues/list?q=foo+OR+bar&num=50')
+    self.assertEqual('foo OR bar', mr.query)
+    self.assertEqual(50, mr.num)
+
+  def testParseQueryParameters_ModeMissing(self):
+    mr = self._MRWithMockRequest(
+        '/p/proj/issues/list?q=foo+OR+bar&num=50')
+    self.assertEqual('list', mr.mode)
+
+  def testParseQueryParameters_ModeList(self):
+    mr = self._MRWithMockRequest(
+        '/p/proj/issues/list?q=foo+OR+bar&num=50&mode=')
+    self.assertEqual('list', mr.mode)
+
+  def testParseQueryParameters_ModeGrid(self):
+    mr = self._MRWithMockRequest(
+        '/p/proj/issues/list?q=foo+OR+bar&num=50&mode=grid')
+    self.assertEqual('grid', mr.mode)
+
+  def testParseQueryParameters_ModeChart(self):
+    mr = self._MRWithMockRequest(
+        '/p/proj/issues/list?q=foo+OR+bar&num=50&mode=chart')
+    self.assertEqual('chart', mr.mode)
+
+  def testParseRequest_Scheme(self):
+    mr = self._MRWithMockRequest('/p/proj/')
+    self.assertEqual('http', mr.request.scheme)
+
+  def testParseRequest_HostportAndCurrentPageURL(self):
+    mr = self._MRWithMockRequest('/p/proj/', headers={
+        'Host': 'example.com',
+        'Cookie': 'asdf',
+        })
+    self.assertEqual('http', mr.request.scheme)
+    self.assertEqual('example.com', mr.request.host)
+    self.assertEqual('http://example.com/p/proj/', mr.current_page_url)
+
+  def testParseRequest_ProjectFound(self):
+    mr = self._MRWithMockRequest('/p/proj/')
+    self.assertEqual(mr.project, self.project)
+
+  def testParseRequest_ProjectNotFound(self):
+    with self.assertRaises(exceptions.NoSuchProjectException):
+      self._MRWithMockRequest('/p/no-such-proj/')
+
+  def testViewedUser_WithEmail(self):
+    mr = self._MRWithMockRequest('/u/jrobbins@example.com/')
+    self.assertEqual('jrobbins@example.com', mr.viewed_username)
+    self.assertEqual(111, mr.viewed_user_auth.user_id)
+    self.assertEqual(
+        self.services.user.GetUser('fake cnxn', 111),
+        mr.viewed_user_auth.user_pb)
+
+  def testViewedUser_WithUserID(self):
+    mr = self._MRWithMockRequest('/u/111/')
+    self.assertEqual('jrobbins@example.com', mr.viewed_username)
+    self.assertEqual(111, mr.viewed_user_auth.user_id)
+    self.assertEqual(
+        self.services.user.GetUser('fake cnxn', 111),
+        mr.viewed_user_auth.user_pb)
+
+  def testViewedUser_NoSuchEmail(self):
+    with self.assertRaises(webapp2.HTTPException) as cm:
+      self._MRWithMockRequest('/u/unknownuser@example.com/')
+    self.assertEqual(404, cm.exception.code)
+
+  def testViewedUser_NoSuchUserID(self):
+    with self.assertRaises(exceptions.NoSuchUserException):
+      self._MRWithMockRequest('/u/234521111/')
+
+  def testGetParam(self):
+    mr = testing_helpers.MakeMonorailRequest(
+        path='/foo?syn=error!&a=a&empty=',
+        params=dict(over1='over_value1', over2='over_value2'))
+
+    # test tampering
+    self.assertRaises(exceptions.InputException, mr.GetParam, 'a',
+                      antitamper_re=re.compile(r'^$'))
+    self.assertRaises(exceptions.InputException, mr.GetParam,
+                      'undefined', default_value='default',
+                      antitamper_re=re.compile(r'^$'))
+
+    # test empty value
+    self.assertEqual('', mr.GetParam(
+        'empty', default_value='default', antitamper_re=re.compile(r'^$')))
+
+    # test default
+    self.assertEqual('default', mr.GetParam(
+        'undefined', default_value='default'))
+
+  def testComputeColSpec(self):
+    # No config passed, and nothing in URL
+    mr = testing_helpers.MakeMonorailRequest(
+        path='/p/proj/issues/detail?id=123')
+    mr.ComputeColSpec(None)
+    self.assertEqual(tracker_constants.DEFAULT_COL_SPEC, mr.col_spec)
+
+    # No config passed, but set in URL
+    mr = testing_helpers.MakeMonorailRequest(
+        path='/p/proj/issues/detail?id=123&colspec=a b C')
+    mr.ComputeColSpec(None)
+    self.assertEqual('a b C', mr.col_spec)
+
+    config = tracker_pb2.ProjectIssueConfig()
+
+    # No default in the config, and nothing in URL
+    mr = testing_helpers.MakeMonorailRequest(
+        path='/p/proj/issues/detail?id=123')
+    mr.ComputeColSpec(config)
+    self.assertEqual(tracker_constants.DEFAULT_COL_SPEC, mr.col_spec)
+
+    # No default in the config, but set in URL
+    mr = testing_helpers.MakeMonorailRequest(
+        path='/p/proj/issues/detail?id=123&colspec=a b C')
+    mr.ComputeColSpec(config)
+    self.assertEqual('a b C', mr.col_spec)
+
+    config.default_col_spec = 'd e f'
+
+    # Default in the config, and nothing in URL
+    mr = testing_helpers.MakeMonorailRequest(
+        path='/p/proj/issues/detail?id=123')
+    mr.ComputeColSpec(config)
+    self.assertEqual('d e f', mr.col_spec)
+
+    # Default in the config, but overrided via URL
+    mr = testing_helpers.MakeMonorailRequest(
+        path='/p/proj/issues/detail?id=123&colspec=a b C')
+    mr.ComputeColSpec(config)
+    self.assertEqual('a b C', mr.col_spec)
+
+    # project colspec contains hotlist columns
+    mr = testing_helpers.MakeMonorailRequest(
+        path='p/proj/issues/detail?id=123&colspec=Rank Adder Adder Owner')
+    mr.ComputeColSpec(None)
+    self.assertEqual(tracker_constants.DEFAULT_COL_SPEC, mr.col_spec)
+
+    # hotlist columns are not deleted when page is a hotlist page
+    mr = testing_helpers.MakeMonorailRequest(
+        path='u/jrobbins@example.com/hotlists/TestHotlist?colspec=Rank Adder',
+        hotlist=self.hotlist)
+    mr.ComputeColSpec(None)
+    self.assertEqual('Rank Adder', mr.col_spec)
+
+  def testComputeColSpec_XSS(self):
+    config_1 = tracker_pb2.ProjectIssueConfig()
+    config_2 = tracker_pb2.ProjectIssueConfig()
+    config_2.default_col_spec = "id '+alert(1)+'"
+    mr_1 = testing_helpers.MakeMonorailRequest(
+        path='/p/proj/issues/detail?id=123')
+    mr_2 = testing_helpers.MakeMonorailRequest(
+        path="/p/proj/issues/detail?id=123&colspec=id '+alert(1)+'")
+
+    # Normal colspec in config but malicious request
+    self.assertRaises(
+        exceptions.InputException,
+        mr_2.ComputeColSpec, config_1)
+
+    # Malicious colspec in config but normal request
+    self.assertRaises(
+        exceptions.InputException,
+        mr_1.ComputeColSpec, config_2)
+
+    # Malicious colspec in config and malicious request
+    self.assertRaises(
+        exceptions.InputException,
+        mr_2.ComputeColSpec, config_2)
+
+
+class CalcDefaultQueryTest(unittest.TestCase):
+
+  def setUp(self):
+    self.project = project_pb2.Project()
+    self.project.project_name = 'proj'
+    self.project.owner_ids = [111]
+    self.config = tracker_pb2.ProjectIssueConfig()
+
+  def testIssueListURL_NotDefaultCan(self):
+    mr = monorailrequest.MonorailRequest(None)
+    mr.query = None
+    mr.can = 1
+    self.assertEqual('', mr._CalcDefaultQuery())
+
+  def testIssueListURL_NoProject(self):
+    mr = monorailrequest.MonorailRequest(None)
+    mr.query = None
+    mr.can = 2
+    self.assertEqual('', mr._CalcDefaultQuery())
+
+  def testIssueListURL_NoConfig(self):
+    mr = monorailrequest.MonorailRequest(None)
+    mr.query = None
+    mr.can = 2
+    mr.project = self.project
+    self.assertEqual('', mr._CalcDefaultQuery())
+
+  def testIssueListURL_NotCustomized(self):
+    mr = monorailrequest.MonorailRequest(None)
+    mr.query = None
+    mr.can = 2
+    mr.project = self.project
+    mr.config = self.config
+    self.assertEqual('', mr._CalcDefaultQuery())
+
+  def testIssueListURL_Customized_Nonmember(self):
+    mr = monorailrequest.MonorailRequest(None)
+    mr.query = None
+    mr.can = 2
+    mr.project = self.project
+    mr.config = self.config
+    mr.config.member_default_query = 'owner:me'
+    self.assertEqual('', mr._CalcDefaultQuery())
+
+    mr.auth = testing_helpers.Blank(effective_ids=set())
+    self.assertEqual('', mr._CalcDefaultQuery())
+
+    mr.auth = testing_helpers.Blank(effective_ids={999})
+    self.assertEqual('', mr._CalcDefaultQuery())
+
+  def testIssueListURL_Customized_Member(self):
+    mr = monorailrequest.MonorailRequest(None)
+    mr.query = None
+    mr.can = 2
+    mr.project = self.project
+    mr.config = self.config
+    mr.config.member_default_query = 'owner:me'
+    mr.auth = testing_helpers.Blank(effective_ids={111})
+    self.assertEqual('owner:me', mr._CalcDefaultQuery())
+
+
+class TestMonorailRequestFunctions(unittest.TestCase):
+
+  def testExtractPathIdentifiers_ProjectOnly(self):
+    (username, project_name, hotlist_id,
+     hotlist_name) = monorailrequest._ParsePathIdentifiers(
+         '/p/proj/issues/list?q=foo+OR+bar&ts=1234')
+    self.assertIsNone(username)
+    self.assertIsNone(hotlist_id)
+    self.assertIsNone(hotlist_name)
+    self.assertEqual('proj', project_name)
+
+  def testExtractPathIdentifiers_ViewedUserOnly(self):
+    (username, project_name, hotlist_id,
+     hotlist_name) = monorailrequest._ParsePathIdentifiers(
+         '/u/jrobbins@example.com/')
+    self.assertEqual('jrobbins@example.com', username)
+    self.assertIsNone(project_name)
+    self.assertIsNone(hotlist_id)
+    self.assertIsNone(hotlist_name)
+
+  def testExtractPathIdentifiers_ViewedUserURLSpace(self):
+    (username, project_name, hotlist_id,
+     hotlist_name) = monorailrequest._ParsePathIdentifiers(
+         '/u/jrobbins@example.com/updates')
+    self.assertEqual('jrobbins@example.com', username)
+    self.assertIsNone(project_name)
+    self.assertIsNone(hotlist_id)
+    self.assertIsNone(hotlist_name)
+
+  def testExtractPathIdentifiers_ViewedGroupURLSpace(self):
+    (username, project_name, hotlist_id,
+     hotlist_name) = monorailrequest._ParsePathIdentifiers(
+        '/g/user-group@example.com/updates')
+    self.assertEqual('user-group@example.com', username)
+    self.assertIsNone(project_name)
+    self.assertIsNone(hotlist_id)
+    self.assertIsNone(hotlist_name)
+
+  def testExtractPathIdentifiers_HotlistIssuesURLSpaceById(self):
+    (username, project_name, hotlist_id,
+     hotlist_name) = monorailrequest._ParsePathIdentifiers(
+         '/u/jrobbins@example.com/hotlists/13124?q=stuff&ts=more')
+    self.assertIsNone(hotlist_name)
+    self.assertIsNone(project_name)
+    self.assertEqual('jrobbins@example.com', username)
+    self.assertEqual(13124, hotlist_id)
+
+  def testExtractPathIdentifiers_HotlistIssuesURLSpaceByName(self):
+    (username, project_name, hotlist_id,
+     hotlist_name) = monorailrequest._ParsePathIdentifiers(
+         '/u/jrobbins@example.com/hotlists/testname?q=stuff&ts=more')
+    self.assertIsNone(project_name)
+    self.assertIsNone(hotlist_id)
+    self.assertEqual('jrobbins@example.com', username)
+    self.assertEqual('testname', hotlist_name)
+
+  def testParseColSpec(self):
+    parse = monorailrequest.ParseColSpec
+    self.assertEqual(['PageName', 'Summary', 'Changed', 'ChangedBy'],
+                     parse(u'PageName Summary Changed ChangedBy'))
+    self.assertEqual(['Foo-Bar', 'Foo-Bar-Baz', 'Release-1.2', 'Hey', 'There'],
+                     parse('Foo-Bar Foo-Bar-Baz Release-1.2 Hey!There'))
+    self.assertEqual(
+        ['\xe7\xaa\xbf\xe8\x8b\xa5\xe7\xb9\xb9'.decode('utf-8'),
+         '\xe5\x9f\xba\xe5\x9c\xb0\xe3\x81\xaf'.decode('utf-8')],
+        parse('\xe7\xaa\xbf\xe8\x8b\xa5\xe7\xb9\xb9 '
+              '\xe5\x9f\xba\xe5\x9c\xb0\xe3\x81\xaf'.decode('utf-8')))
+
+  def testParseColSpec_Dedup(self):
+    """An attacker cannot inflate response size by repeating a column."""
+    parse = monorailrequest.ParseColSpec
+    self.assertEqual([], parse(''))
+    self.assertEqual(
+      ['Aa', 'b', 'c/d'],
+      parse(u'Aa Aa AA AA AA b Aa aa c/d d c aA b aa B C/D D/aa/c'))
+    self.assertEqual(
+      ['A', 'b', 'c/d', 'e', 'f'],
+      parse(u'A b c/d e f g h i j a/k l m/c/a n/o'))
+
+  def testParseColSpec_Huge(self):
+    """An attacker cannot inflate response size with a huge column name."""
+    parse = monorailrequest.ParseColSpec
+    self.assertEqual(
+      ['Aa', 'b', 'c/d'],
+      parse(u'Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa b c/d'))
+
+  def testParseColSpec_Ignore(self):
+    """We ignore groupby and grid axes that would be useless."""
+    parse = monorailrequest.ParseColSpec
+    self.assertEqual(
+      ['Aa', 'b', 'c/d'],
+      parse(u'Aa AllLabels alllabels Id b opened/summary c/d',
+            ignore=tracker_constants.NOT_USED_IN_GRID_AXES))
+
+
+class TestPermissionLookup(unittest.TestCase):
+  OWNER_ID = 1
+  OTHER_USER_ID = 2
+
+  def setUp(self):
+    self.services = service_manager.Services(
+        project=fake.ProjectService(),
+        user=fake.UserService(),
+        usergroup=fake.UserGroupService())
+    self.services.user.TestAddUser('owner@gmail.com', self.OWNER_ID)
+    self.services.user.TestAddUser('user@gmail.com', self.OTHER_USER_ID)
+    self.live_project = self.services.project.TestAddProject(
+        'live', owner_ids=[self.OWNER_ID])
+    self.archived_project = self.services.project.TestAddProject(
+        'archived', owner_ids=[self.OWNER_ID],
+        state=project_pb2.ProjectState.ARCHIVED)
+    self.members_only_project = self.services.project.TestAddProject(
+        'members-only', owner_ids=[self.OWNER_ID],
+        access=project_pb2.ProjectAccess.MEMBERS_ONLY)
+
+    self.mox = mox.Mox()
+
+  def tearDown(self):
+    self.mox.UnsetStubs()
+
+  def CheckPermissions(self, perms, expect_view, expect_commit, expect_edit):
+    may_view = perms.HasPerm(permissions.VIEW, None, None)
+    self.assertEqual(expect_view, may_view)
+    may_commit = perms.HasPerm(permissions.COMMIT, None, None)
+    self.assertEqual(expect_commit, may_commit)
+    may_edit = perms.HasPerm(permissions.EDIT_PROJECT, None, None)
+    self.assertEqual(expect_edit, may_edit)
+
+  def MakeRequestAsUser(self, project_name, email):
+    self.mox.StubOutWithMock(users, 'get_current_user')
+    users.get_current_user().AndReturn(testing_helpers.Blank(
+        email=lambda: email))
+    self.mox.ReplayAll()
+
+    request = webapp2.Request.blank('/p/' + project_name)
+    mr = monorailrequest.MonorailRequest(self.services)
+    with mr.profiler.Phase('parse user info'):
+      mr.ParseRequest(request, self.services)
+      print('mr.auth is %r' % mr.auth)
+    return mr
+
+  def testOwnerPermissions_Live(self):
+    mr = self.MakeRequestAsUser('live', 'owner@gmail.com')
+    self.CheckPermissions(mr.perms, True, True, True)
+
+  def testOwnerPermissions_Archived(self):
+    mr = self.MakeRequestAsUser('archived', 'owner@gmail.com')
+    self.CheckPermissions(mr.perms, True, False, True)
+
+  def testOwnerPermissions_MembersOnly(self):
+    mr = self.MakeRequestAsUser('members-only', 'owner@gmail.com')
+    self.CheckPermissions(mr.perms, True, True, True)
+
+  def testExternalUserPermissions_Live(self):
+    mr = self.MakeRequestAsUser('live', 'user@gmail.com')
+    self.CheckPermissions(mr.perms, True, False, False)
+
+  def testExternalUserPermissions_Archived(self):
+    mr = self.MakeRequestAsUser('archived', 'user@gmail.com')
+    self.CheckPermissions(mr.perms, False, False, False)
+
+  def testExternalUserPermissions_MembersOnly(self):
+    mr = self.MakeRequestAsUser('members-only', 'user@gmail.com')
+    self.CheckPermissions(mr.perms, False, False, False)