Skip to content
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

[auth] Validate usernames upfront #14808

Merged
merged 5 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion auth/auth/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ async def check_valid_new_user(tx: Transaction, username, login_id, is_developer
raise MultipleUserTypes(username)
if not is_service_account and not login_id:
raise EmptyLoginID(username)
if not username or not all(c for c in username if c.isalnum()):
if not username or not (username.isalnum() and username.islower()):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I missing an important reason why this was being done character by character previously?

raise InvalidUsername(username)

existing_users = await users_with_username_or_login_id(tx, username, login_id)
Expand All @@ -151,6 +151,18 @@ async def check_valid_new_user(tx: Transaction, username, login_id, is_developer
return None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here we seem to only allow alphanumeric usernames. Do you know if that's true, or is this not being executed for some reason? Either way, it would be nice to define username validity in one place.

Copy link
Collaborator Author

@cjllanwarne cjllanwarne Jan 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, interesting, I somehow missed this. The original error report was caused by uppercase characters, which I guess is why it got past this check originally. Usernames which don't conform to RFC1123 was where I started seeing downstream problems with user creation, but since "(lowercase) alphanumeric" is a subset of RFC1123, I'm inclined to make that our new rule, instead of expanding the options up to include all of RFC1123.

And I like the idea of moving that check into this function, instead of adding a new validation. I'll do that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, RFC1123 would be a fine validation for the credentials_secret_name that we allow as a development environment option, and is currently unvalidated until it potentially blows up the user creation process downstream. So I'll repurpose the original change into a token name validator, and make username validation stricter and happening here



def validate_credentials_secret_name_input(secret_name: Optional[str]):
if secret_name is None:
return

regex = re.compile(r'^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$')
Copy link
Contributor

@grohli grohli Feb 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like one too many escape characters on the ".", replace "\." with "." double backslash with single backslash (I had to write that out with words because GitHub is escape charactering my escape characters and I don't wanna risk it) -- Do these attached pictures look like an okay sanity check?
image
image

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice catch!

if not regex.match(secret_name):
raise AuthUserError(
f'invalid credentials_secret_name {secret_name}. Must match RFC1123 (lowercase alphanumeric plus "." and "-", start and end with alphanumeric)',
'error',
)


async def insert_new_user(
db: Database,
username: str,
Expand Down Expand Up @@ -183,6 +195,7 @@ async def _insert(tx):
),
)

validate_credentials_secret_name_input(hail_credentials_secret_name)
await _insert()
return True

Expand Down
2 changes: 1 addition & 1 deletion auth/auth/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(self, username, login_id):

class InvalidUsername(AuthUserError):
def __init__(self, username):
super().__init__(f"Invalid username '{username}'. Must be a non-empty alphanumeric string.", 'error')
super().__init__(f"Invalid username '{username}'. Must be a non-empty lowercase alphanumeric string.", 'error')


class InvalidType(AuthUserError):
Expand Down