Skip to content

Commit

Permalink
Merge pull request #1715 from codalab/develop
Browse files Browse the repository at this point in the history
* Update version.json for release 1.15.0 (#1712)

* Merge develop into master (#1405)

* organization oidc login added

* unused test file removed

* http client

* some changes

* oidc login and signup added

* oidc flow completed

* Prevent LimitOverrunError with large output lines

If a submission writes a output line larger than the stream buffer
size ( default 64k ) a LimitOverrunError will be raise. Rather than
using readline(...) use readutil(....) and in the case of a overrun
just return the current buffer, the rest of the line will be returned
with the next read.

Signed-off-by: Chris Harris <[email protected]>

* terms and condition check added

* one terms checkbox for all organization login buttons

* removed sandbox property from iframe to allow links in the iframe

* Detailed results title removed

* Detailed results configuration (#1374)

* competition model updated

* competition settings to allow participant to make submission public/private

* unwanted restriction removed

* detailed results now shown in submission panel and in leaderboard

* submission tests updated

---------

Signed-off-by: Chris Harris <[email protected]>
Co-authored-by: Ihsan Ullah <[email protected]>
Co-authored-by: Chris Harris <[email protected]>

* Revert "Merge develop into master (#1405)"

This reverts commit a26fc36.

* More general exception in views.py (#1512)

* More general exception in views.py

* Update views.py

* Update version.json for release 1.15.0

---------

Signed-off-by: Chris Harris <[email protected]>
Co-authored-by: Adrien Pavão <[email protected]>
Co-authored-by: Ihsan Ullah <[email protected]>
Co-authored-by: Ihsan Ullah <[email protected]>
Co-authored-by: Benjamin Bearce <[email protected]>
Co-authored-by: Chris Harris <[email protected]>
Co-authored-by: Nicolas Homberg <[email protected]>
Co-authored-by: GitHub Actions <[email protected]>

* Feature/remove user/soft removal (#1691) (#1716)

* add soft deletions attributes in profile class + override deletion method + update email and log in mechanism

* add emails template + soft delete + account view + deletion confirmation view

* move the notice emails in the delete method of user

* filter deleted users out of the front page stat

* disable the action buttons on the list of participants modal in the competition management view

Co-authored-by: Tristan Mary <[email protected]>

* Updated the filters to show the new "Is Deleted" (#1717)

* Updated the filters to show the new "Is Deleted"

* Flake8 fixes

---------

Co-authored-by: Obada Haddad <[email protected]>

* .gitingore update to ignore the home page counters file. Also removed the file from cache so that it's not tracked anymore

* Fix/remove user/soft removal (#1724)

* Set is_active to False when deleting a user

* change the email sending method and populate the missing txt files

* add a checkbox to show/hide deleted users in the list of participant modal

* Fixed the wrong user being named in the greetings in the email sent to admins upon account deletion

* Flake8 fixed

* restricted the usage of deletion link more than one time, used a different deletion token to expire it after deletion, account deletion modal now disappears after clickin the delete my account button

---------

Co-authored-by: OhMaley <[email protected]>
Co-authored-by: Obada Haddad <[email protected]>
Co-authored-by: Ihsan Ullah <[email protected]>

* Fix URLs in user deletion email (#1729)

* Fix URLs in user deletion email

* Fix domain

---------

Signed-off-by: Chris Harris <[email protected]>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Ihsan Ullah <[email protected]>
Co-authored-by: Ihsan Ullah <[email protected]>
Co-authored-by: Benjamin Bearce <[email protected]>
Co-authored-by: Chris Harris <[email protected]>
Co-authored-by: Nicolas Homberg <[email protected]>
Co-authored-by: GitHub Actions <[email protected]>
Co-authored-by: Tristan Mary <[email protected]>
Co-authored-by: Obada Haddad-Soussac <[email protected]>
Co-authored-by: Obada Haddad <[email protected]>
  • Loading branch information
11 people authored Jan 16, 2025
2 parents 32f11d9 + 30503a3 commit 8606b17
Show file tree
Hide file tree
Showing 26 changed files with 713 additions and 26 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,4 +37,6 @@ server_config.yaml
.DS_Store?

caddy_config/
caddy_data/
caddy_data/

home_page_counters.json
6 changes: 0 additions & 6 deletions home_page_counters.json

This file was deleted.

3 changes: 1 addition & 2 deletions src/apps/analytics/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -561,8 +561,7 @@ def update_home_page_counters():
public_competitions = Competition.objects.filter(published=True).count()

# Count active users
# TODO: do not count deleted users
users = User.objects.all().count()
users = User.objects.filter(is_deleted=False).count()

# Count all submissions
submissions = Submission.objects.all().count()
Expand Down
2 changes: 2 additions & 0 deletions src/apps/api/serializers/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ class CompetitionParticipantSerializer(serializers.ModelSerializer):
username = serializers.CharField(source='user.username')
is_bot = serializers.BooleanField(source='user.is_bot')
email = serializers.CharField(source='user.email')
is_deleted = serializers.BooleanField(source='user.is_deleted')

class Meta:
model = CompetitionParticipant
Expand All @@ -487,6 +488,7 @@ class Meta:
'is_bot',
'email',
'status',
'is_deleted',
)


Expand Down
3 changes: 3 additions & 0 deletions src/apps/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
path('delete_unused_submissions/', quota.delete_unused_submissions, name="delete_unused_submissions"),
path('delete_failed_submissions/', quota.delete_failed_submissions, name="delete_failed_submissions"),

# User account
path('delete_account/', profiles.delete_account, name="delete_account"),

# Analytics
path('analytics/storage_usage_history/', analytics.storage_usage_history, name='storage_usage_history'),
path('analytics/competitions_usage/', analytics.competitions_usage, name='competitions_usage'),
Expand Down
2 changes: 1 addition & 1 deletion src/apps/api/views/competitions.py
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,7 @@ class CompetitionParticipantViewSet(ModelViewSet):
queryset = CompetitionParticipant.objects.all()
serializer_class = CompetitionParticipantSerializer
filter_backends = (DjangoFilterBackend, SearchFilter)
filter_fields = ('user__username', 'user__email', 'status', 'competition')
filter_fields = ('user__username', 'user__email', 'status', 'competition', 'user__is_deleted')
search_fields = ('user__username', 'user__email',)

def get_queryset(self):
Expand Down
24 changes: 23 additions & 1 deletion src/apps/api/views/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.http import HttpResponse
from rest_framework.decorators import action
from rest_framework.decorators import action, api_view
from rest_framework.exceptions import ValidationError, PermissionDenied
from rest_framework.generics import GenericAPIView, RetrieveAPIView
from rest_framework import permissions, mixins
Expand All @@ -19,6 +19,7 @@
OrganizationSerializer, MembershipSerializer, SimpleOrganizationSerializer, DeleteMembershipSerializer
from profiles.helpers import send_mail
from profiles.models import Organization, Membership
from profiles.views import send_delete_account_confirmation_mail

User = get_user_model()

Expand Down Expand Up @@ -84,6 +85,27 @@ def _get_data(user):
)


@api_view(['DELETE'])
def delete_account(request):
# Check data
user = request.user
is_username_valid = user.username == request.data["username"]
is_password_valid = user.check_password(request.data["password"])

if is_username_valid and is_password_valid:
send_delete_account_confirmation_mail(request, user)

return Response({
"success": True,
"message": "A confirmation link has been sent to your email. Follow the instruction to finish the process"
})
else:
return Response({
"success": False,
"error": "Wrong username or password"
})


class OrganizationViewSet(mixins.CreateModelMixin,
mixins.UpdateModelMixin,
GenericViewSet):
Expand Down
14 changes: 13 additions & 1 deletion src/apps/competitions/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@


def get_organizer_emails(competition):
return [user.email for user in competition.all_organizers]
return [user.email for user in competition.all_organizers if not user.is_deleted]


def send_participation_requested_emails(participant):
if participant.user.is_deleted:
return

context = {
'participant': participant
}
Expand All @@ -29,6 +32,9 @@ def send_participation_requested_emails(participant):


def send_participation_accepted_emails(participant):
if participant.user.is_deleted:
return

context = {
'participant': participant
}
Expand All @@ -50,6 +56,9 @@ def send_participation_accepted_emails(participant):


def send_participation_denied_emails(participant):
if participant.user.is_deleted:
return

context = {
'participant': participant
}
Expand All @@ -72,6 +81,9 @@ def send_participation_denied_emails(participant):


def send_direct_participant_email(participant, content):
if participant.user.is_deleted:
return

codalab_send_markdown_email(
subject=f'A message from the admins of {participant.competition.title}',
markdown_content=content,
Expand Down
2 changes: 1 addition & 1 deletion src/apps/profiles/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class UserAdmin(admin.ModelAdmin):
change_form_template = "admin/auth/user/change_form.html"
change_list_template = "admin/auth/user/change_list.html"
search_fields = ['username', 'email']
list_filter = ['is_staff', 'is_superuser', 'deleted', 'is_bot']
list_filter = ['is_staff', 'is_superuser', 'deleted', 'is_deleted', 'is_bot']
list_display = ['username', 'email', 'is_staff', 'is_superuser']


Expand Down
23 changes: 23 additions & 0 deletions src/apps/profiles/migrations/0014_auto_20241120_1607.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Generated by Django 2.2.28 on 2024-11-20 16:07

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('profiles', '0013_auto_20240304_0616'),
]

operations = [
migrations.AddField(
model_name='user',
name='deleted_at',
field=models.DateTimeField(blank=True, null=True),
),
migrations.AddField(
model_name='user',
name='is_deleted',
field=models.BooleanField(default=False),
),
]
69 changes: 69 additions & 0 deletions src/apps/profiles/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,10 @@ class User(ChaHubSaveMixin, AbstractBaseUser, PermissionsMixin):
# Required for social auth and such to create users
objects = ChaHubUserManager()

# Soft deletion
is_deleted = models.BooleanField(default=False)
deleted_at = models.DateTimeField(null=True, blank=True)

def save(self, *args, **kwargs):
self.slug = slugify(self.username, allow_unicode=True)
super().save(*args, **kwargs)
Expand Down Expand Up @@ -193,6 +197,71 @@ def get_used_storage_space(self):

return storage_used

def delete(self, *args, **kwargs):
"""Soft delete the user and anonymize personal data."""
from .views import send_user_deletion_notice_to_admin, send_user_deletion_confirmed

# Send a notice to admins
send_user_deletion_notice_to_admin(self)

# Mark the user as deleted
self.is_deleted = True
self.deleted_at = now()
self.is_active = False

# Anonymize or removed personal data
user_email = self.email # keep track of the email for the end of the procedure

# Github related
self.github_uid = None
self.avatar_url = None
self.url = None
self.html_url = None
self.name = None
self.company = None
self.bio = None
if self.github_info:
self.github_info.login = None
self.github_info.avatar_url = None
self.github_info.gravatar_id = None
self.github_info.html_url = None
self.github_info.name = None
self.github_info.company = None
self.github_info.bio = None
self.github_info.location = None

# Any user attribute
self.username = f"deleted_user_{self.id}"
self.slug = f"deleted_slug_{self.id}"
self.photo = None
self.email = None
self.display_name = None
self.first_name = None
self.last_name = None
self.title = None
self.location = None
self.biography = None
self.personal_url = None
self.linkedin_url = None
self.twitter_url = None
self.github_url = None

# Queues
self.rabbitmq_username = None
self.rabbitmq_password = None

# Save the changes
self.save()

# Send a confirmation email notice to the removed user
send_user_deletion_confirmed(user_email)

def restore(self, *args, **kwargs):
"""Restore a soft-deleted user. Note that personal data remains anonymized."""
self.is_deleted = False
self.deleted_at = None
self.save()


class GithubUserInfo(models.Model):
# Required Info
Expand Down
18 changes: 17 additions & 1 deletion src/apps/profiles/tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,23 @@

class AccountActivationTokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
return (six.text_type(user.pk) + six.text_type(timestamp) + six.text_type(user.is_active))
return (
six.text_type(user.pk) +
six.text_type(timestamp) +
six.text_type(user.is_active)
)


account_activation_token = AccountActivationTokenGenerator()


class AccountDeletionTokenGenerator(PasswordResetTokenGenerator):
def _make_hash_value(self, user, timestamp):
return (
six.text_type(user.pk) +
six.text_type(timestamp) +
six.text_type(user.is_deleted)
)


account_deletion_token = AccountDeletionTokenGenerator()
2 changes: 2 additions & 0 deletions src/apps/profiles/urls_accounts.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,6 @@
path('password_reset/done/', auth_views.PasswordResetDoneView.as_view(), name='password_reset_done'),
path('reset/<uidb64>/<token>/', views.CustomPasswordResetConfirmView.as_view(), name='password_reset_confirm'),
path('reset/done/', auth_views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
path('user/<slug:username>/account/', views.UserAccountView.as_view(), name="user_account"),
path('delete/<uidb64>/<token>', views.delete, name='delete'),
]
Loading

0 comments on commit 8606b17

Please sign in to comment.