Skip to content

Commit 60ac757

Browse files
committed
cr fixes
1 parent be44b0f commit 60ac757

File tree

5 files changed

+67
-72
lines changed

5 files changed

+67
-72
lines changed

lms/lmsweb/__init__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import typing
44

55
from flask import Flask
6-
from flask_httpauth import HTTPBasicAuth
76
from flask_babel import Babel # type: ignore
7+
from flask_httpauth import HTTPBasicAuth
88
from flask_limiter import Limiter # type: ignore
99
from flask_limiter.util import get_remote_address # type: ignore
1010
from flask_mail import Mail # type: ignore
@@ -68,8 +68,6 @@ def get_password(username: str) -> typing.Optional[str]:
6868
def verify_password(username: str, client_password: str):
6969
username_username = models.User.username == username
7070
login_user = models.User.get_or_none(username_username)
71-
if login_user is None:
72-
return False
73-
if not login_user.is_password_valid(client_password):
71+
if login_user is None or not login_user.is_password_valid(client_password):
7472
return False
7573
return login_user

lms/lmsweb/config.py.example

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,4 @@ LIMITS_PER_HOUR = 50
6767
# Change password settings
6868
MAX_INVALID_PASSWORD_TRIES = 5
6969

70-
_REPOSITORY_FOLDER = os.getenv("REPOSITORY_FOLDER", os.path.abspath(os.path.join(os.curdir, "repositories")))
71-
72-
73-
def get_repository_folder():
74-
return _REPOSITORY_FOLDER
70+
REPOSITORY_FOLDER = os.getenv("REPOSITORY_FOLDER", os.path.abspath(os.path.join(os.curdir, "repositories")))

lms/lmsweb/git_service.py

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,14 @@
33
import subprocess # noqa: S404
44
import tempfile
55
import typing
6-
import logging
6+
import pathlib
77

88
import flask
99

1010
from lms.lmsdb import models
1111
from lms.models import upload
1212
from lms.utils import hashing
13-
14-
_logger = logging.getLogger(__name__)
13+
from lms.utils.log import log
1514

1615

1716
class _GitOperation(typing.NamedTuple):
@@ -23,6 +22,9 @@ class _GitOperation(typing.NamedTuple):
2322

2423

2524
class GitService:
25+
_GIT_PROCESS_TIMEOUT = 20
26+
_GIT_VALID_EXIT_CODE = 0
27+
2628
def __init__(
2729
self,
2830
user: models.User,
@@ -40,19 +42,19 @@ def project_name(self) -> str:
4042
return f'{self._exercise_id}-{self._user.id}'
4143

4244
@property
43-
def repository_folder(self) -> str:
44-
return os.path.join(self._base_repository_folder, self.project_name)
45+
def repository_folder(self) -> pathlib.Path:
46+
return pathlib.Path(self._base_repository_folder) / self.project_name
4547

4648
def handle_operation(self) -> flask.Response:
4749
git_operation = self._extract_git_operation()
48-
git_repository_folder = os.path.join(self.repository_folder, 'config')
50+
repository_folder = self.repository_folder / 'config'
4951

50-
first_time_repository = not os.path.exists(git_repository_folder)
51-
if first_time_repository:
52+
new_repository = not repository_folder.exists()
53+
if new_repository:
5254
self._initialize_bare_repository()
5355

5456
if not git_operation.supported:
55-
raise EnvironmentError
57+
raise OSError
5658

5759
data_out = self._execute_git_operation(git_operation)
5860

@@ -71,38 +73,42 @@ def handle_operation(self) -> flask.Response:
7173

7274
return self.build_response(data_out, git_operation)
7375

74-
def _execute_git_operation(self, git_operation: _GitOperation) -> bytes:
76+
def _execute_command(
77+
self,
78+
args: typing.List[str],
79+
cwd: typing.Union[str, pathlib.Path],
80+
proc_input: typing.Optional[bytes] = None,
81+
):
7582
proc = subprocess.Popen( # noqa: S603
76-
args=git_operation.service_command,
83+
args=args,
7784
stdin=subprocess.PIPE,
7885
stdout=subprocess.PIPE,
7986
stderr=subprocess.PIPE,
80-
cwd=self._base_repository_folder,
87+
cwd=cwd,
8188
)
82-
data_out, _ = proc.communicate(self._request.data, 20)
83-
if proc.wait() != 0:
84-
_logger.error(
85-
'Failed to execute command. stdout=%s\nstderr=%s',
86-
proc.stdout.read(), proc.stderr.read(),
89+
data_out, _ = proc.communicate(proc_input, self._GIT_PROCESS_TIMEOUT)
90+
operation_failed = proc.wait() != self._GIT_VALID_EXIT_CODE
91+
if operation_failed:
92+
log.error(
93+
'Failed to execute command %s. stdout=%s\nstderr=%s',
94+
args, proc.stdout.read(), proc.stderr.read(),
8795
)
88-
raise EnvironmentError
96+
raise OSError
8997
return data_out
9098

99+
def _execute_git_operation(self, git_operation: _GitOperation) -> bytes:
100+
return self._execute_command(
101+
args=git_operation.service_command,
102+
cwd=self._base_repository_folder,
103+
proc_input=self._request.data,
104+
)
105+
91106
def _initialize_bare_repository(self) -> None:
92107
os.makedirs(self.repository_folder, exist_ok=True)
93-
proc = subprocess.Popen( # noqa: S603
108+
self._execute_command(
94109
args=['git', 'init', '--bare'],
95-
stdin=subprocess.PIPE,
96-
stdout=subprocess.PIPE,
97-
stderr=subprocess.PIPE,
98110
cwd=self.repository_folder,
99111
)
100-
if proc.wait() != 0:
101-
_logger.error(
102-
'Failed to execute command. stdout=%s\nstderr=%s',
103-
proc.stdout.read(), proc.stderr.read(),
104-
)
105-
raise EnvironmentError
106112

107113
@staticmethod
108114
def build_response(
@@ -167,7 +173,7 @@ def format_response_callback(response_bytes: bytes) -> bytes:
167173
format_response = format_response_callback
168174

169175
else:
170-
_logger.error(
176+
log.error(
171177
'Failed to find the git command for route %s',
172178
self._request.path,
173179
)
@@ -182,33 +188,29 @@ def format_response_callback(response_bytes: bytes) -> bytes:
182188
)
183189

184190
def _load_files_from_repository(self) -> typing.List[upload.File]:
191+
"""
192+
Since the remote server is a git bare repository
193+
we need to 'clone' the bare repository to resolve the files.
194+
We are not changing the remote at any end - that is why we
195+
don't care about git files here.
196+
"""
185197
with tempfile.TemporaryDirectory() as tempdir:
186-
proc = subprocess.Popen( # noqa: S603
198+
self._execute_command(
187199
args=['git', 'clone', self.repository_folder, '.'],
188-
stdin=subprocess.PIPE,
189-
stdout=subprocess.PIPE,
190-
stderr=subprocess.PIPE,
191200
cwd=tempdir,
192201
)
193-
return_code = proc.wait()
194-
if return_code != 0:
195-
_logger.error(
196-
'Failed to execute command. stdout=%s\nstderr=%s',
197-
proc.stdout.read(), proc.stderr.read(),
198-
)
199-
raise EnvironmentError
200202
to_return = []
201203
# remove git internal files
202-
shutil.rmtree(os.path.join(tempdir, '.git'))
204+
shutil.rmtree(pathlib.Path(tempdir) / '.git')
203205
for root, _, files in os.walk(tempdir):
204206
for file in files:
205207
upload_file = self._load_file(file, root, tempdir)
206208
to_return.append(upload_file)
207209
return to_return
208210

209211
@staticmethod
210-
def _load_file(file_path: str, root: str, tempdir: str) -> upload.File:
211-
with open(os.path.join(root, file_path)) as f:
212-
file_path = os.path.join(os.path.relpath(root, tempdir), file_path)
212+
def _load_file(file_name: str, root: str, tempdir: str) -> upload.File:
213+
file_path = str(pathlib.Path(root).relative_to(tempdir) / file_name)
214+
with open(pathlib.Path(root) / file_name) as f:
213215
upload_file = upload.File(path=file_path, code=f.read())
214216
return upload_file

lms/lmsweb/views.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import arrow # type: ignore
44
from flask import (
5-
jsonify, make_response, render_template, request,
5+
Response, jsonify, make_response, render_template, request,
66
send_from_directory, session, url_for,
77
)
88
from flask_babel import gettext as _ # type: ignore
@@ -24,7 +24,7 @@
2424
)
2525
from lms.lmsweb.config import (
2626
CONFIRMATION_TIME, LANGUAGES, LIMITS_PER_HOUR,
27-
LIMITS_PER_MINUTE, LOCALE, MAX_UPLOAD_SIZE, get_repository_folder,
27+
LIMITS_PER_MINUTE, LOCALE, MAX_UPLOAD_SIZE, REPOSITORY_FOLDER,
2828
)
2929
from lms.lmsweb.forms.change_password import ChangePasswordForm
3030
from lms.lmsweb.forms.register import RegisterForm
@@ -547,14 +547,14 @@ def download(download_id: str):
547547

548548
@webapp.route(f'{routes.GIT}/info/refs')
549549
@webapp.route(f'{routes.GIT}/git-receive-pack', methods=['POST'])
550-
@webapp.route(f'{routes.GIT}/git-upload-pack', methods=('POST',))
550+
@webapp.route(f'{routes.GIT}/git-upload-pack', methods=['POST'])
551551
@http_basic_auth.login_required
552-
def git_handler(exercise_id: int):
552+
def git_handler(exercise_id: int) -> Response:
553553
git_service = GitService(
554554
user=http_basic_auth.current_user(),
555555
exercise_id=exercise_id,
556556
request=request,
557-
base_repository_folder=get_repository_folder(),
557+
base_repository_folder=REPOSITORY_FOLDER,
558558
)
559559
return git_service.handle_operation()
560560

tests/test_git_solution.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@
22
import os.path
33
import shutil
44
import tempfile
5+
from unittest import mock
56

67
from flask.testing import FlaskClient
78

89
from lms.lmsdb import models
9-
from lms.lmsweb import config, webapp
10+
from lms.lmsweb import webapp
1011
from tests import conftest
1112

1213

@@ -38,26 +39,21 @@ class TestSendSolutionFromGit:
3839
GET_METHOD = FlaskClient.get.__name__
3940
POST_METHOD = FlaskClient.post.__name__
4041

41-
temp_folder = None
42+
temp_folder = ''
4243

43-
@classmethod
44-
def setup_class(cls):
45-
cls.temp_folder = tempfile.mkdtemp()
44+
def setup_method(self, _method: str) -> None:
45+
self.temp_folder = tempfile.mkdtemp()
4646

47-
def setup_method(self, method):
48-
config._REPOSITORY_FOLDER = os.path.join(self.temp_folder, method.__name__)
49-
50-
@classmethod
51-
def teardown_class(cls):
52-
if cls.temp_folder and os.path.exists(cls.temp_folder):
53-
shutil.rmtree(cls.temp_folder)
47+
def teardown_method(self, _method: str) -> None:
48+
if self.temp_folder and os.path.exists(self.temp_folder):
49+
shutil.rmtree(self.temp_folder)
5450

5551
@staticmethod
5652
def _get_formatted_git_url(exercise: models.Exercise, rel_path: str) -> str:
5753
return f'/git/{exercise.id}.git/{rel_path}'
5854

59-
@staticmethod
6055
def _send_git_request(
56+
self,
6157
username: str,
6258
method_name: str,
6359
url: str,
@@ -71,7 +67,10 @@ def _send_git_request(
7167
('Authorization', f'Basic {encoded_credentials}'),
7268
)
7369
query_string = {'service': service} if service is not None else None
74-
return getattr(client, method_name)(url, query_string=query_string, headers=headers, data=data)
70+
71+
# patch the REPOSITORY_FOLDER to make new repository every test
72+
with mock.patch('lms.lmsweb.views.REPOSITORY_FOLDER', self.temp_folder):
73+
return getattr(client, method_name)(url, query_string=query_string, headers=headers, data=data)
7574

7675
def test_not_authorized_access(self, exercise: models.Exercise, student_user: models.User):
7776
client = conftest.get_logged_user(student_user.username)

0 commit comments

Comments
 (0)