Skip to content

Commit 90bb1fb

Browse files
authored
DPE-4643 ensure username uniqueness (#464)
* ensure username uniqueness * libs bump * temporary branch for router * test against temp branch revision of router * support for legacy username format * model uuid as suffix * trunc at 26chars * leftover
1 parent 2d3059c commit 90bb1fb

File tree

4 files changed

+37
-11
lines changed

4 files changed

+37
-11
lines changed

lib/charms/mysql/v0/mysql.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,29 +1215,28 @@ def delete_users_for_unit(self, unit_name: str) -> None:
12151215
logger.exception(f"Failed to query and delete users for unit {unit_name}")
12161216
raise MySQLDeleteUsersForUnitError(e.message)
12171217

1218-
def delete_users_for_relation(self, relation_id: int) -> None:
1218+
def delete_users_for_relation(self, username: str) -> None:
12191219
"""Delete users for a relation.
12201220
12211221
Args:
1222-
relation_id: The id of the relation for which to delete mysql users for
1222+
username: The username do drop
12231223
12241224
Raises:
12251225
MySQLDeleteUsersForRelationError if there is an error deleting users for the relation
12261226
"""
1227-
user = f"relation-{str(relation_id)}"
12281227
drop_users_command = [
12291228
f"shell.connect_to_primary('{self.server_config_user}:{self.server_config_password}@{self.instance_address}')",
1230-
f"session.run_sql(\"DROP USER IF EXISTS '{user}'@'%';\")",
1229+
f"session.run_sql(\"DROP USER IF EXISTS '{username}'@'%';\")",
12311230
]
12321231
# If the relation is with a MySQL Router charm application, delete any users
12331232
# created by that application.
12341233
drop_users_command.extend(
1235-
self._get_statements_to_delete_users_with_attribute("created_by_user", f"'{user}'")
1234+
self._get_statements_to_delete_users_with_attribute("created_by_user", f"'{username}'")
12361235
)
12371236
try:
12381237
self._run_mysqlsh_script("\n".join(drop_users_command))
12391238
except MySQLClientError as e:
1240-
logger.exception(f"Failed to delete users for relation {relation_id}")
1239+
logger.exception(f"Failed to delete {username=}")
12411240
raise MySQLDeleteUsersForRelationError(e.message)
12421241

12431242
def delete_user(self, username: str) -> None:

src/relations/mysql_provider.py

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,20 @@ def _get_or_set_password(self, relation) -> str:
194194
self.database.update_relation_data(relation.id, {"password": password})
195195
return password
196196

197+
def _get_username(self, relation_id: int, legacy: bool = False) -> str:
198+
"""Generate a unique username for the relation using the model uuid and the relation id.
199+
200+
Args:
201+
relation_id (int): The relation id.
202+
legacy (bool): If True, generate a username without the model uuid.
203+
204+
Returns:
205+
str: A valid unique username (max 32 characters long)
206+
"""
207+
if legacy:
208+
return f"relation-{relation_id}"
209+
return f"relation-{relation_id}_{self.model.uuid.replace('-', '')}"[:26]
210+
197211
def _on_database_requested(self, event: DatabaseRequestedEvent):
198212
"""Handle the `database-requested` event."""
199213
if not self.charm.unit.is_leader():
@@ -211,7 +225,7 @@ def _on_database_requested(self, event: DatabaseRequestedEvent):
211225
if event.extra_user_roles:
212226
extra_user_roles = event.extra_user_roles.split(",")
213227
# user name is derived from the relation id
214-
db_user = f"relation-{relation_id}"
228+
db_user = self._get_username(relation_id)
215229
db_pass = self._get_or_set_password(event.relation)
216230

217231
remote_app = event.app.name
@@ -274,8 +288,18 @@ def _on_database_broken(self, event: RelationBrokenEvent) -> None:
274288

275289
relation_id = event.relation.id
276290
try:
277-
self.charm._mysql.delete_users_for_relation(relation_id)
278-
logger.info(f"Removed user for relation {relation_id}")
291+
if self.charm._mysql.does_mysql_user_exist(self._get_username(relation_id), "%"):
292+
self.charm._mysql.delete_users_for_relation(self._get_username(relation_id))
293+
elif self.charm._mysql.does_mysql_user_exist(
294+
self._get_username(relation_id, legacy=True), "%"
295+
):
296+
self.charm._mysql.delete_users_for_relation(
297+
self._get_username(relation_id, legacy=True)
298+
)
299+
else:
300+
logger.warning(f"User(s) not found for relation {relation_id}")
301+
return
302+
logger.info(f"Removed user(s) for relation {relation_id}")
279303
except (MySQLDeleteUsersForRelationError, KeyError):
280304
logger.error(f"Failed to delete user for relation {relation_id}")
281305

tests/unit/test_database.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,15 @@ def test_database_requested(
6666
self.database_relation_id, "app", {"database": "test_db"}
6767
)
6868

69+
username = (
70+
f"relation-{self.database_relation_id}_{self.harness.model.uuid.replace('-', '')}"
71+
)[:26]
6972
self.assertEqual(
7073
database_relation_databag,
7174
{
7275
"data": '{"database": "test_db"}',
7376
"password": "super_secure_password",
74-
"username": f"relation-{self.database_relation_id}",
77+
"username": username,
7578
"endpoints": "2.2.2.2:3306",
7679
"version": "8.0.29-0ubuntu0.20.04.3",
7780
"database": "test_db",

tests/unit/test_mysql.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -872,7 +872,7 @@ def test_delete_users_for_relation_failure(
872872
_run_mysqlsh_script.side_effect = MySQLClientError
873873

874874
with self.assertRaises(MySQLDeleteUsersForRelationError):
875-
self.mysql.delete_users_for_relation(40)
875+
self.mysql.delete_users_for_relation("foouser")
876876

877877
@patch("charms.mysql.v0.mysql.MySQLBase._run_mysqlsh_script")
878878
def test_delete_user(self, _run_mysqlsh_script):

0 commit comments

Comments
 (0)