2020-05-18 10:03:30 +00:00
|
|
|
import json
|
|
|
|
|
|
|
|
from django import http
|
|
|
|
from django.contrib import auth
|
|
|
|
from django.middleware import csrf
|
|
|
|
|
2017-06-23 21:00:42 +00:00
|
|
|
from allauth.account.adapter import get_adapter
|
2019-09-17 09:23:59 +00:00
|
|
|
from rest_auth import views as rest_auth_views
|
|
|
|
from rest_auth.registration import views as registration_views
|
2020-05-18 10:03:30 +00:00
|
|
|
from rest_framework import mixins
|
|
|
|
from rest_framework import viewsets
|
2019-01-11 12:33:35 +00:00
|
|
|
from rest_framework.decorators import action
|
2018-06-10 08:55:16 +00:00
|
|
|
from rest_framework.response import Response
|
2017-06-23 21:00:42 +00:00
|
|
|
|
2022-10-09 12:56:03 +00:00
|
|
|
from drf_spectacular.utils import extend_schema, extend_schema_view
|
2022-09-25 13:57:22 +00:00
|
|
|
|
2020-04-20 13:42:29 +00:00
|
|
|
from funkwhale_api.common import authentication
|
2018-05-09 20:18:33 +00:00
|
|
|
from funkwhale_api.common import preferences
|
2020-05-18 10:03:30 +00:00
|
|
|
from funkwhale_api.common import throttling
|
2018-05-09 20:18:33 +00:00
|
|
|
|
2019-09-21 14:20:49 +00:00
|
|
|
from . import models, serializers, tasks
|
2017-06-23 21:00:42 +00:00
|
|
|
|
|
|
|
|
2022-10-09 13:03:52 +00:00
|
|
|
@extend_schema_view(post=extend_schema(operation_id="register", methods=["post"]))
|
2019-09-17 09:23:59 +00:00
|
|
|
class RegisterView(registration_views.RegisterView):
|
2018-06-19 20:23:22 +00:00
|
|
|
serializer_class = serializers.RegisterSerializer
|
2019-03-25 16:02:51 +00:00
|
|
|
permission_classes = []
|
2019-09-17 09:23:59 +00:00
|
|
|
action = "signup"
|
|
|
|
throttling_scopes = {"signup": {"authenticated": "signup", "anonymous": "signup"}}
|
2018-06-19 20:23:22 +00:00
|
|
|
|
2017-06-23 21:00:42 +00:00
|
|
|
def create(self, request, *args, **kwargs):
|
2018-06-19 20:23:22 +00:00
|
|
|
invitation_code = request.data.get("invitation")
|
|
|
|
if not invitation_code and not self.is_open_for_signup(request):
|
2018-06-09 13:36:16 +00:00
|
|
|
r = {"detail": "Registration has been disabled"}
|
2017-06-23 21:00:42 +00:00
|
|
|
return Response(r, status=403)
|
|
|
|
return super().create(request, *args, **kwargs)
|
|
|
|
|
|
|
|
def is_open_for_signup(self, request):
|
|
|
|
return get_adapter().is_open_for_signup(request)
|
|
|
|
|
2020-04-20 13:42:29 +00:00
|
|
|
def perform_create(self, serializer):
|
|
|
|
user = super().perform_create(serializer)
|
|
|
|
if not user.is_active:
|
2021-06-17 15:55:12 +00:00
|
|
|
# manual approval, we need to send the confirmation e-mail by hand
|
2020-04-20 13:42:29 +00:00
|
|
|
authentication.send_email_confirmation(self.request, user)
|
|
|
|
return user
|
|
|
|
|
2017-06-23 21:00:42 +00:00
|
|
|
|
2022-10-09 13:03:52 +00:00
|
|
|
@extend_schema_view(post=extend_schema(operation_id="verify_email"))
|
2019-09-17 09:23:59 +00:00
|
|
|
class VerifyEmailView(registration_views.VerifyEmailView):
|
|
|
|
action = "verify-email"
|
|
|
|
|
|
|
|
|
2022-10-09 12:56:03 +00:00
|
|
|
@extend_schema_view(post=extend_schema(operation_id="change_password"))
|
2019-09-17 09:23:59 +00:00
|
|
|
class PasswordChangeView(rest_auth_views.PasswordChangeView):
|
|
|
|
action = "password-change"
|
|
|
|
|
|
|
|
|
2022-10-09 13:03:52 +00:00
|
|
|
@extend_schema_view(post=extend_schema(operation_id="reset_password"))
|
2019-09-17 09:23:59 +00:00
|
|
|
class PasswordResetView(rest_auth_views.PasswordResetView):
|
|
|
|
action = "password-reset"
|
|
|
|
|
|
|
|
|
2022-10-09 13:03:52 +00:00
|
|
|
@extend_schema_view(post=extend_schema(operation_id="confirm_password_reset"))
|
2019-09-17 09:23:59 +00:00
|
|
|
class PasswordResetConfirmView(rest_auth_views.PasswordResetConfirmView):
|
|
|
|
action = "password-reset-confirm"
|
|
|
|
|
|
|
|
|
2018-06-09 13:36:16 +00:00
|
|
|
class UserViewSet(mixins.UpdateModelMixin, viewsets.GenericViewSet):
|
2020-01-23 15:38:04 +00:00
|
|
|
queryset = models.User.objects.all().select_related("actor__attachment_icon")
|
2018-03-03 10:20:21 +00:00
|
|
|
serializer_class = serializers.UserWriteSerializer
|
2018-06-09 13:36:16 +00:00
|
|
|
lookup_field = "username"
|
2019-04-23 09:35:59 +00:00
|
|
|
lookup_value_regex = r"[a-zA-Z0-9-_.]+"
|
2019-03-25 16:02:51 +00:00
|
|
|
required_scope = "profile"
|
2017-06-23 21:00:42 +00:00
|
|
|
|
2022-09-25 15:02:21 +00:00
|
|
|
@extend_schema(operation_id="get_authenticated_user", methods=["get"])
|
|
|
|
@extend_schema(operation_id="delete_authenticated_user", methods=["delete"])
|
2019-09-21 14:20:49 +00:00
|
|
|
@action(methods=["get", "delete"], detail=False)
|
2017-06-23 21:00:42 +00:00
|
|
|
def me(self, request, *args, **kwargs):
|
2019-09-21 14:20:49 +00:00
|
|
|
"""Return information about the current user or delete it"""
|
|
|
|
if request.method.lower() == "delete":
|
|
|
|
serializer = serializers.UserDeleteSerializer(
|
|
|
|
request.user, data=request.data
|
|
|
|
)
|
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
tasks.delete_account.delay(user_id=request.user.pk)
|
|
|
|
# at this point, password is valid, we launch deletion
|
|
|
|
return Response(status=204)
|
2018-09-06 18:35:02 +00:00
|
|
|
serializer = serializers.MeSerializer(request.user)
|
2017-06-23 21:00:42 +00:00
|
|
|
return Response(serializer.data)
|
2018-03-03 10:20:21 +00:00
|
|
|
|
2022-09-25 15:02:21 +00:00
|
|
|
@extend_schema(operation_id="update_settings")
|
2020-07-05 09:22:31 +00:00
|
|
|
@action(methods=["post"], detail=False, url_name="settings", url_path="settings")
|
|
|
|
def set_settings(self, request, *args, **kwargs):
|
|
|
|
"""Return information about the current user or delete it"""
|
|
|
|
new_settings = request.data
|
|
|
|
request.user.set_settings(**new_settings)
|
|
|
|
return Response(request.user.settings)
|
|
|
|
|
2019-03-25 16:02:51 +00:00
|
|
|
@action(
|
|
|
|
methods=["get", "post", "delete"],
|
|
|
|
required_scope="security",
|
|
|
|
url_path="subsonic-token",
|
|
|
|
detail=True,
|
|
|
|
)
|
2018-05-09 20:18:33 +00:00
|
|
|
def subsonic_token(self, request, *args, **kwargs):
|
2018-06-09 13:36:16 +00:00
|
|
|
if not self.request.user.username == kwargs.get("username"):
|
2018-05-09 20:18:33 +00:00
|
|
|
return Response(status=403)
|
2018-06-09 13:36:16 +00:00
|
|
|
if not preferences.get("subsonic__enabled"):
|
2018-05-09 20:18:33 +00:00
|
|
|
return Response(status=405)
|
2018-06-09 13:36:16 +00:00
|
|
|
if request.method.lower() == "get":
|
|
|
|
return Response(
|
|
|
|
{"subsonic_api_token": self.request.user.subsonic_api_token}
|
|
|
|
)
|
|
|
|
if request.method.lower() == "delete":
|
2018-05-09 20:18:33 +00:00
|
|
|
self.request.user.subsonic_api_token = None
|
2018-06-09 13:36:16 +00:00
|
|
|
self.request.user.save(update_fields=["subsonic_api_token"])
|
2018-05-09 20:18:33 +00:00
|
|
|
return Response(status=204)
|
|
|
|
self.request.user.update_subsonic_api_token()
|
2018-06-09 13:36:16 +00:00
|
|
|
self.request.user.save(update_fields=["subsonic_api_token"])
|
|
|
|
data = {"subsonic_api_token": self.request.user.subsonic_api_token}
|
2018-05-09 20:18:33 +00:00
|
|
|
return Response(data)
|
|
|
|
|
2022-09-28 17:53:49 +00:00
|
|
|
@extend_schema(operation_id="change_email", responses={200: None, 403: None})
|
2020-08-02 14:29:58 +00:00
|
|
|
@action(
|
|
|
|
methods=["post"],
|
|
|
|
required_scope="security",
|
|
|
|
url_path="change-email",
|
|
|
|
detail=False,
|
|
|
|
)
|
|
|
|
def change_email(self, request, *args, **kwargs):
|
|
|
|
if not self.request.user.is_authenticated:
|
|
|
|
return Response(status=403)
|
|
|
|
serializer = serializers.UserChangeEmailSerializer(
|
|
|
|
request.user, data=request.data, context={"user": request.user}
|
|
|
|
)
|
|
|
|
serializer.is_valid(raise_exception=True)
|
|
|
|
serializer.save(request)
|
|
|
|
return Response(status=204)
|
|
|
|
|
2018-03-03 10:20:21 +00:00
|
|
|
def update(self, request, *args, **kwargs):
|
2018-06-09 13:36:16 +00:00
|
|
|
if not self.request.user.username == kwargs.get("username"):
|
2018-03-03 10:20:21 +00:00
|
|
|
return Response(status=403)
|
|
|
|
return super().update(request, *args, **kwargs)
|
|
|
|
|
|
|
|
def partial_update(self, request, *args, **kwargs):
|
2018-06-09 13:36:16 +00:00
|
|
|
if not self.request.user.username == kwargs.get("username"):
|
2018-03-03 10:20:21 +00:00
|
|
|
return Response(status=403)
|
|
|
|
return super().partial_update(request, *args, **kwargs)
|
2020-05-18 10:03:30 +00:00
|
|
|
|
|
|
|
|
2022-09-25 15:02:21 +00:00
|
|
|
@extend_schema(operation_id="login")
|
|
|
|
@action(methods=["post"], detail=False)
|
2020-05-18 10:03:30 +00:00
|
|
|
def login(request):
|
|
|
|
throttling.check_request(request, "login")
|
|
|
|
if request.method != "POST":
|
|
|
|
return http.HttpResponse(status=405)
|
|
|
|
serializer = serializers.LoginSerializer(
|
|
|
|
data=request.POST, context={"request": request}
|
|
|
|
)
|
|
|
|
if not serializer.is_valid():
|
|
|
|
return http.HttpResponse(
|
|
|
|
json.dumps(serializer.errors), status=400, content_type="application/json"
|
|
|
|
)
|
|
|
|
serializer.save(request)
|
|
|
|
csrf.rotate_token(request)
|
2020-07-03 09:35:11 +00:00
|
|
|
token = csrf.get_token(request)
|
|
|
|
response = http.HttpResponse(status=200)
|
|
|
|
response.set_cookie("csrftoken", token, max_age=None)
|
|
|
|
return response
|
2020-05-18 10:03:30 +00:00
|
|
|
|
|
|
|
|
2022-09-25 15:02:21 +00:00
|
|
|
@extend_schema(operation_id="logout")
|
|
|
|
@action(methods=["post"], detail=False)
|
2020-05-18 10:03:30 +00:00
|
|
|
def logout(request):
|
|
|
|
if request.method != "POST":
|
|
|
|
return http.HttpResponse(status=405)
|
|
|
|
auth.logout(request)
|
2020-07-03 09:35:11 +00:00
|
|
|
token = csrf.get_token(request)
|
|
|
|
response = http.HttpResponse(status=200)
|
|
|
|
response.set_cookie("csrftoken", token, max_age=None)
|
|
|
|
return response
|