Skip to content

Commit

Permalink
handle archived users in ui
Browse files Browse the repository at this point in the history
- show user archived page on welcome
- avoid resending activation link if user is archived
- avoid deleting archived users
  • Loading branch information
elelay committed Jan 20, 2025
1 parent d477b96 commit 81217c3
Show file tree
Hide file tree
Showing 7 changed files with 142 additions and 19 deletions.
11 changes: 5 additions & 6 deletions mygpo/administration/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,16 +365,15 @@ def post(self, request):
return HttpResponseRedirect(reverse("admin-resend-activation"))

if user.is_active:
messages.success(request, "User {username} is already activated")

messages.success(request, _("User {username} is already activated").format(username=username))
elif user.profile.archived_date is not None:
messages.error(self.request, _("User {username} data has been archived.").format(username=username))
else:
send_activation_email(user, request)
messages.success(
request,
_(
"Email for {username} ({email}) resent".format(
username=user.username, email=user.email
)
_("Email for {username} ({email}) resent").format(
username=user.username, email=user.email
),
)

Expand Down
2 changes: 1 addition & 1 deletion mygpo/users/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def remove_inactive_users():
remove_before = datetime.utcnow() - timedelta(days=valid_days)
logger.warning("Removing unactivated users before %s", remove_before)

users = User.objects.filter(is_active=False, date_joined__lt=remove_before)
users = User.objects.filter(is_active=False, date_joined__lt=remove_before, profile__archived_date__isnull=True)

for user in users:
clients = models.Client.objects.filter(user=user)
Expand Down
2 changes: 2 additions & 0 deletions mygpo/users/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,4 +92,6 @@
name="login-google-callback",
),
path("logout/", LogoutView.as_view(next_page="home"), name="logout"),
path("archived/", user.ArchivedView.as_view(), name="user-archived"),
path("archived/download", user.download_archive, name="user-download-archive"),
]
11 changes: 9 additions & 2 deletions mygpo/users/views/registration.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def get(self, request, activation_key):

try:
user = UserProxy.objects.get(
profile__activation_key=activation_key, is_active=False
profile__activation_key=activation_key, is_active=False, profile__archived_date__isnull=True
)
except UserProxy.DoesNotExist:
messages.error(
Expand Down Expand Up @@ -186,7 +186,7 @@ class ResendActivationView(FormView):
success_url = reverse_lazy("resent-activation")

def form_valid(self, form):
"""called whene the form was POSTed and its contents were valid"""
"""called when the form was POSTed and its contents were valid"""

try:
user = UserProxy.objects.all().by_username_or_email(
Expand All @@ -197,6 +197,13 @@ def form_valid(self, form):
messages.error(self.request, _("User does not exist."))
return HttpResponseRedirect(reverse("resend-activation"))

if user.profile.archived_date is not None:
messages.error(self.request, _("Your data has been archived."))
user_archived_page = "{page}?user={username}".format(
page=reverse("user-archived"), username=user.username
)
return HttpResponseRedirect(user_archived_page)

if user.profile.activation_key is None:
messages.success(
self.request,
Expand Down
69 changes: 59 additions & 10 deletions mygpo/users/views/user.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import os
import string
import random

from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.http import FileResponse, HttpResponseRedirect, HttpResponseBadRequest, HttpResponseNotFound
from django.contrib.auth import authenticate, get_user_model
from django.contrib.auth.decorators import login_required
from django.contrib import messages
Expand Down Expand Up @@ -86,15 +87,21 @@ def post(self, request):
return HttpResponseRedirect(login_page)

if not user.is_active:
send_activation_email(user, request)
messages.error(
request,
_(
"Please activate your account first. "
"We have just re-sent your activation email"
),
)
return HttpResponseRedirect(login_page)
if user.profile.archived_date is not None:
user_archived_page = "{page}?user={username}".format(
page=reverse("user-archived"), username=username
)
return HttpResponseRedirect(user_archived_page)
else:
send_activation_email(user, request)
messages.error(
request,
_(
"Please activate your account first. "
"We have just re-sent your activation email"
),
)
return HttpResponseRedirect(login_page)

# set up the user's session
login(request, user)
Expand Down Expand Up @@ -236,3 +243,45 @@ def _get_email(self, response):
headers = {"Authorization": "Bearer " + response["access_token"]}
resp = requests.get(USERINFO_URL, headers=headers).json()
return resp["data"]["email"]


class ArchivedView(View):
"""View to login a user"""

def get(self, request):
"""Shows the info page"""

# Do not show this page for already-logged-in users
if request.user.is_authenticated:
# return HttpResponseRedirect(DEFAULT_LOGIN_REDIRECT)
raise Exception("coucou")

return render(
request,
"user_archived.html",
{"username": request.GET.get("user", "")},
)


@never_cache
def download_archive(request):
""" download a user's archive.
Requires the username and password to match the user's.
A check for correct folder is done to prevent arbitrary file exfiltration in case of database corruption.
It will only allow from within the ARCHIVE_ROOT (test will be adjusted later if necessary).
"""
user = authenticate(username=request.POST.get("user"), password=request.POST.get("pwd"))

if not user:
return HttpResponseNotFound("Invalid user or password")

if not user.is_active and user.profile.archive_path is not None:
archive_path = os.path.abspath(os.path.normpath(user.profile.archive_path))
if os.path.dirname(archive_path) == settings.ARCHIVE_ROOT:
return FileResponse(open(archive_path, "rb"), as_attachment=True)
return HttpResponseBadRequest("Invalid archive path")

return HttpResponseBadRequest(
"Invalid user state"
)
64 changes: 64 additions & 0 deletions mygpo/web/templates/user_archived.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{% extends "base.html" %}
{% load i18n %}

{% load menu %}
{% block mainmenu %}{{ "/user_archived/"|main_menu }}{% endblock %}
{% block sectionmenu %}{{ "/user_archived/"|section_menu }}{% endblock %}

{% block title %}{% trans "Archived User" %}{% endblock %}

{% block header %}
<h1>{% trans "Archived User" %}</h1>

{% if next %}
<div class="alert alert-warning">
{% trans "Due to over a year inactivity your account has been archived." %}
</div>
{% endif %}

{% endblock %}


{% block content %}

<p>Dear <b>{{ username }}</b>, due to resource restrictions on gpodder.net it is no
longer (as of October 2024) feasible to keep all data.</p>

<p>We have decided to first archive inactive user (no login for 6 months) data.
This reduces required capacity by a factor of 3.</p>

<p>We understand this is an inconvenience for you but this is required to keep
running this free service.</p>

<p>We prepared an archive of your user data and all derived data at the time,
to the best of our abilities. It contains all your subscriptions, history,
as well as related podcast and episode information.</p>

<p>For instance you'll find all your subscriptions in OPML format in the <code>subscriptions.opml</code> file
of the archive. This format is used to import podcast subscriptions in a podcatcher, like gPodder desktop
or AntennaPod.</p>

<p>Additional data is in json format in various files of the archive.</p>

<p>The archive doesn't include podcast or episode cover art, as it would make it too
large to manage.</p>

<p>To get access to your archive, please enter your password and click on <i>Download</i></p>

<form role="form" class="col-lg-offset-3 col-lg-6" action="{% url "user-download-archive" %}" method="post">

{% csrf_token %}

<input type="hidden" name="user" value="{{ username }}" />

<div class="form-group">
<label class="control-label">Password</label>
<input class="form-control" type="password" name="pwd" />
</div>

<div class="form-group">
<button class="btn btn-primary" type="submit">{% trans "Download" %}</button>
</div>

</form>
{% endblock %}
2 changes: 2 additions & 0 deletions mygpo/web/templatetags/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"/user/subscriptions/",
"/publisher/podcast/",
"/share/me",
"/user_archived/",
)

MENU_STRUCTURE = (
Expand All @@ -23,6 +24,7 @@
("/", _("Home")),
("/login/", _("Login")),
("/register/", _("Register")),
("/user_archived/", _("Archived")),
("", _("Docs")),
("/contribute/", _("Contribute")),
("/developer/", _("Development")),
Expand Down

0 comments on commit 81217c3

Please sign in to comment.