Skip to content

Commit

Permalink
Merge pull request #5057 from grafana/dev
Browse files Browse the repository at this point in the history
v1.9.26
  • Loading branch information
iskhakov authored Sep 23, 2024
2 parents 286eacf + 82c564f commit 65fce5e
Show file tree
Hide file tree
Showing 235 changed files with 4,738 additions and 4,025 deletions.
10 changes: 0 additions & 10 deletions .github/issue_and_pr_commands.json

This file was deleted.

15 changes: 15 additions & 0 deletions .github/workflows/add-to-docs-project.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Add to docs project
on:
issues:
types: [labeled]
pull_request:
types: [labeled]
jobs:
main:
if: ${{ github.event.label.name == 'type/docs' }}
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
steps:
- uses: grafana/writers-toolkit/add-to-docs-project@add-to-docs-project/v1
25 changes: 0 additions & 25 deletions .github/workflows/issue_commands.yml

This file was deleted.

11 changes: 7 additions & 4 deletions .github/workflows/snyk-security-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@ jobs:
# only required for workflows in private repositories
actions: read
contents: read
# required for Vault secrets
id-token: write
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: ./.github/actions/setup-python
- name: Install frontend dependencies
uses: ./.github/actions/install-frontend-dependencies
- name: Get Vault secrets
uses: grafana/shared-workflows/actions/get-vault-secrets@main
with:
common_secrets: |
SNYK_TOKEN=snyk_scan_github_action:token
- name: Install Snyk
uses: snyk/actions/setup@master
# NOTE: on the snyk monitor and snyk test commands, we are excluding the dev and tools directories
Expand All @@ -31,15 +38,11 @@ jobs:
- name: snyk monitor
# https://docs.snyk.io/snyk-cli/commands/monitor
run: snyk monitor --all-projects --severity-threshold=high --exclude=dev,tools
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
- name: snyk test
# https://docs.snyk.io/snyk-cli/commands/test
# yamllint disable rule:line-length
run: snyk test --all-projects --severity-threshold=high --exclude=dev,tools --fail-on=all --show-vulnerable-paths=all
# yamllint enable rule:line-length
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# TODO: setup snyk container monitor & snyk container test
# will require building the docker image and storing it in the local docker registry..
# will need to refactor .github/workflows/build-engine-docker-image-and-publish-to-dockerhub.yml
Expand Down
2 changes: 1 addition & 1 deletion .tilt/plugin/Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ if not is_ci:
dir=grafana_plugin_dir,
cmd="pnpm install",
serve_dir=grafana_plugin_dir,
serve_cmd="pnpm watch",
serve_cmd="pnpm dev",
allow_parallel=True,
)

Expand Down
2 changes: 2 additions & 0 deletions docs/sources/configure/integrations/references/slack/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ title: Slack integration for Grafana IRM
menuTitle: IRM Slack
description: Learn more about Slack integration for Grafana IRM.
weight: 0
_build:
list: false
keywords:
- OnCall
- IRM
Expand Down
2 changes: 1 addition & 1 deletion engine/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.12.3-alpine3.20 AS base
FROM python:3.12.6-alpine3.20 AS base
ARG TARGETPLATFORM

# Create a group and user to run an app
Expand Down
2 changes: 1 addition & 1 deletion engine/apps/api/serializers/on_call_shifts.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class OnCallShiftSerializer(EagerLoadingMixin, serializers.ModelSerializer):
allow_null=True,
required=False,
child=UsersFilteredByOrganizationField(
queryset=User.objects, required=False, allow_null=True
queryset=User.objects, require_all_exist=True, required=False, allow_null=True
), # todo: filter by team?
)
updated_shift = serializers.CharField(read_only=True, allow_null=True, source="updated_shift.public_primary_key")
Expand Down
77 changes: 77 additions & 0 deletions engine/apps/api/tests/test_oncall_shift.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,48 @@ def test_update_future_on_call_shift_removing_users(
assert response.data["rolling_users"][0] == "User(s) are required"


@pytest.mark.django_db
def test_update_on_call_shift_invalid_rolling_users(
on_call_shift_internal_api_setup,
make_on_call_shift,
make_user_auth_headers,
):
token, user1, _, _, schedule = on_call_shift_internal_api_setup

client = APIClient()
start_date = (timezone.now() + timezone.timedelta(days=1)).replace(microsecond=0)

name = "Test Shift Rotation"
on_call_shift = make_on_call_shift(
schedule.organization,
shift_type=CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
schedule=schedule,
name=name,
start=start_date,
duration=timezone.timedelta(hours=1),
rotation_start=start_date,
rolling_users=[{user1.pk: user1.public_primary_key}],
)
data_to_update = {
"name": name,
"priority_level": 2,
"shift_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"shift_end": (start_date + timezone.timedelta(hours=1)).strftime("%Y-%m-%dT%H:%M:%SZ"),
"rotation_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"until": None,
"frequency": None,
"interval": None,
"by_day": None,
"rolling_users": [["fuzz"]],
}

url = reverse("api-internal:oncall_shifts-detail", kwargs={"pk": on_call_shift.public_primary_key})
response = client.put(url, data=data_to_update, format="json", **make_user_auth_headers(user1, token))

assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json() == {"rolling_users": {"0": ["User does not exist {'fuzz'}"]}}


@pytest.mark.django_db
def test_update_started_on_call_shift(
on_call_shift_internal_api_setup,
Expand Down Expand Up @@ -1202,6 +1244,41 @@ def test_create_on_call_shift_invalid_data_rolling_users(
assert response.data["rolling_users"][0] == "Cannot set multiple user groups for non-recurrent shifts"


@pytest.mark.django_db
def test_create_on_call_shift_invalid_rolling_users(on_call_shift_internal_api_setup, make_user_auth_headers):
token, user1, user2, _, schedule = on_call_shift_internal_api_setup
client = APIClient()
url = reverse("api-internal:oncall_shifts-list")
start_date = timezone.now().replace(microsecond=0, tzinfo=None)

data = {
"name": "Test Shift",
"type": CustomOnCallShift.TYPE_ROLLING_USERS_EVENT,
"schedule": schedule.public_primary_key,
"priority_level": 1,
"shift_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"shift_end": (start_date + timezone.timedelta(hours=2)).strftime("%Y-%m-%dT%H:%M:%SZ"),
"rotation_start": start_date.strftime("%Y-%m-%dT%H:%M:%SZ"),
"until": None,
"frequency": 1,
"interval": 1,
"by_day": [
CustomOnCallShift.ICAL_WEEKDAY_MAP[CustomOnCallShift.MONDAY],
CustomOnCallShift.ICAL_WEEKDAY_MAP[CustomOnCallShift.FRIDAY],
],
"week_start": CustomOnCallShift.ICAL_WEEKDAY_MAP[CustomOnCallShift.MONDAY],
"rolling_users": [[user1.public_primary_key], [user2.public_primary_key, "fuzz"]],
}

with patch("apps.schedules.models.CustomOnCallShift.refresh_schedule") as mock_refresh_schedule:
response = client.post(url, data, format="json", **make_user_auth_headers(user1, token))

expected_payload = {"rolling_users": {"1": ["User does not exist {'fuzz'}"]}}
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json() == expected_payload
mock_refresh_schedule.assert_not_called()


@pytest.mark.django_db
def test_create_on_call_shift_override_invalid_data(on_call_shift_internal_api_setup, make_user_auth_headers):
token, user1, _, _, schedule = on_call_shift_internal_api_setup
Expand Down
89 changes: 49 additions & 40 deletions engine/apps/api/tests/test_user.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from unittest import mock
from unittest.mock import Mock, patch
from unittest.mock import Mock, PropertyMock, patch

import pytest
from django.core.cache import cache
Expand Down Expand Up @@ -1775,17 +1775,10 @@ def test_invalid_working_hours(

@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
@patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True)
@patch(
"apps.api.throttlers.GetPhoneVerificationCodeThrottlerPerUser.get_throttle_limits",
return_value=(1, 10 * 60),
)
@patch("apps.api.throttlers.VerifyPhoneNumberThrottlerPerUser.get_throttle_limits", return_value=(1, 10 * 60))
@pytest.mark.django_db
def test_phone_number_verification_flow_ratelimit_per_user(
mock_verification_start,
mocked_verification_check,
mocked_get_phone_verification_code_get_throttle_limits,
mocked_get_phone_verify_phone_number_limits,
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
):
Expand All @@ -1794,40 +1787,44 @@ def test_phone_number_verification_flow_ratelimit_per_user(
client = APIClient()
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": user.public_primary_key})

# first get_verification_code request is succesfull
response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK
with patch(
"apps.api.throttlers.GetPhoneVerificationCodeThrottlerPerUser.rate",
new_callable=PropertyMock,
) as mocked_rate:
mocked_rate.return_value = "1/10m"
# first get_verification_code request is succesfull
response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK

# second get_verification_code request is ratelimited
response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
# second get_verification_code request is ratelimited
response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS

url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})

# first verify_number request is succesfull, because it uses different ratelimit scope
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK
with patch(
"apps.api.throttlers.VerifyPhoneNumberThrottlerPerUser.rate",
new_callable=PropertyMock,
) as mocked_rate:
mocked_rate.return_value = "1/10m"

url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
# first verify_number request is succesfull, because it uses different ratelimit scope
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK

# second verify_number request is succesfull, because it ratelimited
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})

# second verify_number request is succesfull, because it ratelimited
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS


@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
@patch("apps.phone_notifications.phone_backend.PhoneBackend.verify_phone_number", return_value=True)
@patch(
"apps.api.throttlers.GetPhoneVerificationCodeThrottlerPerOrg.get_throttle_limits",
return_value=(1, 10 * 60),
)
@patch("apps.api.throttlers.VerifyPhoneNumberThrottlerPerOrg.get_throttle_limits", return_value=(1, 10 * 60))
@pytest.mark.django_db
def test_phone_number_verification_flow_ratelimit_per_org(
mock_verification_start,
mocked_verification_check,
mocked_get_phone_verification_code_get_throttle_limits,
mocked_get_phone_verify_phone_number_limits,
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
make_user_for_organization,
Expand All @@ -1841,21 +1838,33 @@ def test_phone_number_verification_flow_ratelimit_per_org(

client = APIClient()

url = reverse("api-internal:user-get-verification-code", kwargs={"pk": user.public_primary_key})
response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK
with patch(
"apps.api.throttlers.GetPhoneVerificationCodeThrottlerPerOrg.rate",
new_callable=PropertyMock,
) as mocked_rate:
mocked_rate.return_value = "1/10m"

url = reverse("api-internal:user-get-verification-code", kwargs={"pk": second_user.public_primary_key})
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": user.public_primary_key})
response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK

url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK
url = reverse("api-internal:user-get-verification-code", kwargs={"pk": second_user.public_primary_key})
response = client.get(url, format="json", **make_user_auth_headers(second_user, token))
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS

url = reverse("api-internal:user-verify-number", kwargs={"pk": second_user.public_primary_key})
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(second_user, token))
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS
with patch(
"apps.api.throttlers.VerifyPhoneNumberThrottlerPerOrg.rate",
new_callable=PropertyMock,
) as mocked_rate:
mocked_rate.return_value = "1/10m"

url = reverse("api-internal:user-verify-number", kwargs={"pk": user.public_primary_key})
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK

url = reverse("api-internal:user-verify-number", kwargs={"pk": second_user.public_primary_key})
response = client.put(f"{url}?token=12345", format="json", **make_user_auth_headers(second_user, token))
assert response.status_code == status.HTTP_429_TOO_MANY_REQUESTS


@patch("apps.phone_notifications.phone_backend.PhoneBackend.send_verification_sms", return_value=Mock())
Expand Down
4 changes: 2 additions & 2 deletions engine/apps/api/throttlers/demo_alert_throttler.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from rest_framework.throttling import UserRateThrottle
from common.api_helpers.custom_rate_scoped_throttler import CustomRateUserThrottler


class DemoAlertThrottler(UserRateThrottle):
class DemoAlertThrottler(CustomRateUserThrottler):
scope = "send_demo_alert"
rate = "30/m"
Loading

0 comments on commit 65fce5e

Please sign in to comment.