-
Notifications
You must be signed in to change notification settings - Fork 133
feat: Allow Wikimedia users to register without email #1041
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
base: enext
Are you sure you want to change the base?
Changes from 5 commits
bd64b99
7c7deda
f6acb42
45ec22a
3cca425
c79302a
94753df
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,21 @@ | ||
| from django.db import migrations, models | ||
|
|
||
|
|
||
| class Migration(migrations.Migration): | ||
|
|
||
| dependencies = [ | ||
| ('base', '0005_alter_logentry_data'), | ||
| ] | ||
|
|
||
| operations = [ | ||
| migrations.AddField( | ||
| model_name='user', | ||
| name='is_wikimedia_user', | ||
| field=models.BooleanField(default=False, verbose_name='Is Wikimedia user'), | ||
| ), | ||
| migrations.AlterField( | ||
| model_name='user', | ||
| name='wikimedia_username', | ||
| field=models.CharField(max_length=255, blank=True, null=True, unique=True, verbose_name='Wikimedia username'), | ||
| ), | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| def get_or_create_email_for_wikimedia_user(username, email=None, user_id=None): | ||
| """ | ||
| Generate an email for Wikimedia users. | ||
|
|
||
| Sanitizes username to ensure valid email format and uniqueness. | ||
| Falls back to user ID if username is empty or invalid. | ||
|
|
||
| Args: | ||
| username (str): Wikimedia username | ||
| email (str): Email from Wikimedia profile (can be None) | ||
| user_id: Wikimedia user ID for uniqueness and fallback | ||
|
|
||
| Returns: | ||
| str: Email address | ||
| """ | ||
| if email and email.strip(): | ||
| return email | ||
|
|
||
| # Sanitize username: lowercase, strip, replace whitespace with dots, remove invalid chars | ||
| if username: | ||
| sanitized = username.lower().strip() | ||
| sanitized = sanitized.replace(' ', '.').replace('_', '.') | ||
| sanitized = ''.join(c for c in sanitized if c.isalnum() or c in '.-') | ||
| while '..' in sanitized: | ||
| sanitized = sanitized.replace('..', '.') | ||
| sanitized = sanitized.strip('.') | ||
| else: | ||
| sanitized = None | ||
|
|
||
| # If sanitized username is empty or invalid, use user ID as fallback | ||
| if sanitized: | ||
| if user_id: | ||
| return f"{sanitized}.{user_id}@wikimedia.local" | ||
| return f"{sanitized}@wikimedia.local" | ||
| else: | ||
| # Fallback: use Wikimedia user ID only | ||
| if user_id: | ||
| return f"wm.{user_id}@wikimedia.local" | ||
| else: | ||
| return "[email protected]" | ||
|
|
||
|
|
||
| def is_placeholder_email(email): | ||
| """ | ||
| Check if an email is a placeholder (non-routable) Wikimedia address. | ||
| These emails should never have messages sent to them. | ||
|
|
||
| Args: | ||
| email (str): Email address to check | ||
|
|
||
| Returns: | ||
| bool: True if the email is a placeholder Wikimedia email | ||
| """ | ||
| if not email: | ||
| return False | ||
| return email.endswith('@wikimedia.local') | ||
|
|
||
|
|
||
| def is_wikimedia_user(user): | ||
| """ | ||
| Check if user is authenticated via Wikimedia OAuth | ||
|
|
||
| Args: | ||
| user: Django User instance | ||
|
|
||
| Returns: | ||
| bool: True if user is a Wikimedia OAuth user | ||
| """ | ||
| return user.is_authenticated and getattr(user, 'is_wikimedia_user', False) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,7 @@ | |
| from eventyay.control.permissions import AdministratorPermissionRequiredMixin | ||
| from eventyay.eventyay_common.views.auth import process_login_and_set_cookie | ||
| from eventyay.helpers.urls import build_absolute_uri | ||
| from eventyay.person.utils import get_or_create_email_for_wikimedia_user | ||
|
|
||
| from .schemas.login_providers import LoginProviders | ||
| from .schemas.oauth2_params import OAuth2Params | ||
|
|
@@ -95,28 +96,65 @@ def get_or_create_user(request: HttpRequest) -> User: | |
| social_account = request.user.socialaccount_set.filter( | ||
| provider='mediawiki' | ||
| ).last() # Fetch only the latest signed in Wikimedia account | ||
| email = request.user.email | ||
| wikimedia_username = '' | ||
|
|
||
| if social_account: | ||
| extra_data = social_account.extra_data | ||
| wikimedia_username = extra_data.get('username', extra_data.get('realname', '')) | ||
|
|
||
| user, created = User.objects.get_or_create( | ||
| email=request.user.email, | ||
| defaults={ | ||
| 'locale': getattr(request, 'LANGUAGE_CODE', settings.LANGUAGE_CODE), | ||
| 'timezone': getattr(request, 'timezone', settings.TIME_ZONE), | ||
| 'auth_backend': 'native', | ||
| 'password': '', | ||
| 'wikimedia_username': wikimedia_username, | ||
| }, | ||
|
|
||
| # Guard: ensure we have a valid username | ||
| if not wikimedia_username or not wikimedia_username.strip(): | ||
| # Use social account ID as fallback | ||
| wikimedia_username = f"wm_{social_account.uid}" | ||
|
|
||
| # Generate placeholder email for Wikimedia users without email | ||
| final_email = get_or_create_email_for_wikimedia_user( | ||
| wikimedia_username, | ||
| email, | ||
| user_id=social_account.uid if social_account else None | ||
| ) | ||
|
|
||
| # Update wikimedia_username if the user exists but has no wikimedia_username value set | ||
| # (basically our existing users), or if the user has updated his username in his wikimedia account | ||
| if not created and (not user.wikimedia_username or user.wikimedia_username != wikimedia_username): | ||
| user.wikimedia_username = wikimedia_username | ||
| user.save() | ||
| # For Wikimedia users, look up by wikimedia_username instead of email | ||
| # to avoid collisions between placeholder emails | ||
| if social_account: | ||
| user, created = User.objects.get_or_create( | ||
| wikimedia_username=wikimedia_username, | ||
| defaults={ | ||
| 'email': final_email, | ||
| 'locale': getattr(request, 'LANGUAGE_CODE', settings.LANGUAGE_CODE), | ||
| 'timezone': getattr(request, 'timezone', settings.TIME_ZONE), | ||
| 'auth_backend': 'mediawiki', | ||
| 'password': '', | ||
| 'is_wikimedia_user': True, | ||
| }, | ||
| ) | ||
Aqil-Ahmad marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| else: | ||
| # Non-Wikimedia users: use email as before | ||
| user, created = User.objects.get_or_create( | ||
| email=final_email, | ||
| defaults={ | ||
| 'locale': getattr(request, 'LANGUAGE_CODE', settings.LANGUAGE_CODE), | ||
| 'timezone': getattr(request, 'timezone', settings.TIME_ZONE), | ||
| 'auth_backend': 'native', | ||
| 'password': '', | ||
| }, | ||
| ) | ||
|
Comment on lines
+124
to
+134
|
||
|
|
||
| # Update wikimedia_username and is_wikimedia_user if the user exists | ||
| if not created: | ||
| update_fields = [] | ||
| if not user.wikimedia_username or user.wikimedia_username != wikimedia_username: | ||
| user.wikimedia_username = wikimedia_username | ||
| update_fields.append('wikimedia_username') | ||
|
|
||
| # Update is_wikimedia_user if user exists and is logging in via Wikimedia | ||
| if social_account and not user.is_wikimedia_user: | ||
| user.is_wikimedia_user = True | ||
| update_fields.append('is_wikimedia_user') | ||
|
|
||
| if update_fields: | ||
| user.save(update_fields=update_fields) | ||
|
|
||
| return user | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.