# 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.assert_(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)
