-
Notifications
You must be signed in to change notification settings - Fork 480
feat: add do
command to update the authentication plugin of MySQL users
#1097
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
f9fb3bc
9ee515d
9735201
a17a0d6
cb1d1fa
216d0fd
49f850e
d32b8f8
735a6b1
6c927ce
eb3609e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
- [Improvement] Add a do command to update the authentication plugin of existing MySQL users from mysql_native_password to caching_sha2_password for compatibility with MySQL v8.4.0 and above. (by @Danyal-Faheem) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,11 @@ | |
from tutor import config as tutor_config | ||
from tutor import env, fmt, hooks | ||
from tutor.commands.context import Context | ||
from tutor.commands.jobs_utils import get_mysql_change_charset_query | ||
from tutor.commands.jobs_utils import ( | ||
create_user_template, | ||
get_mysql_change_charset_query, | ||
set_theme_template, | ||
) | ||
from tutor.hooks import priorities | ||
|
||
|
||
|
@@ -109,24 +113,6 @@ def createuser( | |
yield ("lms", create_user_template(superuser, staff, name, email, password)) | ||
|
||
|
||
def create_user_template( | ||
superuser: str, staff: bool, username: str, email: str, password: str | ||
) -> str: | ||
opts = "" | ||
if superuser: | ||
opts += " --superuser" | ||
if staff: | ||
opts += " --staff" | ||
return f""" | ||
./manage.py lms manage_user {opts} {username} {email} | ||
./manage.py lms shell -c " | ||
from django.contrib.auth import get_user_model | ||
u = get_user_model().objects.get(username='{username}') | ||
u.set_password('{password}') | ||
u.save()" | ||
""" | ||
|
||
|
||
@click.command(help="Import the demo course") | ||
@click.option( | ||
"-r", | ||
|
@@ -273,43 +259,6 @@ def settheme(domains: list[str], theme_name: str) -> t.Iterable[tuple[str, str]] | |
yield ("lms", set_theme_template(theme_name, domains)) | ||
|
||
|
||
def set_theme_template(theme_name: str, domain_names: list[str]) -> str: | ||
""" | ||
For each domain, get or create a Site object and assign the selected theme. | ||
""" | ||
# Note that there are no double quotes " in this piece of code | ||
python_command = """ | ||
import sys | ||
from django.contrib.sites.models import Site | ||
def assign_theme(name, domain): | ||
print('Assigning theme', name, 'to', domain) | ||
if len(domain) > 50: | ||
sys.stderr.write( | ||
'Assigning a theme to a site with a long (> 50 characters) domain name.' | ||
' The displayed site name will be truncated to 50 characters.\\n' | ||
) | ||
site, _ = Site.objects.get_or_create(domain=domain) | ||
if not site.name: | ||
name_max_length = Site._meta.get_field('name').max_length | ||
site.name = domain[:name_max_length] | ||
site.save() | ||
site.themes.all().delete() | ||
if name != 'default': | ||
site.themes.create(theme_dir_name=name) | ||
""" | ||
domain_names = domain_names or [ | ||
"{{ LMS_HOST }}", | ||
"{{ LMS_HOST }}:8000", | ||
"{{ CMS_HOST }}", | ||
"{{ CMS_HOST }}:8001", | ||
"{{ PREVIEW_LMS_HOST }}", | ||
"{{ PREVIEW_LMS_HOST }}:8000", | ||
] | ||
for domain_name in domain_names: | ||
python_command += f"assign_theme('{theme_name}', '{domain_name}')\n" | ||
return f'./manage.py lms shell -c "{python_command}"' | ||
|
||
|
||
@click.command(context_settings={"ignore_unknown_options": True}) | ||
@click.argument("args", nargs=-1) | ||
def sqlshell(args: list[str]) -> t.Iterable[tuple[str, str]]: | ||
|
@@ -428,6 +377,95 @@ def generate_query_to_append(tables: list[str], exclude: bool = False) -> str: | |
fmt.echo_info("MySQL charset and collation successfully upgraded") | ||
|
||
|
||
@click.command( | ||
short_help="Update the authentication plugin of a mysql user to caching_sha2_password.", | ||
help=( | ||
"Update the authentication plugin of a mysql user to caching_sha2_password from mysql_native_password. You can specify either specific users to update or all to update all users." | ||
), | ||
) | ||
@click.option( | ||
"-p", | ||
"--password", | ||
help="Specify password from the command line.", | ||
) | ||
@click.argument( | ||
"user", | ||
) | ||
@click.pass_obj | ||
def update_mysql_authentication_plugin( | ||
context: Context, user: str, password: str | ||
) -> t.Iterable[tuple[str, str]]: | ||
""" | ||
Update the authentication plugin of MySQL users from mysql_native_password to caching_sha2_password | ||
Handy command utilized when upgrading to v8.4 of MySQL which deprecates mysql_native_password | ||
""" | ||
|
||
config = tutor_config.load(context.root) | ||
|
||
if not config["RUN_MYSQL"]: | ||
fmt.echo_info( | ||
"You are not running MySQL (RUN_MYSQL=False). It is your " | ||
"responsibility to update the authentication plugin of mysql users." | ||
) | ||
return | ||
|
||
# Official plugins that have their own mysql user | ||
known_plugins_with_mysql_keys = [ | ||
"credentials", | ||
"discovery", | ||
"ecommerce", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ecommerce has been deprecated, let's not add it here. Or should we, for backward compatibility purposes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right, thanks for this catch.
Since this PR will be merged in release and will be launched in a version of Tutor v19.0.1+, it does not make sense to keep ecommerce. If someone is running ecommerce, they can still use the command and provide the password as a command line option. |
||
"jupyter", | ||
"notes", | ||
"openedx", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. openedx user is not part of plugin but rather core, it does not belong in the list. We can rename the variable to known_mysql_users (or something along these lines) and then clarify via comments which is core and which is from plugins. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've updated the variable name and added a small comment to signify the difference between plugin users and core user. |
||
"xqueue", | ||
] | ||
|
||
# Create a list of the usernames and passwords | ||
Danyal-Faheem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
known_mysql_credentials_keys = [ | ||
(f"{plugin.upper()}_MYSQL_USERNAME", f"{plugin.upper()}_MYSQL_PASSWORD") | ||
for plugin in known_plugins_with_mysql_keys | ||
] | ||
# Add the root user as it is the only one that is different from the rest | ||
known_mysql_credentials_keys.append(("MYSQL_ROOT_USERNAME", "MYSQL_ROOT_PASSWORD")) | ||
|
||
known_mysql_credentials = {} | ||
# Build the dictionary of known credentials | ||
Danyal-Faheem marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for k, v in known_mysql_credentials_keys: | ||
if username := config.get(k): | ||
known_mysql_credentials[username] = config[v] | ||
|
||
if not password: | ||
password = known_mysql_credentials.get(user) # type: ignore | ||
|
||
# Prompt the user if password was not found in config | ||
if not password: | ||
password = click.prompt( | ||
f"Please enter the password for the user {user}", | ||
type=str, | ||
) | ||
|
||
host = "%" | ||
|
||
query = f"ALTER USER IF EXISTS '{user}'@'{host}' IDENTIFIED with caching_sha2_password BY '{password}';" | ||
|
||
yield ( | ||
"lms", | ||
shlex.join( | ||
[ | ||
"mysql", | ||
"--user={{ MYSQL_ROOT_USERNAME }}", | ||
"--password={{ MYSQL_ROOT_PASSWORD }}", | ||
"--host={{ MYSQL_HOST }}", | ||
"--port={{ MYSQL_PORT }}", | ||
"--database={{ OPENEDX_MYSQL_DATABASE }}", | ||
"--show-warnings", | ||
"-e", | ||
query, | ||
] | ||
), | ||
) | ||
|
||
|
||
def add_job_commands(do_command_group: click.Group) -> None: | ||
""" | ||
This is meant to be called with the `local/dev/k8s do` group commands, to add the | ||
|
@@ -511,5 +549,6 @@ def do_callback(service_commands: t.Iterable[tuple[str, str]]) -> None: | |
print_edx_platform_setting, | ||
settheme, | ||
sqlshell, | ||
update_mysql_authentication_plugin, | ||
] | ||
) |
Uh oh!
There was an error while loading. Please reload this page.