Skip to content
Permalink
Browse files
[webkitbugspy] Limit number of failed bugzilla login attempts
https://bugs.webkit.org/show_bug.cgi?id=239850
<rdar://problem/92462214>

Reviewed by Michael Catanzaro.

* Tools/Scripts/libraries/webkitbugspy/setup.py: Bump version.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py: Ditto.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py:
(Tracker.__init__): Caller can define the number of failed login attempts.
(Tracker.user): Detect a failed login.
(Tracker._login_arguments): Only provide login arguments if login attempts
have not been exceeded.
(Tracker.populate): Detect a failed login.
(Tracker.set): Detect a failed login, handle case where login arguments are
not provided due to exceeded login attempts.
(Tracker.add_comment): Ditto.
(Tracker.projects): Detect a failed login.
* Tools/Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py:

Canonical link: https://commits.webkit.org/250089@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@293575 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
JonWBedard committed Apr 28, 2022
1 parent 4635a1b commit e93b0a71b6ab9e4988455dfd448c9641ff2fa9cd
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 19 deletions.
@@ -1,3 +1,25 @@
2022-04-28 Jonathan Bedard <jbedard@apple.com>

[webkitbugspy] Limit number of failed bugzilla login attempts
https://bugs.webkit.org/show_bug.cgi?id=239850
<rdar://problem/92462214>

Reviewed by Michael Catanzaro.

* Scripts/libraries/webkitbugspy/setup.py: Bump version.
* Scripts/libraries/webkitbugspy/webkitbugspy/__init__.py: Ditto.
* Scripts/libraries/webkitbugspy/webkitbugspy/bugzilla.py:
(Tracker.__init__): Caller can define the number of failed login attempts.
(Tracker.user): Detect a failed login.
(Tracker._login_arguments): Only provide login arguments if login attempts
have not been exceeded.
(Tracker.populate): Detect a failed login.
(Tracker.set): Detect a failed login, handle case where login arguments are
not provided due to exceeded login attempts.
(Tracker.add_comment): Ditto.
(Tracker.projects): Detect a failed login.
* Scripts/libraries/webkitbugspy/webkitbugspy/tests/bugzilla_unittest.py:

2022-04-28 Kimmo Kinnunen <kkinnunen@apple.com>

test-webkitperl outputs errors about uninitialized $platform variable
@@ -30,7 +30,7 @@ def readme():

setup(
name='webkitbugspy',
version='0.5.3',
version='0.5.4',
description='Library containing a shared API for various bug trackers.',
long_description=readme(),
classifiers=[
@@ -46,7 +46,7 @@ def _maybe_add_library_path(path):
"Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
)

version = Version(0, 5, 3)
version = Version(0, 5, 4)

from .user import User
from .issue import Issue
@@ -56,10 +56,10 @@ def default(context, obj):
raise TypeError('Cannot invoke parent class when classmethod')
return super(Tracker.Encoder, context).default(obj)


def __init__(self, url, users=None, res=None):
def __init__(self, url, users=None, res=None, login_attempts=3):
super(Tracker, self).__init__(users=users)

self._logins_left = login_attempts + 1 if login_attempts else 1
match = self.ROOT_RE.match(url)
if not match:
raise TypeError("'{}' is not a valid bugzilla url".format(url))
@@ -89,6 +89,8 @@ def user(self, name=None, username=None, email=None):
query='names={name}'.format(name=username or email or name),
),
))
if response.status_code // 100 == 4 and self._logins_left:
self._logins_left -= 1
response = response.json().get('users') if response.status_code // 100 == 2 else None
if not response:
return self.users.create(
@@ -127,6 +129,11 @@ def validater(username, password):
)

def _login_arguments(self, required=False, query=None):
if not self._logins_left:
if required:
raise RuntimeError('Exhausted login attempts')
return '?{}'.format(query) if query else ''

username, password = self.credentials(required=required)
if not username or not password:
return '?{}'.format(query) if query else ''
@@ -150,6 +157,8 @@ def populate(self, issue, member=None):

if member in ('title', 'timestamp', 'creator', 'opened', 'assignee', 'watchers', 'project', 'component', 'version'):
response = requests.get('{}/rest/bug/{}{}'.format(self.url, issue.id, self._login_arguments(required=False)))
if response.status_code // 100 == 4 and self._logins_left:
self._logins_left -= 1
response = response.json().get('bugs', []) if response.status_code == 200 else None
if response:
response = response[0]
@@ -185,6 +194,8 @@ def populate(self, issue, member=None):

if member in ['description', 'comments']:
response = requests.get('{}/rest/bug/{}/comment{}'.format(self.url, issue.id, self._login_arguments(required=False)))
if response.status_code // 100 == 4 and self._logins_left:
self._logins_left -= 1
if response.status_code == 200:
response = response.json().get('bugs', {}).get(str(issue.id), {}).get('comments', None)
else:
@@ -218,6 +229,8 @@ def populate(self, issue, member=None):
url=self.url, id=issue.id,
query=self._login_arguments(required=False, query='include_fields=see_also'),
))
if response.status_code // 100 == 4 and self._logins_left:
self._logins_left -= 1
response = response.json().get('bugs', []) if response.status_code == 200 else None
if response:
for link in response[0].get('see_also', []):
@@ -287,11 +300,17 @@ def set(self, issue, assignee=None, opened=None, why=None, project=None, compone

if update_dict:
update_dict['ids'] = [issue.id]
response = requests.put(
'{}/rest/bug/{}{}'.format(self.url, issue.id, self._login_arguments(required=True)),
json=update_dict,
)
if response.status_code // 100 != 2:
response = None
try:
response = requests.put(
'{}/rest/bug/{}{}'.format(self.url, issue.id, self._login_arguments(required=True)),
json=update_dict,
)
except RuntimeError as e:
sys.stderr.write('{}\n'.format(e))
if response and response.status_code // 100 == 4 and self._logins_left:
self._logins_left -= 1
if not response or response.status_code // 100 != 2:
if assignee:
issue._assignee = None
if opened is not None:
@@ -306,11 +325,18 @@ def set(self, issue, assignee=None, opened=None, why=None, project=None, compone
return issue

def add_comment(self, issue, text):
response = requests.post(
'{}/rest/bug/{}/comment{}'.format(self.url, issue.id, self._login_arguments(required=True)),
json=dict(comment=text),
)
if response.status_code // 100 != 2:
response = None
try:
response = requests.post(
'{}/rest/bug/{}/comment{}'.format(self.url, issue.id, self._login_arguments(required=True)),
json=dict(comment=text),
)
except RuntimeError as e:
sys.stderr.write('{}\n'.format(e))

if response and response.status_code // 100 == 4 and self._logins_left:
self._logins_left -= 1
if not response or response.status_code // 100 != 2:
sys.stderr.write("Failed to add comment to '{}'\n".format(issue))
return None

@@ -329,13 +355,17 @@ def add_comment(self, issue, text):
@webkitcorepy.decorators.Memoize()
def projects(self):
response = requests.get('{}/rest/product_enterable{}'.format(self.url, self._login_arguments(required=False)))
if response.status_code // 100 == 4 and self._logins_left:
self._logins_left -= 1
if response.status_code // 100 != 2:
sys.stderr.write("Failed to retrieve project list'\n")
return dict()

result = dict()
for id in response.json().get('ids', []):
id_response = requests.get('{}/rest/product/{}{}'.format(self.url, id, self._login_arguments(required=False)))
if response.status_code // 100 == 4 and self._logins_left:
self._logins_left -= 1
if response.status_code // 100 != 2:
sys.stderr.write("Failed to query bugzilla about prod '{}'\n".format(id))
continue
@@ -403,8 +433,16 @@ def create(
if assign:
params['assigned_to'] = self.me().username

response = requests.post('{}/rest/bug{}'.format(self.url, self._login_arguments(required=True)), json=params)
if response.status_code // 100 != 2:
sys.stderr.write("Failed to create bug: {}\n".format(response.json().get('message', '?')))
response = None
try:
response = requests.post('{}/rest/bug{}'.format(self.url, self._login_arguments(required=True)), json=params)
except RuntimeError as e:
sys.stderr.write('{}\n'.format(e))
if response and response.status_code // 100 == 4 and self._logins_left:
self._logins_left -= 1
if not response or response.status_code // 100 != 2:
sys.stderr.write("Failed to create bug: {}\n".format(
response.json().get('message', '?') if response else 'Login attempts exhausted'),
)
return None
return self.issue(response.json()['id'])
@@ -21,11 +21,10 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import json
import os
import re
import unittest

from webkitbugspy import Issue, Tracker, User, bugzilla, mocks
from webkitbugspy import Tracker, User, bugzilla, mocks
from webkitcorepy import OutputCapture, mocks as wkmocks


@@ -380,3 +379,38 @@ def test_labels(self):
with mocks.Bugzilla(self.URL.split('://')[1], issues=mocks.ISSUES, projects=mocks.PROJECTS):
issue = bugzilla.Tracker(self.URL).issue(1)
self.assertEqual(issue.labels, [])

def test_exhausted_logins(self):
with mocks.Bugzilla(self.URL.split('://')[1], environment=wkmocks.Environment(
BUGS_EXAMPLE_COM_USERNAME='tcontributor@example.com',
BUGS_EXAMPLE_COM_PASSWORD='password',
), projects=mocks.PROJECTS, issues=mocks.ISSUES):
tracker = bugzilla.Tracker(self.URL)
tracker._logins_left = 0

with OutputCapture() as captured:
self.assertFalse(tracker.issue(1).close())
self.assertEqual(
captured.stderr.getvalue(),
'Exhausted login attempts\n'
"Failed to modify 'https://bugs.example.com/show_bug.cgi?id=1 Example issue 1'\n",
)

with OutputCapture() as captured:
self.assertIsNone(tracker.issue(1).add_comment('Failed comment'))
self.assertEqual(
captured.stderr.getvalue(),
'Exhausted login attempts\n'
"Failed to add comment to 'https://bugs.example.com/show_bug.cgi?id=1 Example issue 1'\n",
)

with OutputCapture() as captured:
self.assertIsNone(tracker.create(
'New bug', 'Creating new bug',
project='WebKit', component='Tables', version='Other',
))
self.assertEqual(
captured.stderr.getvalue(),
'Exhausted login attempts\n'
'Failed to create bug: Login attempts exhausted\n',
)

0 comments on commit e93b0a7

Please sign in to comment.