Skip to content

Commit 0b40f1b

Browse files
committed
remove unused encrypt_mylogin_cnf()
This function is not needed, and its use in the tests might be a little bit dangerous. The vendor tools are sufficient to edit mylogin.cnf.
1 parent 0aeb6ca commit 0b40f1b

5 files changed

Lines changed: 2 additions & 83 deletions

File tree

changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Documentation
2020
Internal
2121
---------
2222
* Improve test coverage for DSN variable expansion.
23+
* Remove unused support for writing `.mylogin.cnf` files.
2324

2425

2526
1.76.0 (2026/06/20)

mycli/config.py

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -138,56 +138,6 @@ def open_mylogin_cnf(name: str) -> TextIOWrapper | None:
138138
return TextIOWrapper(plaintext)
139139

140140

141-
# TODO reuse code between encryption an decryption
142-
def encrypt_mylogin_cnf(plaintext: IO[str]) -> BytesIO:
143-
"""Encryption of .mylogin.cnf file, analogous to calling
144-
mysql_config_editor.
145-
146-
Code is based on the python implementation by Kristian Koehntopp
147-
https://github.com/isotopp/mysql-config-coder
148-
149-
"""
150-
151-
def realkey(key: bytes) -> bytes:
152-
"""Create the AES key from the login key."""
153-
rkey = bytearray(16)
154-
for i in range(len(key)):
155-
rkey[i % 16] ^= key[i]
156-
return bytes(rkey)
157-
158-
def encode_line(plaintext: str, real_key: bytes, buf_len: int) -> bytes:
159-
aes = AES.new(real_key, AES.MODE_ECB)
160-
text_len = len(plaintext)
161-
pad_len = buf_len - text_len
162-
pad_chr = bytes(chr(pad_len), "utf8")
163-
plaintext_b = plaintext.encode() + pad_chr * pad_len
164-
encrypted_text = b"".join([aes.encrypt(plaintext_b[i : i + 16]) for i in range(0, len(plaintext_b), 16)])
165-
return encrypted_text
166-
167-
LOGIN_KEY_LENGTH = 20
168-
key = os.urandom(LOGIN_KEY_LENGTH)
169-
real_key = realkey(key)
170-
171-
outfile = BytesIO()
172-
173-
outfile.write(struct.pack("i", 0))
174-
outfile.write(key)
175-
176-
while True:
177-
line = plaintext.readline()
178-
if not line:
179-
break
180-
real_len = len(line)
181-
pad_len = (int(real_len / 16) + 1) * 16
182-
183-
outfile.write(struct.pack("i", pad_len))
184-
x = encode_line(line, real_key, pad_len)
185-
outfile.write(x)
186-
187-
outfile.seek(0)
188-
return outfile
189-
190-
191141
def read_and_decrypt_mylogin_cnf(f: BinaryIO) -> BytesIO | None:
192142
"""Read and decrypt the contents of .mylogin.cnf.
193143

test/features/connection.feature

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,3 @@ Feature: connect to a database:
2121
When we run mycli without arguments "host port"
2222
When we query "status"
2323
Then status contains "via UNIX socket"
24-
25-
Scenario: run mycli with mylogin.cnf configuration
26-
When we create mylogin.cnf file
27-
When we run mycli with arguments "login_path=test_login_path" without arguments "host port user pass defaults_file"
28-
Then we are logged in
29-
30-

test/features/steps/connection.py

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
11
# type: ignore
22

3-
import io
4-
import os
5-
63
from behave import then, when
74
import wrappers
85

9-
from mycli.config import encrypt_mylogin_cnf
10-
from test.features.environment import MYLOGIN_CNF_PATH, get_db_name_from_context
6+
from test.features.environment import get_db_name_from_context
117
from test.features.steps.utils import parse_cli_args_to_dict
12-
from test.utils import HOST, PASSWORD, PORT, USER
138

149
TEST_LOGIN_PATH = "test_login_path"
1510

@@ -31,15 +26,6 @@ def status_contains(context, expression):
3126
context.atprompt = True
3227

3328

34-
@when("we create mylogin.cnf file")
35-
def step_create_mylogin_cnf_file(context):
36-
os.environ.pop("MYSQL_TEST_LOGIN_FILE", None)
37-
mylogin_cnf = f"[{TEST_LOGIN_PATH}]\nhost = {HOST}\nport = {PORT}\nuser = {USER}\npassword = {PASSWORD}\n"
38-
with open(MYLOGIN_CNF_PATH, "wb") as f:
39-
input_file = io.StringIO(mylogin_cnf)
40-
f.write(encrypt_mylogin_cnf(input_file).read())
41-
42-
4329
@then("we are logged in")
4430
def we_are_logged_in(context):
4531
db_name = get_db_name_from_context(context)

test/pytests/test_config.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
from mycli.config import (
1717
_remove_pad,
1818
create_default_config,
19-
encrypt_mylogin_cnf,
2019
get_mylogin_cnf_path,
2120
log,
2221
open_mylogin_cnf,
@@ -244,16 +243,6 @@ def test_open_mylogin_cnf_error_paths(monkeypatch, tmp_path, caplog) -> None:
244243
assert 'Unable to read login path file.' in caplog.text
245244

246245

247-
def test_encrypt_mylogin_cnf_round_trip() -> None:
248-
plaintext = StringIO('[client]\nuser=test\npassword=secret\n')
249-
250-
encrypted = encrypt_mylogin_cnf(plaintext)
251-
decrypted = read_and_decrypt_mylogin_cnf(encrypted)
252-
253-
assert isinstance(encrypted, BytesIO)
254-
assert decrypted.read().decode('utf8') == '[client]\nuser=test\npassword=secret\n'
255-
256-
257246
def test_read_and_decrypt_mylogin_cnf_error_branches(caplog) -> None:
258247
incomplete_key = BytesIO(struct.pack('i', 0) + b'a')
259248
with caplog.at_level(logging.ERROR, logger='mycli.config'):

0 commit comments

Comments
 (0)