def post(self, request: Request, user, interface_id) -> Response: """ Enroll in authenticator interface ````````````````````````````````` :pparam string user_id: user id or "me" for current user :pparam string interface_id: interface id :auth: required """ if ratelimiter.is_limited( f"auth:authenticator-enroll:{request.user.id}:{interface_id}", limit=10, window=86400, # 10 per day should be fine ): return HttpResponse( "You have made too many authenticator enrollment attempts. Please try again later.", content_type="text/plain", status=429, ) # Using `request.user` here because superuser should not be able to set a user's 2fa # start activation serializer_cls = serializer_map.get(interface_id, None) if serializer_cls is None: return Response(status=status.HTTP_404_NOT_FOUND) serializer = serializer_cls(data=request.data) if not serializer.is_valid(): return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) interface = Authenticator.objects.get_interface( request.user, interface_id) # Not all interfaces allow multi enrollment # # This is probably un-needed because we catch # `Authenticator.AlreadyEnrolled` when attempting to enroll if interface.is_enrolled(): if interface.allow_multi_enrollment: interface.status = EnrollmentStatus.MULTI elif interface.allow_rotation_in_place: interface.status = EnrollmentStatus.ROTATION else: return Response(ALREADY_ENROLLED_ERR, status=status.HTTP_400_BAD_REQUEST) try: interface.secret = request.data["secret"] except KeyError: pass context = {} # Need to update interface with phone number before validating OTP if "phone" in request.data: interface.phone_number = serializer.data["phone"] # Disregarding value of 'otp', if no OTP was provided, # send text message to phone number with OTP if "otp" not in request.data: if interface.send_text(for_enrollment=True, request=request._request): return Response(status=status.HTTP_204_NO_CONTENT) else: # Error sending text message return Response( SEND_SMS_ERR, status=status.HTTP_500_INTERNAL_SERVER_ERROR) # Attempt to validate OTP if "otp" in request.data and not interface.validate_otp( serializer.data["otp"]): return Response(INVALID_OTP_ERR, status=status.HTTP_400_BAD_REQUEST) # Try u2f enrollment if interface_id == "u2f": # What happens when this fails? has_webauthn_register = self._check_can_webauthn_register(user) state = request.session[ "webauthn_register_state"] if has_webauthn_register else None interface.try_enroll( serializer.data["challenge"], serializer.data["response"], has_webauthn_register, serializer.data["deviceName"], state, ) context.update({"device_name": serializer.data["deviceName"]}) if interface.status == EnrollmentStatus.ROTATION: interface.rotate_in_place() else: try: interface.enroll(request.user) except Authenticator.AlreadyEnrolled: return Response(ALREADY_ENROLLED_ERR, status=status.HTTP_400_BAD_REQUEST) context.update({"authenticator": interface.authenticator}) capture_security_activity( account=request.user, type="mfa-added", actor=request.user, ip_address=request.META["REMOTE_ADDR"], context=context, send_email=True, ) request.user.clear_lost_passwords() request.user.refresh_session_nonce(self.request) request.user.save() Authenticator.objects.auto_add_recovery_codes(request.user) response = Response(status=status.HTTP_204_NO_CONTENT) # If there is a pending organization invite accept after the # authenticator has been configured. invite_helper = ApiInviteHelper.from_cookie(request=request, instance=self, logger=logger) if invite_helper and invite_helper.valid_request: invite_helper.accept_invite() remove_invite_cookie(request, response) return response
def handle_basic_auth(self, request, **kwargs): can_register = self.can_register(request) op = request.POST.get("op") organization = kwargs.pop("organization", None) if not op: # Detect that we are on the register page by url /register/ and # then activate the register tab by default. if "/register" in request.path_info and can_register: op = "register" elif request.GET.get("op") == "sso": op = "sso" login_form = self.get_login_form(request) if can_register: register_form = self.get_register_form( request, initial={"username": request.session.get("invite_email", "")} ) else: register_form = None if can_register and register_form.is_valid(): user = register_form.save() user.send_confirm_emails(is_new_user=True) # HACK: grab whatever the first backend is and assume it works user.backend = settings.AUTHENTICATION_BACKENDS[0] auth.login(request, user, organization_id=organization.id if organization else None) # can_register should only allow a single registration request.session.pop("can_register", None) request.session.pop("invite_email", None) # Attempt to directly accept any pending invites invite_helper = ApiInviteHelper.from_cookie(request=request, instance=self) if invite_helper and invite_helper.valid_request: invite_helper.accept_invite() response = self.redirect_to_org(request) remove_invite_cookie(request, response) return response return self.redirect(auth.get_login_redirect(request)) elif request.method == "POST": from sentry.app import ratelimiter from sentry.utils.hashlib import md5_text login_attempt = ( op == "login" and request.POST.get("username") and request.POST.get("password") ) if login_attempt and ratelimiter.is_limited( u"auth:login:username:{}".format( md5_text(request.POST["username"].lower()).hexdigest() ), limit=10, window=60, # 10 per minute should be enough for anyone ): login_form.errors["__all__"] = [ u"You have made too many login attempts. Please try again later." ] metrics.incr( "login.attempt", instance="rate_limited", skip_internal=True, sample_rate=1.0 ) elif login_form.is_valid(): user = login_form.get_user() auth.login(request, user, organization_id=organization.id if organization else None) metrics.incr( "login.attempt", instance="success", skip_internal=True, sample_rate=1.0 ) if not user.is_active: return self.redirect(reverse("sentry-reactivate-account")) return self.redirect(auth.get_login_redirect(request)) else: metrics.incr( "login.attempt", instance="failure", skip_internal=True, sample_rate=1.0 ) context = { "op": op or "login", "server_hostname": get_server_hostname(), "login_form": login_form, "organization": organization, "register_form": register_form, "CAN_REGISTER": can_register, "join_request_link": self.get_join_request_link(organization), } context.update(additional_context.run_callbacks(request)) return self.respond_login(request, context, **kwargs)