Exemple #1
0
def for_organization_member_invite(organization, email, user=None, auth=None):
    """
    Rate limit logic for triggering a user invite email, which should also be applied for generating
    a brand new member invite when possible.
    """
    if not features.has("organizations:invite-members-rate-limits", organization, actor=user):
        return False

    limits = (
        ratelimiter.is_limited(
            u"members:invite-by-user:{}".format(
                md5_text(user.id if user and user.is_authenticated() else six.text_type(auth))
            ),
            # 100 invites from a user per day
            limit=100,
            window=3600 * 24,
        )
        if (user or auth)
        else None,
        ratelimiter.is_limited(
            u"members:invite-by-org:{}".format(md5_text(organization.id)),
            # 100 invites from an org per day
            limit=100,
            window=3600 * 24,
        ),
        ratelimiter.is_limited(
            u"members:org-invite-to-email:{}-{}".format(
                organization.id, md5_text(email.lower()).hexdigest()
            ),
            # 10 invites per email per org per day
            limit=10,
            window=3600 * 24,
        ),
    )
    return any(limits)
Exemple #2
0
def for_organization_member_invite(organization,
                                   email,
                                   user=None,
                                   auth=None,
                                   config=None):
    """
    Rate limit logic for triggering a user invite email, which should also be applied for generating
    a brand new member invite when possible.
    """
    if config is None:
        config = DEFAULT_CONFIG

    if not features.has("organizations:invite-members-rate-limits",
                        organization,
                        actor=user):
        return False

    limits = (
        ratelimiter.is_limited(
            u"members:invite-by-user:{}".format(
                md5_text(user.id if user and user.is_authenticated() else six.
                         text_type(auth)).hexdigest()), **
            config["members:invite-by-user"]) if (user or auth) else None,
        ratelimiter.is_limited(
            u"members:invite-by-org:{}".format(
                md5_text(organization.id).hexdigest()),
            **config["members:invite-by-org"]),
        ratelimiter.is_limited(
            u"members:org-invite-to-email:{}-{}".format(
                organization.id,
                md5_text(email.lower()).hexdigest()),
            **config["members:org-invite-to-email"]),
    )
    return any(limits)
Exemple #3
0
def start_confirm_email(request):
    from sentry.app import ratelimiter

    if ratelimiter.is_limited(
            'auth:confirm-email:{}'.format(request.user.id),
            limit=10,
            window=60,  # 10 per minute should be enough for anyone
    ):
        return HttpResponse(
            'You have made too many email confirmation requests. Please try again later.',
            content_type='text/plain',
            status=429,
        )

    has_unverified_emails = request.user.has_unverified_emails()
    if has_unverified_emails:
        request.user.send_confirm_emails()
        unverified_emails = [
            e.email for e in request.user.get_unverified_emails()
        ]
        msg = _('A verification email has been sent to %s.') % (
            ', ').join(unverified_emails)
    else:
        msg = _(
            'Your email (%s) has already been verified.') % request.user.email
    messages.add_message(request, messages.SUCCESS, msg)
    return HttpResponseRedirect(reverse('sentry-account-settings-emails'))
Exemple #4
0
    def post(self, request, organization):
        assignment = experiments.get(org=organization,
                                     experiment_name=JOIN_REQUEST_EXPERIMENT)
        if assignment != 1:
            return Response(status=403)

        if organization.get_option("sentry:join_requests") is False:
            return Response(
                {"detail": "Your organization does not allow join requests."},
                status=403)

        # users can already join organizations with SSO enabled without an invite
        # so no need to allow requests to join as well
        if AuthProvider.objects.filter(organization=organization).exists():
            return Response(status=403)

        ip_address = request.META["REMOTE_ADDR"]

        if ratelimiter.is_limited(
                u"org-join-request:ip:{}".format(ip_address),
                limit=5,
                window=60  # 5 per minute
        ):
            return Response({"detail": "Rate limit exceeded."}, status=429)

        serializer = JoinRequestSerializer(data=request.data)

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.validated_data
        email = result["email"]

        create_organization_join_request(organization, email, ip_address)
        return Response(status=204)
Exemple #5
0
def recover(request):
    from sentry.app import ratelimiter

    if request.method == 'POST' and ratelimiter.is_limited(
            'accounts:recover:{}'.format(request.META['REMOTE_ADDR']),
            limit=5,
            window=60,  # 5 per minute should be enough for anyone
    ):
        return HttpResponse(
            'You have made too many password recovery attempts. Please try again later.',
            content_type='text/plain',
            status=429,
        )

    form = RecoverPasswordForm(request.POST or None)
    if form.is_valid():
        password_hash = send_password_recovery_mail(request,
                                                    form.cleaned_data['user'])

        return render_to_response('sentry/account/recover/sent.html', {
            'email': password_hash.user.email,
        }, request)

    context = {
        'form': form,
    }
    return render_to_response('sentry/account/recover/index.html', context,
                              request)
Exemple #6
0
    def post_process(self, event, **kwargs):
        # TODO(dcramer): we currently only support authenticated events, as the
        # value of anonymous errors/crashes/etc is much less meaningful in the
        # context of Segment

        # we avoid instantiating interfaces here as they're only going to be
        # used if there's a User present
        user_interface = event.data.get('sentry.interfaces.User')
        if not user_interface:
            return

        # we currently only support errors
        if event.get_event_type() != 'error':
            return

        user_id = user_interface.get('id')

        if not user_id:
            return

        write_key = self.get_option('write_key', event.project)
        if not write_key:
            return

        rl_key = 'segment:{}'.format(md5_text(write_key).hexdigest())
        # limit segment to 50 requests/second
        if ratelimiter.is_limited(rl_key, limit=50, window=1):
            return

        payload = self.get_event_payload(event)
        session = http.build_session()
        session.post(self.endpoint, json=payload, auth=(write_key, ''))
Exemple #7
0
    def post(self, request, organization):
        ip_address = request.META["REMOTE_ADDR"]

        if ratelimiter.is_limited(
                u"org-join-request:ip:{}".format(ip_address),
                limit=5,
                window=60  # 5 per minute
        ):
            return Response({"detail": "Rate limit exceeded."}, status=429)

        serializer = JoinRequestSerializer(data=request.data)

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.validated_data
        email = result["email"]

        assignment = experiments.get(org=organization,
                                     experiment_name=JOIN_REQUEST_EXPERIMENT)
        if assignment != 1:
            return Response(status=403)

        create_organization_join_request(organization, email, ip_address)
        return Response(status=204)
Exemple #8
0
    def _is_ip_rate_limited(self):
        limit = options.get("auth.ip-rate-limit")
        if not limit:
            return False

        ip_address = self.request.META["REMOTE_ADDR"]
        return ratelimiter.is_limited("auth:ip:{}".format(ip_address), limit)
Exemple #9
0
    def post(self, request, organization=None, *args, **kwargs):
        """
        Process a login request via username/password. SSO login is handled
        elsewhere.
        """
        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)

        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 Response({
                "nextUri":
                "/auth/reactivate/",
                "user":
                serialize(user, user, DetailedUserSerializer()),
            })

        active_org = self.get_active_organization(request)
        redirect_url = auth.get_org_redirect_url(request, active_org)

        return Response({
            "nextUri":
            auth.get_login_redirect(request, redirect_url),
            "user":
            serialize(user, user, DetailedUserSerializer()),
        })
Exemple #10
0
def recover(request):
    from sentry.app import ratelimiter

    if request.method == 'POST' and ratelimiter.is_limited(
        'accounts:recover:{}'.format(request.META['REMOTE_ADDR']),
        limit=5, window=60,  # 5 per minute should be enough for anyone
    ):
        return HttpResponse(
            'You have made too many password recovery attempts. Please try again later.',
            content_type='text/plain',
            status=429,
        )

    form = RecoverPasswordForm(request.POST or None)
    if form.is_valid():
        password_hash = send_password_recovery_mail(request, form.cleaned_data['user'])

        return render_to_response('sentry/account/recover/sent.html', {
            'email': password_hash.user.email,
        }, request)

    context = {
        'form': form,
    }
    return render_to_response('sentry/account/recover/index.html', context, request)
Exemple #11
0
def start_confirm_email(request):
    from sentry.app import ratelimiter

    if ratelimiter.is_limited(
        'auth:confirm-email:{}'.format(request.user.id),
        limit=10, window=60,  # 10 per minute should be enough for anyone
    ):
        return HttpResponse(
            'You have made too many email confirmation requests. Please try again later.',
            content_type='text/plain',
            status=429,
        )

    if 'primary-email' in request.POST:
        email = request.POST.get('email')
        try:
            email_to_send = UserEmail.objects.get(user=request.user, email=email)
        except UserEmail.DoesNotExist:
            msg = _('There was an error confirming your email.')
            level = messages.ERROR
        else:
            request.user.send_confirm_email_singular(email_to_send)
            msg = _('A verification email has been sent to %s.') % (email)
            level = messages.SUCCESS
        messages.add_message(request, level, msg)
        return HttpResponseRedirect(reverse('sentry-account-settings'))
    elif request.user.has_unverified_emails():
        request.user.send_confirm_emails()
        unverified_emails = [e.email for e in request.user.get_unverified_emails()]
        msg = _('A verification email has been sent to %s.') % (', ').join(unverified_emails)
    else:
        msg = _('Your email (%s) has already been verified.') % request.user.email
    messages.add_message(request, messages.SUCCESS, msg)
    return HttpResponseRedirect(reverse('sentry-account-settings-emails'))
Exemple #12
0
    def post(self, request, organization=None, *args, **kwargs):
        """
        Process a login request via username/password. SSO login is handled
        elsewhere.
        """
        login_form = AuthenticationForm(request, request.DATA)

        # Rate limit logins
        is_limited = ratelimiter.is_limited(
            u'auth:login:username:{}'.format(
                md5_text(request.DATA.get('username').lower()).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)

        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 Response({
                'nextUri':
                '/auth/reactivate/',
                'user':
                serialize(user, user, DetailedUserSerializer()),
            })

        active_org = self.get_active_organization(request)
        redirect_url = auth.get_org_redirect_url(request, active_org)

        return Response({
            'nextUri':
            auth.get_login_redirect(request, redirect_url),
            'user':
            serialize(user, user, DetailedUserSerializer()),
        })
    def get(self, request, organization, event_id):
        """
        Resolve a Event ID
        ``````````````````

        This resolves a event ID to the project slug and internal issue ID and internal event ID.

        :pparam string organization_slug: the slug of the organization the
                                          event ID should be looked up in.
        :param string event_id: the event ID to look up.
        :auth: required
        """
        # Largely copied from ProjectGroupIndexEndpoint
        if len(event_id) != 32:
            return Response({"detail": "Event ID must be 32 characters."},
                            status=400)

        # Limit to 100req/s
        if ratelimiter.is_limited(
                u"api:event-id-lookup:{}".format(
                    md5_text(request.user.id if request.user and request.user.
                             is_authenticated() else "").hexdigest()),
                limit=100,
                window=1,
        ):
            return Response(
                {
                    "detail":
                    "You are attempting to use this endpoint too quickly. Limit is 100 requests/second."
                },
                status=429,
            )

        project_slugs_by_id = dict(
            Project.objects.filter(organization=organization).values_list(
                "id", "slug"))

        try:
            snuba_filter = eventstore.Filter(
                conditions=[["event.type", "!=", "transaction"]],
                project_ids=project_slugs_by_id.keys(),
                event_ids=[event_id],
            )
            event = eventstore.get_events(filter=snuba_filter, limit=1)[0]
        except IndexError:
            raise ResourceDoesNotExist()
        else:
            return Response({
                "organizationSlug":
                organization.slug,
                "projectSlug":
                project_slugs_by_id[event.project_id],
                "groupId":
                six.text_type(event.group_id),
                "eventId":
                six.text_type(event.event_id),
                "event":
                serialize(event, request.user),
            })
Exemple #14
0
    def _is_ip_rate_limited(self):
        limit = options.get('auth.ip-rate-limit')
        if not limit:
            return False

        ip_address = self.request.META['REMOTE_ADDR']
        return ratelimiter.is_limited(
            'auth:ip:{}'.format(ip_address),
            limit,
        )
Exemple #15
0
    def _is_ip_rate_limited(self):
        limit = options.get('auth.ip-rate-limit')
        if not limit:
            return False

        ip_address = self.request.META['REMOTE_ADDR']
        return ratelimiter.is_limited(
            u'auth:ip:{}'.format(ip_address),
            limit,
        )
Exemple #16
0
    def _is_user_rate_limited(self):
        limit = options.get("auth.user-rate-limit")
        if not limit:
            return False

        username = self.cleaned_data.get("username")
        if not username:
            return False

        return ratelimiter.is_limited(f"auth:username:{username}", limit)
Exemple #17
0
    def should_notify(self, group, event):
        if group.is_muted():
            return False

        project = group.project

        rate_limited = ratelimiter.is_limited(project=project, key=self.get_conf_key(), limit=15)

        if rate_limited:
            self.logger.info("Notification for project %s dropped due to rate limiting", project.id)

        return not rate_limited
Exemple #18
0
    def post(self, request):
        """
        Create a New Organization
        `````````````````````````

        Create a new organization owned by the request's user.  To create
        an organization only the name is required.

        :param string name: the human readable name for the new organization.
        :param string slug: the unique URL slug for this organization.  If
                            this is not provided a slug is automatically
                            generated based on the name.
        :auth: required, user-context-needed
        """
        if not request.user.is_authenticated():
            return Response({"detail": "This endpoint requires user info"}, status=401)

        if not features.has("organizations:create", actor=request.user):
            return Response({"detail": "Organizations are not allowed to be created by this user."}, status=401)

        limit = options.get("api.rate-limit.org-create")
        if limit and ratelimiter.is_limited(u"org-create:{}".format(request.user.id), limit=5, window=3600):
            return Response({"detail": "You are attempting to create too many organizations too quickly."}, status=429)

        serializer = OrganizationSerializer(data=request.DATA)

        if serializer.is_valid():
            result = serializer.object

            try:
                with transaction.atomic():
                    org = Organization.objects.create(name=result["name"], slug=result.get("slug"))
            except IntegrityError:
                return Response({"detail": "An organization with this slug already exists."}, status=409)

            om = OrganizationMember.objects.create(organization=org, user=request.user, role=roles.get_top_dog().id)

            if result.get("defaultTeam"):
                team = org.team_set.create(name=org.name)

                OrganizationMemberTeam.objects.create(team=team, organizationmember=om, is_active=True)

            self.create_audit_entry(
                request=request,
                organization=org,
                target_object=org.id,
                event=AuditLogEntryEvent.ORG_ADD,
                data=org.get_audit_log_data(),
            )

            return Response(serialize(org, request.user), status=201)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Exemple #19
0
    def _is_user_rate_limited(self):
        limit = options.get('auth.user-rate-limit')
        if not limit:
            return False

        username = self.cleaned_data.get('username')
        if not username:
            return False

        return ratelimiter.is_limited(
            u'auth:username:{}'.format(username),
            limit,
        )
Exemple #20
0
    def _is_user_rate_limited(self):
        limit = options.get('auth.user-rate-limit')
        if not limit:
            return False

        username = self.cleaned_data.get('username')
        if not username:
            return False

        return ratelimiter.is_limited(
            u'auth:username:{}'.format(username),
            limit,
        )
Exemple #21
0
    def post(self, request, user):
        """
        Sends a confirmation email to user
        ``````````````````````````````````

        :auth required:
        """

        from sentry.app import ratelimiter

        if ratelimiter.is_limited(
                "auth:confirm-email:{}".format(user.id),
                limit=10,
                window=60,  # 10 per minute should be enough for anyone
        ):
            return self.respond(
                {
                    "detail":
                    "You have made too many email confirmation requests. Please try again later."
                },
                status=status.HTTP_429_TOO_MANY_REQUESTS,
            )

        serializer = EmailSerializer(data=request.data)

        if not serializer.is_valid():
            return InvalidEmailResponse()

        # If email is specified then try to only send one confirmation email
        try:
            email_to_send = UserEmail.objects.get(
                user=user,
                email=serializer.validated_data["email"].lower().strip())
        except UserEmail.DoesNotExist:
            return InvalidEmailResponse()
        else:
            if email_to_send.is_verified:
                return self.respond({"detail": "Email is already verified"},
                                    status=status.HTTP_400_BAD_REQUEST)

            user.send_confirm_email_singular(email_to_send)

            logger.info(
                "user.email.start_confirm",
                extra={
                    "user_id": user.id,
                    "ip_address": request.META["REMOTE_ADDR"],
                    "email": email_to_send,
                },
            )
            return self.respond(status=status.HTTP_204_NO_CONTENT)
Exemple #22
0
def recover(request):
    from sentry.app import ratelimiter

    extra = {
        'ip_address': request.META['REMOTE_ADDR'],
        'user_agent': request.META.get('HTTP_USER_AGENT'),
    }

    if request.method == 'POST' and ratelimiter.is_limited(
        u'accounts:recover:{}'.format(extra['ip_address']),
        limit=5,
        window=60,  # 5 per minute should be enough for anyone
    ):
        logger.warning('recover.rate-limited', extra=extra)

        return HttpResponse(
            'You have made too many password recovery attempts. Please try again later.',
            content_type='text/plain',
            status=429,
        )

    prefill = {'user': request.GET.get('email')}

    form = RecoverPasswordForm(request.POST or None, initial=prefill)
    extra['user_recovered'] = form.data.get('user')

    if form.is_valid():
        email = form.cleaned_data['user']
        if email:
            password_hash = LostPasswordHash.for_user(email)
            password_hash.send_email(request)

            extra['passwordhash_id'] = password_hash.id
            extra['user_id'] = password_hash.user_id

            logger.info('recover.sent', extra=extra)

        tpl = 'sentry/account/recover/sent.html'
        context = {'email': email}

        return render_to_response(tpl, context, request)

    if form._errors:
        logger.warning('recover.error', extra=extra)

    tpl = 'sentry/account/recover/index.html'
    context = {'form': form}

    return render_to_response(tpl, context, request)
def recover(request):
    from sentry.app import ratelimiter

    extra = {
        "ip_address": request.META["REMOTE_ADDR"],
        "user_agent": request.META.get("HTTP_USER_AGENT"),
    }

    if request.method == "POST" and ratelimiter.is_limited(
            "accounts:recover:{}".format(extra["ip_address"]),
            limit=5,
            window=60,  # 5 per minute should be enough for anyone
    ):
        logger.warning("recover.rate-limited", extra=extra)

        return HttpResponse(
            "You have made too many password recovery attempts. Please try again later.",
            content_type="text/plain",
            status=429,
        )

    prefill = {"user": request.GET.get("email")}

    form = RecoverPasswordForm(request.POST or None, initial=prefill)
    extra["user_recovered"] = form.data.get("user")

    if form.is_valid():
        email = form.cleaned_data["user"]
        if email:
            password_hash = LostPasswordHash.for_user(email)
            password_hash.send_email(request)

            extra["passwordhash_id"] = password_hash.id
            extra["user_id"] = password_hash.user_id

            logger.info("recover.sent", extra=extra)

        context = {"email": email}

        return render_to_response(get_template("recover", "sent"), context,
                                  request)

    if form._errors:
        logger.warning("recover.error", extra=extra)

    context = {"form": form}

    return render_to_response(get_template("recover", "index"), context,
                              request)
Exemple #24
0
def recover(request):
    from sentry.app import ratelimiter

    extra = {
        'ip_address': request.META['REMOTE_ADDR'],
        'user_agent': request.META.get('HTTP_USER_AGENT'),
    }

    if request.method == 'POST' and ratelimiter.is_limited(
        u'accounts:recover:{}'.format(extra['ip_address']),
        limit=5,
        window=60,  # 5 per minute should be enough for anyone
    ):
        logger.warning('recover.rate-limited', extra=extra)

        return HttpResponse(
            'You have made too many password recovery attempts. Please try again later.',
            content_type='text/plain',
            status=429,
        )

    prefill = {'user': request.GET.get('email')}

    form = RecoverPasswordForm(request.POST or None, initial=prefill)
    extra['user_recovered'] = form.data.get('user')

    if form.is_valid():
        email = form.cleaned_data['user']
        if email:
            password_hash = LostPasswordHash.for_user(email)
            password_hash.send_email(request)

            extra['passwordhash_id'] = password_hash.id
            extra['user_id'] = password_hash.user_id

            logger.info('recover.sent', extra=extra)

        tpl = 'sentry/account/recover/sent.html'
        context = {'email': email}

        return render_to_response(tpl, context, request)

    if form._errors:
        logger.warning('recover.error', extra=extra)

    tpl = 'sentry/account/recover/index.html'
    context = {'form': form}

    return render_to_response(tpl, context, request)
def start_confirm_email(request):
    from sentry.app import ratelimiter

    if ratelimiter.is_limited(
            f"auth:confirm-email:{request.user.id}",
            limit=10,
            window=60,  # 10 per minute should be enough for anyone
    ):
        return HttpResponse(
            "You have made too many email confirmation requests. Please try again later.",
            content_type="text/plain",
            status=429,
        )

    if "primary-email" in request.POST:
        email = request.POST.get("email")
        try:
            email_to_send = UserEmail.objects.get(user=request.user,
                                                  email=email)
        except UserEmail.DoesNotExist:
            msg = _("There was an error confirming your email.")
            level = messages.ERROR
        else:
            request.user.send_confirm_email_singular(email_to_send)
            msg = _("A verification email has been sent to %s.") % (email)
            level = messages.SUCCESS
        messages.add_message(request, level, msg)
        return HttpResponseRedirect(reverse("sentry-account-settings"))
    elif request.user.has_unverified_emails():
        request.user.send_confirm_emails()
        unverified_emails = [
            e.email for e in request.user.get_unverified_emails()
        ]
        msg = _("A verification email has been sent to %s.") % (
            ", ").join(unverified_emails)
        for email in unverified_emails:
            logger.info(
                "user.email.start_confirm",
                extra={
                    "user_id": request.user.id,
                    "ip_address": request.META["REMOTE_ADDR"],
                    "email": email,
                },
            )
    else:
        msg = _(
            "Your email (%s) has already been verified.") % request.user.email
    messages.add_message(request, messages.SUCCESS, msg)
    return HttpResponseRedirect(reverse("sentry-account-settings-emails"))
Exemple #26
0
 def wrapper(*args, **kwargs):
     try:
         if ratelimiter.is_limited(
             u"rate_limit_endpoint:{}".format(md5_text(function).hexdigest()),
             limit=limit,
             window=window,
         ):
             return Response(
                 {"detail": "You are attempting to use this endpoint too quickly."},
                 status=429,
             )
         else:
             return function(*args, **kwargs)
     except Exception:
         raise
Exemple #27
0
 def wrapper(self, request, *args, **kwargs):
     ip = request.META["REMOTE_ADDR"]
     if ratelimiter.is_limited(
         "rate_limit_endpoint:{}:{}".format(md5_text(function).hexdigest(), ip),
         limit=limit,
         window=window,
     ):
         return Response(
             {
                 "detail": f"You are attempting to use this endpoint too quickly. Limit is {limit}/{window}s"
             },
             status=429,
         )
     else:
         return function(self, request, *args, **kwargs)
Exemple #28
0
    def should_notify(self, group, event):
        if group.is_muted():
            return False

        project = group.project

        rate_limited = ratelimiter.is_limited(
            project=project,
            key=self.get_conf_key(),
            limit=10,
        )

        if rate_limited:
            self.logger.info('notification.rate_limited', extra={'project_id': project.id})

        return not rate_limited
Exemple #29
0
 def wrapper(self: Any, request: Request, *args: Any,
             **kwargs: Any) -> Response:
     if ratelimiter.is_limited(
             build_rate_limit_key(function, request),
             limit=limit,
             window=window,
     ):
         return Response(
             {
                 "detail":
                 f"You are attempting to use this endpoint too quickly. Limit is {limit}/{window}s"
             },
             status=429,
         )
     else:
         return function(self, request, *args, **kwargs)
Exemple #30
0
def start_confirm_email(request):
    from sentry.app import ratelimiter

    if ratelimiter.is_limited(
            u'auth:confirm-email:{}'.format(request.user.id),
            limit=10,
            window=60,  # 10 per minute should be enough for anyone
    ):
        return HttpResponse(
            'You have made too many email confirmation requests. Please try again later.',
            content_type='text/plain',
            status=429,
        )

    if 'primary-email' in request.POST:
        email = request.POST.get('email')
        try:
            email_to_send = UserEmail.objects.get(user=request.user,
                                                  email=email)
        except UserEmail.DoesNotExist:
            msg = _('There was an error confirming your email.')
            level = messages.ERROR
        else:
            request.user.send_confirm_email_singular(email_to_send)
            msg = _('A verification email has been sent to %s.') % (email)
            level = messages.SUCCESS
        messages.add_message(request, level, msg)
        return HttpResponseRedirect(reverse('sentry-account-settings'))
    elif request.user.has_unverified_emails():
        request.user.send_confirm_emails()
        unverified_emails = [
            e.email for e in request.user.get_unverified_emails()
        ]
        msg = _('A verification email has been sent to %s.') % (
            ', ').join(unverified_emails)
        for email in unverified_emails:
            logger.info('user.email.start_confirm',
                        extra={
                            'user_id': request.user.id,
                            'ip_address': request.META['REMOTE_ADDR'],
                            'email': email,
                        })
    else:
        msg = _(
            'Your email (%s) has already been verified.') % request.user.email
    messages.add_message(request, messages.SUCCESS, msg)
    return HttpResponseRedirect(reverse('sentry-account-settings-emails'))
Exemple #31
0
    def should_notify(self, group, event):
        if group.is_muted():
            return False

        project = group.project

        rate_limited = ratelimiter.is_limited(
            project=project,
            key=self.get_conf_key(),
            limit=10,
        )

        if rate_limited:
            logger = logging.getLogger('sentry.plugins.{0}'.format(self.get_conf_key()))
            logger.info('Notification for project %s dropped due to rate limiting', project.id)

        return not rate_limited
Exemple #32
0
    def should_notify(self, group, event):
        if group.is_muted():
            return False

        project = group.project

        rate_limited = ratelimiter.is_limited(
            project=project,
            key=self.get_conf_key(),
            limit=15,
        )

        if rate_limited:
            logger = logging.getLogger('sentry.plugins.{0}'.format(self.get_conf_key()))
            logger.info('Notification for project %s dropped due to rate limiting', project.id)

        return not rate_limited
Exemple #33
0
    def should_notify(self, group, event):
        if group.is_ignored():
            return False

        project = group.project

        rate_limited = ratelimiter.is_limited(
            project=project,
            key=self.get_conf_key(),
            limit=10,
        )

        if rate_limited:
            self.logger.info('notification.rate_limited',
                             extra={'project_id': project.id})

        return not rate_limited
    def post(self, request, user):
        """
        Sends a confirmation email to user
        ``````````````````````````````````

        :auth required:
        """

        from sentry.app import ratelimiter

        if ratelimiter.is_limited(
            'auth:confirm-email:{}'.format(user.id),
            limit=10,
            window=60,  # 10 per minute should be enough for anyone
        ):
            return self.respond({'detail': 'You have made too many email confirmation requests. Please try again later.', },
                                status=status.HTTP_429_TOO_MANY_REQUESTS)

        serializer = EmailSerializer(data=request.DATA)

        if not serializer.is_valid():
            return InvalidEmailResponse()

        # If email is specified then try to only send one confirmation email
        try:
            email_to_send = UserEmail.objects.get(
                user=user, email=serializer.object['email'].lower().strip())
        except UserEmail.DoesNotExist:
            return InvalidEmailResponse()
        else:
            if email_to_send.is_verified:
                return self.respond({'detail': 'Email is already verified'},
                                    status=status.HTTP_400_BAD_REQUEST)

            user.send_confirm_email_singular(email_to_send)

            logger.info(
                'user.email.start_confirm',
                extra={
                    'user_id': user.id,
                    'ip_address': request.META['REMOTE_ADDR'],
                    'email': email_to_send,
                }
            )
            return self.respond(status=status.HTTP_204_NO_CONTENT)
    def should_notify(self, group, event):
        if group.is_muted():
            return False

        project = group.project

        rate_limited = ratelimiter.is_limited(
            project=project,
            key=self.get_conf_key(),
            limit=10,
        )

        if rate_limited:
            self.logger.info(
                'Notification for project %s dropped due to rate limiting',
                project.id)

        return not rate_limited
Exemple #36
0
    def should_notify(self, group, event):
        if group.is_muted():
            return False

        project = group.project
        send_to = self.get_sendable_users(project)
        if not send_to:
            return False

        rate_limited = ratelimiter.is_limited(
            project=project,
            key=self.get_conf_key(),
            limit=15,
        )

        if rate_limited:
            logger = logging.getLogger('sentry.plugins.{0}'.format(self.get_conf_key()))
            logger.info('Notification dropped due to rate limiting')

        return not rate_limited
    def post(self, request, organization):
        variant = experiments.get(org=organization,
                                  experiment_name="ImprovedInvitesExperiment")
        if variant not in ("all", "join_request"):
            return Response(status=403)

        if organization.get_option("sentry:join_requests") is False:
            return Response(
                {"detail": "Your organization does not allow join requests."},
                status=403)

        # users can already join organizations with SSO enabled without an invite
        # so they should join that way and not through a request to the admins
        if AuthProvider.objects.filter(organization=organization).exists():
            return Response(status=403)

        ip_address = request.META["REMOTE_ADDR"]

        if ratelimiter.is_limited(
                u"org-join-request:ip:{}".format(ip_address),
                limit=5,
                window=86400,  # 5 per day, 60 x 60 x 24
        ):
            return Response({"detail": "Rate limit exceeded."}, status=429)

        serializer = JoinRequestSerializer(data=request.data)

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.validated_data
        email = result["email"]

        member = create_organization_join_request(organization, email,
                                                  ip_address)

        if member:
            send_invite_request_notification_email.delay(member.id)
            join_request_created.send_robust(sender=self, member=member)

        return Response(status=204)
Exemple #38
0
    def post(self, request: Request, organization) -> Response:
        if organization.get_option("sentry:join_requests") is False:
            return Response(
                {"detail": "Your organization does not allow join requests."},
                status=403)

        # users can already join organizations with SSO enabled without an invite
        # so they should join that way and not through a request to the admins
        if AuthProvider.objects.filter(organization=organization).exists():
            return Response(status=403)

        ip_address = request.META["REMOTE_ADDR"]

        if ratelimiter.is_limited(
                f"org-join-request:ip:{ip_address}",
                limit=5,
                window=86400,  # 5 per day, 60 x 60 x 24
        ):
            return Response({"detail": "Rate limit exceeded."}, status=429)

        serializer = JoinRequestSerializer(data=request.data)

        if not serializer.is_valid():
            return Response(serializer.errors, status=400)

        result = serializer.validated_data
        email = result["email"]

        member = create_organization_join_request(organization, email,
                                                  ip_address)

        if member:
            async_send_notification(JoinRequestNotification, member,
                                    request.user)
            # legacy analytics
            join_request_created.send_robust(sender=self, member=member)

        return Response(status=204)
Exemple #39
0
    def post_process(self, event, **kwargs):
        token = self.get_option('token', event.project)
        index = self.get_option('index', event.project)
        instance = self.get_option('instance', event.project)
        if not (token and index and instance):
            return

        if not instance.endswith('/services/collector'):
            instance = instance.rstrip('/') + '/services/collector'

        source = self.get_option('source', event.project) or 'sentry'

        rl_key = 'splunk:{}'.format(md5_text(token).hexdigest())
        # limit splunk to 50 requests/second
        if ratelimiter.is_limited(rl_key, limit=50, window=1):
            return

        payload = {
            'time': int(event.datetime.strftime('%s')),
            'source': source,
            'index': index,
            'event': self.get_event_payload(event),
        }
        host = self.get_host_for_splunk(event)
        if host:
            payload['host'] = host

        session = http.build_session()
        session.post(
            instance,
            json=payload,
            # Splunk cloud instances certifcates dont play nicely
            verify=False,
            headers={
                'Authorization': 'Splunk {}'.format(token)
            },
        ).raise_for_status()
    def post(self, request, user, interface_id):
        """
        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() and not interface.allow_multi_enrollment:
            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?
            interface.try_enroll(
                serializer.data["challenge"],
                serializer.data["response"],
                serializer.data["deviceName"],
            )
            context.update({"device_name": serializer.data["deviceName"]})

        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
Exemple #41
0
    def handle(self, request: Request) -> Response:
        user = auth.get_pending_2fa_user(request)
        if user is None:
            return HttpResponseRedirect(auth.get_login_url())

        interfaces = Authenticator.objects.all_interfaces_for_user(user)

        # If for whatever reason we ended up here but the user has no 2FA
        # enabled, we just continue successfully.
        if not interfaces:
            return self.perform_signin(request, user)

        challenge = activation = None
        interface = self.negotiate_interface(request, interfaces)

        if request.method == "POST" and ratelimiter.is_limited(
                f"auth-2fa:user:{user.id}", limit=5, window=60):
            # TODO: Maybe email the account owner or do something to notify someone
            # This would probably be good for them to know.
            return HttpResponse(
                "You have made too many 2FA attempts. Please try again later.",
                content_type="text/plain",
                status=429,
            )
        # check if webauthn-login feature flag is enabled for frontend
        webauthn_signin_ff = self._check_can_webauthn_signin(
            user, request.user)

        if request.method == "GET":
            if interface.type == U2fInterface.type:
                activation = interface.activate(request, webauthn_signin_ff)
            else:
                activation = interface.activate(request)

            if activation is not None and activation.type == "challenge":
                challenge = activation.challenge

                if webauthn_signin_ff and interface.type == U2fInterface.type:
                    activation.challenge = {}
                    activation.challenge[
                        "webAuthnAuthenticationData"] = b64encode(challenge)

        elif "challenge" in request.POST:
            challenge = json.loads(request.POST["challenge"])
        form = TwoFactorForm()

        # If an OTP response was supplied, we try to make it pass.
        otp = request.POST.get("otp")
        if otp:
            used_interface = self.validate_otp(otp, interface, interfaces)
            if used_interface is not None:
                return self.perform_signin(request, user, used_interface)
            self.fail_signin(request, user, form)

        #  If a challenge and response exists, validate
        if challenge:
            response = request.POST.get("response")
            if response:
                response = json.loads(response)
                if interface.validate_response(request, challenge, response,
                                               webauthn_signin_ff):
                    return self.perform_signin(request, user, interface)
                self.fail_signin(request, user, form)

        return render_to_response(
            [
                "sentry/twofactor_%s.html" % interface.interface_id,
                "sentry/twofactor.html"
            ],
            {
                "form":
                form,
                "interface":
                interface,
                "other_interfaces":
                self.get_other_interfaces(interface, interfaces),
                "activation":
                activation,
                "isWebauthnSigninFFEnabled":
                webauthn_signin_ff,
            },
            request,
            status=200,
        )
Exemple #42
0
    def post_process(self, event, **kwargs):
        token = self.get_option('token', event.project)
        index = self.get_option('index', event.project)
        instance = self.get_option('instance', event.project)
        if not (token and index and instance):
            metrics.incr('integrations.splunk.forward-event.unconfigured', tags={
                'project_id': event.project_id,
                'organization_id': event.project.organization_id,
                'event_type': event.get_event_type(),
            })
            return

        if not instance.endswith('/services/collector'):
            instance = instance.rstrip('/') + '/services/collector'

        source = self.get_option('source', event.project) or 'sentry'

        rl_key = 'splunk:{}'.format(md5_text(token).hexdigest())
        # limit splunk to 50 requests/second
        if ratelimiter.is_limited(rl_key, limit=1000, window=1):
            metrics.incr('integrations.splunk.forward-event.rate-limited', tags={
                'project_id': event.project_id,
                'organization_id': event.project.organization_id,
                'event_type': event.get_event_type(),
            })
            return

        payload = {
            'time': int(event.datetime.strftime('%s')),
            'source': source,
            'index': index,
            'event': self.get_event_payload(event),
        }
        host = self.get_host_for_splunk(event)
        if host:
            payload['host'] = host

        session = http.build_session()
        try:
            # https://docs.splunk.com/Documentation/Splunk/7.2.3/Data/TroubleshootHTTPEventCollector
            resp = session.post(
                instance,
                json=payload,
                # Splunk cloud instances certifcates dont play nicely
                verify=False,
                headers={
                    'Authorization': 'Splunk {}'.format(token)
                },
                timeout=5,
            )
            if resp.status_code != 200:
                raise SplunkError.from_response(resp)
        except Exception as exc:
            metrics.incr('integrations.splunk.forward-event.error', tags={
                'project_id': event.project_id,
                'organization_id': event.project.organization_id,
                'event_type': event.get_event_type(),
                'error_code': getattr(exc, 'code', None),
            })
            raise

        metrics.incr('integrations.splunk.forward-event.success', tags={
            'project_id': event.project_id,
            'organization_id': event.project.organization_id,
            'event_type': event.get_event_type(),
        })
Exemple #43
0
 def __is_rate_limited(self, group, event):
     return ratelimiter.is_limited(
         project=group.project,
         key=self.get_conf_key(),
         limit=10,
     )
Exemple #44
0
    def handle_basic_auth(self, request, organization=None, *args, **kwargs):
        can_register = self.can_register(
            request, organization=organization, *args, **kwargs)

        op = request.POST.get('op')

        # Detect that we are on the register page by url /register/ and
        # then activate the register tab by default.
        if not op and '/register' in request.path_info and can_register:
            op = 'register'

        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)

            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.'
                ]
            elif login_form.is_valid():
                user = login_form.get_user()

                auth.login(
                    request,
                    user,
                    organization_id=organization.id if organization else None,
                )

                if not user.is_active:
                    return self.redirect(reverse('sentry-reactivate-account'))

                return self.redirect(auth.get_login_redirect(request))

        context = {
            'op': op or 'login',
            'server_hostname': get_server_hostname(),
            'login_form': login_form,
            'organization': organization,
            'register_form': register_form,
            'CAN_REGISTER': can_register,
        }
        return self.respond_login(request, context, organization=organization, *args, **kwargs)
    def post(self, request):
        """
        Create a New Organization
        `````````````````````````

        Create a new organization owned by the request's user.  To create
        an organization only the name is required.

        :param string name: the human readable name for the new organization.
        :param string slug: the unique URL slug for this organization.  If
                            this is not provided a slug is automatically
                            generated based on the name.
        :param bool agreeTerms: a boolean signaling you agree to the applicable
                                terms of service and privacy policy.
        :auth: required, user-context-needed
        """
        if not request.user.is_authenticated():
            return Response({'detail': 'This endpoint requires user info'}, status=401)

        if not features.has('organizations:create', actor=request.user):
            return Response(
                {
                    'detail': 'Organizations are not allowed to be created by this user.'
                }, status=401
            )

        limit = options.get('api.rate-limit.org-create')
        if limit and ratelimiter.is_limited(
            u'org-create:{}'.format(request.user.id),
            limit=limit,
            window=3600,
        ):
            return Response(
                {
                    'detail': 'You are attempting to create too many organizations too quickly.'
                },
                status=429
            )

        serializer = OrganizationSerializer(data=request.DATA)

        if serializer.is_valid():
            result = serializer.object

            try:
                with transaction.atomic():
                    org = Organization.objects.create(
                        name=result['name'],
                        slug=result.get('slug'),
                    )

                    om = OrganizationMember.objects.create(
                        organization=org,
                        user=request.user,
                        role=roles.get_top_dog().id,
                    )

                    if result.get('defaultTeam'):
                        team = org.team_set.create(
                            name=org.name,
                        )

                        OrganizationMemberTeam.objects.create(
                            team=team, organizationmember=om, is_active=True
                        )

                    self.create_audit_entry(
                        request=request,
                        organization=org,
                        target_object=org.id,
                        event=AuditLogEntryEvent.ORG_ADD,
                        data=org.get_audit_log_data(),
                    )

                    analytics.record(
                        'organization.created',
                        org,
                        actor_id=request.user.id if request.user.is_authenticated() else None
                    )

            except IntegrityError:
                return Response(
                    {
                        'detail': 'An organization with this slug already exists.'
                    },
                    status=409,
                )

            # failure on sending this signal is acceptable
            if result.get('agreeTerms'):
                terms_accepted.send_robust(
                    user=request.user,
                    organization=org,
                    ip_address=request.META['REMOTE_ADDR'],
                    sender=type(self),
                )

            return Response(serialize(org, request.user), status=201)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)