Skip to content

Commit

Permalink
Handle "password reset" errors
Browse files Browse the repository at this point in the history
  • Loading branch information
vvd170501 committed Sep 23, 2023
1 parent c81f8ed commit dc0e516
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 7 deletions.
2 changes: 1 addition & 1 deletion kks/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.16.26'
__version__ = '1.16.27'
15 changes: 15 additions & 0 deletions kks/errors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import click
from click.exceptions import ClickException

from kks.util.common import format_file


class EjudgeError(ClickException):
def __init__(self, message='unknown error'):
Expand All @@ -25,6 +27,19 @@ def show(self):
click.secho(self.message, fg=self.fg, err=True)


class DefaultPasswordError(AuthError):
message = click.style(
'Your account has a default password.\n'
'Change your password via Web UI and rerun "kks auth" or edit ', fg='red'
) + format_file('~/.kks/config.ini') + click.style(' manually.', fg='red')

def __init__(self):
super().__init__(self.message)

def show(self):
click.echo(self.message, err=True)


class APIError(EjudgeError):
INVALID_RESPONSE = -2
UNKNOWN = -1
Expand Down
28 changes: 22 additions & 6 deletions kks/util/ejudge.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import inspect
import json
import re
from base64 import b64decode
from copy import copy
from dataclasses import asdict, dataclass
Expand All @@ -13,7 +14,7 @@
import click

from kks import __version__
from kks.errors import EjudgeUnavailableError, AuthError, APIError
from kks.errors import AuthError, APIError, DefaultPasswordError, EjudgeUnavailableError
from kks.util.common import deprecated
from kks.util.storage import Config, PickleStorage

Expand Down Expand Up @@ -505,6 +506,12 @@ def auth(self, creds: AuthData):

class EjudgeSession:

_invalid_session_error_text = 'Invalid session'
_password_reset_error_text = 'You must set up new password!'
_auth_issues_pattern = re.compile(
'|'.join([_invalid_session_error_text, _password_reset_error_text]).encode()
)

@dataclass(frozen=True)
class _SessionKey:
base_url: str
Expand Down Expand Up @@ -599,6 +606,9 @@ def _auth(self, auth_data: Optional[AuthData] = None, internal: bool = True):
if 'Permission denied' in page.text:
raise AuthError('Permission denied (invalid username, password or contest id)')

if self._password_reset_error_text in page.text:
raise DefaultPasswordError()

self._update_sids(page.url)
self._store_auth_state()

Expand Down Expand Up @@ -680,11 +690,17 @@ def _request(self, method, url, *args, **kwargs):

response = method(url, *args, **kwargs)
_check_response(response)
# the requested page may contain binary data (e.g. problem attachments)
if b'Invalid session' in response.content:
self._auth()
params['SID'] = self._sids.sid
response = method(url, *args, **kwargs)
# the requested page may contain binary data (e.g. problem attachments), so we use .content
# TODO search only if returned page is html
match = self._auth_issues_pattern.search(response.content)
if match:
if match.group(0) == self._invalid_session_error_text:
self._auth()
params['SID'] = self._sids.sid
response = method(url, *args, **kwargs)
else:
# Do we need the same check for API? Does the API even require password reset?
raise DefaultPasswordError()
return response

def get(self, url, *args, **kwargs):
Expand Down

0 comments on commit dc0e516

Please sign in to comment.