Browse Source

Revert "feat(api): Add user registration endpoint (#26117)"

This reverts commit 23988acbfc5cb7e20b634a4e494d37c01011177a.
David Cramer 3 years ago
parent
commit
8382802ee7

+ 1 - 20
src/sentry/api/authentication.py

@@ -2,11 +2,7 @@ from django.conf import settings
 from django.contrib.auth.models import AnonymousUser
 from django.utils.crypto import constant_time_compare
 from django.utils.encoding import force_text
-from rest_framework.authentication import (
-    BasicAuthentication,
-    SessionAuthentication,
-    get_authorization_header,
-)
+from rest_framework.authentication import BasicAuthentication, get_authorization_header
 from rest_framework.exceptions import AuthenticationFailed
 from sentry_relay import UnpackError
 
@@ -187,18 +183,3 @@ class DSNAuthentication(StandardAuthentication):
             scope.set_tag("api_project_key", key.id)
 
         return (AnonymousUser(), key)
-
-
-class ImprovedSessionAuthentication(SessionAuthentication):
-    """
-    Identical to SesssionAuthentication but it forces CSRF even on anonymous requests.
-    """
-
-    def authenticate(self, request):
-        rv = super().authenticate(request)
-        if rv:
-            return rv
-
-        # if we didnt return a user, it means we never ran CSRF checks, so force them now
-        self.enforce_csrf(request)
-        return None

+ 3 - 2
src/sentry/api/base.py

@@ -9,6 +9,7 @@ from django.http import HttpResponse
 from django.utils.http import urlquote
 from django.views.decorators.csrf import csrf_exempt
 from pytz import utc
+from rest_framework.authentication import SessionAuthentication
 from rest_framework.exceptions import ParseError
 from rest_framework.response import Response
 from rest_framework.views import APIView
@@ -23,7 +24,7 @@ from sentry.utils.dates import to_datetime
 from sentry.utils.http import absolute_uri, is_valid_origin, origin_from_request
 from sentry.utils.sdk import capture_exception
 
-from .authentication import ApiKeyAuthentication, ImprovedSessionAuthentication, TokenAuthentication
+from .authentication import ApiKeyAuthentication, TokenAuthentication
 from .paginator import BadPaginationError, Paginator
 from .permissions import NoPermission
 
@@ -35,7 +36,7 @@ ONE_DAY = ONE_HOUR * 24
 
 LINK_HEADER = '<{uri}&cursor={cursor}>; rel="{name}"; results="{has_results}"; cursor="{cursor}"'
 
-DEFAULT_AUTHENTICATION = (TokenAuthentication, ApiKeyAuthentication, ImprovedSessionAuthentication)
+DEFAULT_AUTHENTICATION = (TokenAuthentication, ApiKeyAuthentication, SessionAuthentication)
 
 logger = logging.getLogger(__name__)
 audit_logger = logging.getLogger("sentry.audit.api")

+ 2 - 3
src/sentry/api/endpoints/accept_project_transfer.py

@@ -5,8 +5,7 @@ from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 
 from sentry import roles
-from sentry.api.authentication import ImprovedSessionAuthentication
-from sentry.api.base import Endpoint
+from sentry.api.base import Endpoint, SessionAuthentication
 from sentry.api.decorators import sudo_required
 from sentry.api.serializers import serialize
 from sentry.api.serializers.models.organization import (
@@ -28,7 +27,7 @@ class InvalidPayload(Exception):
 
 
 class AcceptProjectTransferEndpoint(Endpoint):
-    authentication_classes = (ImprovedSessionAuthentication,)
+    authentication_classes = (SessionAuthentication,)
     permission_classes = (IsAuthenticated,)
 
     def get_validated_data(self, data, user):

+ 2 - 3
src/sentry/api/endpoints/api_application_details.py

@@ -5,8 +5,7 @@ from rest_framework import serializers
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 
-from sentry.api.authentication import ImprovedSessionAuthentication
-from sentry.api.base import Endpoint
+from sentry.api.base import Endpoint, SessionAuthentication
 from sentry.api.exceptions import ResourceDoesNotExist
 from sentry.api.serializers import serialize
 from sentry.api.serializers.rest_framework import ListField
@@ -36,7 +35,7 @@ class ApiApplicationSerializer(serializers.Serializer):
 
 
 class ApiApplicationDetailsEndpoint(Endpoint):
-    authentication_classes = (ImprovedSessionAuthentication,)
+    authentication_classes = (SessionAuthentication,)
     permission_classes = (IsAuthenticated,)
 
     def get(self, request, app_id):

+ 2 - 3
src/sentry/api/endpoints/api_applications.py

@@ -1,15 +1,14 @@
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 
-from sentry.api.authentication import ImprovedSessionAuthentication
-from sentry.api.base import Endpoint
+from sentry.api.base import Endpoint, SessionAuthentication
 from sentry.api.paginator import OffsetPaginator
 from sentry.api.serializers import serialize
 from sentry.models import ApiApplication, ApiApplicationStatus
 
 
 class ApiApplicationsEndpoint(Endpoint):
-    authentication_classes = (ImprovedSessionAuthentication,)
+    authentication_classes = (SessionAuthentication,)
     permission_classes = (IsAuthenticated,)
 
     def get(self, request):

+ 2 - 3
src/sentry/api/endpoints/api_authorizations.py

@@ -2,15 +2,14 @@ from django.db import transaction
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 
-from sentry.api.authentication import ImprovedSessionAuthentication
-from sentry.api.base import Endpoint
+from sentry.api.base import Endpoint, SessionAuthentication
 from sentry.api.paginator import OffsetPaginator
 from sentry.api.serializers import serialize
 from sentry.models import ApiApplicationStatus, ApiAuthorization, ApiToken
 
 
 class ApiAuthorizationsEndpoint(Endpoint):
-    authentication_classes = (ImprovedSessionAuthentication,)
+    authentication_classes = (SessionAuthentication,)
     permission_classes = (IsAuthenticated,)
 
     def get(self, request):

+ 2 - 3
src/sentry/api/endpoints/api_tokens.py

@@ -3,8 +3,7 @@ from rest_framework import serializers
 from rest_framework.permissions import IsAuthenticated
 from rest_framework.response import Response
 
-from sentry.api.authentication import ImprovedSessionAuthentication
-from sentry.api.base import Endpoint
+from sentry.api.base import Endpoint, SessionAuthentication
 from sentry.api.fields import MultipleChoiceField
 from sentry.api.serializers import serialize
 from sentry.models import ApiToken
@@ -16,7 +15,7 @@ class ApiTokenSerializer(serializers.Serializer):
 
 
 class ApiTokensEndpoint(Endpoint):
-    authentication_classes = (ImprovedSessionAuthentication,)
+    authentication_classes = (SessionAuthentication,)
     permission_classes = (IsAuthenticated,)
 
     def get(self, request):

+ 3 - 2
src/sentry/api/endpoints/auth_index.py

@@ -1,9 +1,10 @@
 from django.contrib.auth import logout
 from django.contrib.auth.models import AnonymousUser
 from rest_framework import status
+from rest_framework.authentication import SessionAuthentication
 from rest_framework.response import Response
 
-from sentry.api.authentication import ImprovedSessionAuthentication, QuietBasicAuthentication
+from sentry.api.authentication import QuietBasicAuthentication
 from sentry.api.base import Endpoint
 from sentry.api.serializers import DetailedUserSerializer, serialize
 from sentry.api.validators import AuthVerifyValidator
@@ -21,7 +22,7 @@ class AuthIndexEndpoint(Endpoint):
     and simple HTTP authentication.
     """
 
-    authentication_classes = [QuietBasicAuthentication, ImprovedSessionAuthentication]
+    authentication_classes = [QuietBasicAuthentication, SessionAuthentication]
 
     permission_classes = ()
 

+ 20 - 1
src/sentry/api/endpoints/auth_login.py

@@ -3,13 +3,15 @@ from rest_framework.response import Response
 from sentry.api.base import Endpoint
 from sentry.api.serializers.base import serialize
 from sentry.api.serializers.models.user import DetailedUserSerializer
+from sentry.app import ratelimiter
 from sentry.utils import auth, metrics
+from sentry.utils.hashlib import md5_text
 from sentry.web.forms.accounts import AuthenticationForm
 from sentry.web.frontend.base import OrganizationMixin
 
 
 class AuthLoginEndpoint(Endpoint, OrganizationMixin):
-    # Disable permission requirements.
+    # Disable authentication and permission requirements.
     permission_classes = []
 
     def post(self, request, organization=None, *args, **kwargs):
@@ -19,6 +21,23 @@ class AuthLoginEndpoint(Endpoint, OrganizationMixin):
         """
         login_form = AuthenticationForm(request, request.data)
 
+        # Rate limit logins
+        is_limited = ratelimiter.is_limited(
+            "auth:login:username:{}".format(
+                md5_text(login_form.clean_username(request.data.get("username"))).hexdigest()
+            ),
+            limit=10,
+            window=60,  # 10 per minute should be enough for anyone
+        )
+
+        if is_limited:
+            errors = {"__all__": [login_form.error_messages["rate_limited"]]}
+            metrics.incr(
+                "login.attempt", instance="rate_limited", skip_internal=True, sample_rate=1.0
+            )
+
+            return self.respond_with_error(errors)
+
         if not login_form.is_valid():
             metrics.incr("login.attempt", instance="failure", skip_internal=True, sample_rate=1.0)
             return self.respond_with_error(login_form.errors)

+ 0 - 135
src/sentry/api/endpoints/auth_register.py

@@ -1,135 +0,0 @@
-import logging
-
-from django.conf import settings
-from django.core.exceptions import ValidationError as DjangoValidationError
-from django.db import transaction
-from rest_framework import serializers
-
-from sentry import newsletter, options
-from sentry.api.base import Endpoint
-from sentry.api.serializers.base import serialize
-from sentry.api.serializers.rest_framework.base import CamelSnakeSerializer
-from sentry.app import ratelimiter
-from sentry.auth import password_validation
-from sentry.models import User
-from sentry.signals import user_signup
-from sentry.utils import auth, metrics
-
-logger = logging.getLogger("sentry.auth")
-audit_logger = logging.getLogger("sentry.audit.ui")
-
-ERR_UNKNOWN = "An unknown error occurred while creating the account."
-
-
-class AuthRegisterSerializer(CamelSnakeSerializer):
-    name = serializers.CharField(max_length=30, required=True)
-    email = serializers.EmailField(required=True)
-    password = serializers.CharField(required=True)
-    subscribe = serializers.BooleanField(required=False)
-    referrer = serializers.CharField(required=False)
-
-    def is_rate_limited(self):
-        limit = options.get("auth.ip-rate-limit")
-        if not limit:
-            return False
-
-        ip_address = self.context["request"].META["REMOTE_ADDR"]
-        return ratelimiter.is_limited(f"auth:ip:{ip_address}", limit)
-
-    def validate_email(self, value):
-        value = value.strip()
-        if not value:
-            return
-        if User.objects.filter(username__iexact=value).exists():
-            raise serializers.ValidationError(
-                "An account is already registered with that email address."
-            )
-        return value.lower()
-
-    def validate_password(self, value):
-        try:
-            password_validation.validate_password(value)
-        except DjangoValidationError as error:
-            raise serializers.ValidationError(error.messages[0])
-        return value
-
-    def validate(self, data):
-        if self.is_rate_limited():
-            raise serializers.ValidationError("Rate limit exceeded")
-
-        return data
-
-    def create(self, data) -> User:
-        request = self.context["request"]
-
-        user = User(username=data["email"], email=data["email"], name=data["name"])
-        user.set_password(data["password"])
-        user.save()
-
-        # Authenticate user into the current request context
-        #
-        # XXX: This is a side-effect, which is maybe a little confusing as
-        #      creation of an account using this serializer will also
-        #      authenticate the created user.
-        user.backend = settings.AUTHENTICATION_BACKENDS[0]
-        assert auth.login(request, user)
-
-        if newsletter.is_enabled() and data.get("subscribe"):
-            newsletter.create_or_update_subscriptions(
-                user, list_ids=newsletter.get_default_list_ids()
-            )
-
-        for email in user.emails.filter(is_verified=False):
-            user.send_confirm_email_singular(email=email, is_new_user=True)
-
-        user_signup.send_robust(sender=self, user=user, source="api", referrer=data.get("referrer"))
-
-        # can_register should only allow a single registration
-        request.session.pop("can_register", None)
-
-        return user
-
-
-class AuthRegisterEndpoint(Endpoint):
-    # Disable permission requirements.
-    permission_classes = []
-
-    def can_register(self, request):
-        return bool(auth.has_user_registration() or request.session.get("can_register"))
-
-    def post(self, request):
-        if not self.can_register(request):
-            return self.respond({"details": "Registration is not allowed"}, status=400)
-
-        if request.user.is_authenticated():
-            # Authentication not allowed when a user is currently signed in
-            # 409 Conflict
-            metrics.incr("auth-register.failure", tags={"response_code": 409}, skip_internal=False)
-            return self.respond({"details": "Cannot register while authenticated"}, status=409)
-
-        serializer = AuthRegisterSerializer(data=request.data, context={"request": request})
-
-        if not serializer.is_valid():
-            # collect more specific error message
-            for field in serializer.errors:
-                metrics.incr(
-                    "auth-register.failure",
-                    tags={"response_code": 400, "type": "serializer-error", "subtype": field},
-                    skip_internal=False,
-                )
-
-            return self.respond(serializer.errors, status=400)
-
-        try:
-            with transaction.atomic():
-                user = serializer.save()
-        except Exception as e:
-            logger.error(repr(e), extra={"request": request}, exc_info=True)
-            metrics.incr(
-                "auth-register.failure",
-                tags={"response_code": 400, "type": "unknown-billing-error"},
-                skip_internal=False,
-            )
-            return self.respond({"details": ERR_UNKNOWN}, status=400)
-
-        return self.respond(serialize(user))

Some files were not shown because too many files changed in this diff