Merge pull request #5356 from grafana/dev

v1.13.10
This commit is contained in:
Matias Bordese 2024-12-11 16:31:09 -03:00 committed by GitHub
commit 391cbd03de
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 162 additions and 42 deletions

View file

@ -29,7 +29,7 @@ To authorize, use the **Authorization** header:
```shell
# With shell, you can just pass the correct header with each request
curl "api_endpoint_here" --header "Authorization: "api_key_here""
curl "api_endpoint_here" --header "Authorization: <api_key_here>"
```
Grafana OnCall uses API keys to allow access to the API. You can request a new OnCall API key in OnCall -> Settings page.
@ -39,6 +39,19 @@ request a different API key.
The endpoint refers to the OnCall Application endpoint and can be found on the OnCall -> Settings page as well.
### Authentication using Service Account tokens
It is also possible to use a [service account token](https://grafana.com/docs/grafana/latest/administration/service-accounts/#service-account-tokens)
to authenticate instead of an OnCall access token. In this case you will also need to provide a
header (`X-Grafana-URL`) pointing to your Grafana stack:
```shell
# With shell, you can just pass the correct header with each request
curl "api_endpoint_here" --header "Authorization: <service account token>" --header "X-Grafana-URL: <your stack URL>"
```
Service accounts allow you to set explicit permissions for tokens as well as expire and/or disable them if needed.
## Pagination
List endpoints such as List Integrations or List Alert Groups return multiple objects.

View file

@ -14,6 +14,8 @@ refs:
## List alert groups
**Required permission**: `grafana-oncall-app.alert-groups:read`
```shell
curl "{{API_URL}}/api/v1/alert_groups/" \
--request GET \
@ -97,6 +99,8 @@ These available filter parameters should be provided as `GET` arguments:
## Alert group details
**Required permission**: `grafana-oncall-app.alert-groups:read`
```shell
curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1" \
--request GET \
@ -109,6 +113,8 @@ curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1" \
## Acknowledge an alert group
**Required permission**: `grafana-oncall-app.alert-groups:write` (user authentication only)
```shell
curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/acknowledge" \
--request POST \
@ -121,6 +127,8 @@ curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/acknowledge" \
## Unacknowledge an alert group
**Required permission**: `grafana-oncall-app.alert-groups:write` (user authentication only)
```shell
curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/unacknowledge" \
--request POST \
@ -133,6 +141,8 @@ curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/unacknowledge" \
## Resolve an alert group
**Required permission**: `grafana-oncall-app.alert-groups:write` (user authentication only)
```shell
curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/resolve" \
--request POST \
@ -145,6 +155,8 @@ curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/resolve" \
## Unresolve an alert group
**Required permission**: `grafana-oncall-app.alert-groups:write` (user authentication only)
```shell
curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/unresolve" \
--request POST \
@ -157,6 +169,8 @@ curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/unresolve" \
## Silence an alert group
**Required permission**: `grafana-oncall-app.alert-groups:write` (user authentication only)
```shell
curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/silence" \
--request POST \
@ -177,6 +191,8 @@ curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/silence" \
## Unsilence an alert group
**Required permission**: `grafana-oncall-app.alert-groups:write` (user authentication only)
```shell
curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/unsilence" \
--request POST \
@ -189,6 +205,8 @@ curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/unsilence" \
## Delete an alert group
**Required permission**: `grafana-oncall-app.alert-groups:write`
```shell
curl "{{API_URL}}/api/v1/alert_groups/I68T24C13IFW1/" \
--request DELETE \

View file

@ -14,6 +14,8 @@ refs:
## List Alerts
**Required permission**: `grafana-oncall-app.alert-groups:read`
```shell
curl "{{API_URL}}/api/v1/alerts/" \
--request GET \

View file

@ -22,6 +22,8 @@ refs:
# Escalation HTTP API
**Required permission**: `grafana-oncall-app.alert-groups:direct-paging` (user authentication only)
See [Manual paging integration](ref:manual-paging) for more background on how escalating to a team or user(s) works.
## Escalate to a set of users

View file

@ -14,6 +14,8 @@ refs:
## Create an escalation chain
**Required permission**: `grafana-oncall-app.escalation-chains:write`
```shell
curl "{{API_URL}}/api/v1/escalation_chains/" \
--request POST \
@ -45,6 +47,8 @@ The above command returns JSON structured in the following way:
## Get an escalation chain
**Required permission**: `grafana-oncall-app.escalation-chains:read`
```shell
curl "{{API_URL}}/api/v1/escalation_chains/F5JU6KJET33FE/" \
--request GET \
@ -68,6 +72,8 @@ The above command returns JSON structured in the following way:
## List escalation chains
**Required permission**: `grafana-oncall-app.escalation-chains:read`
```shell
curl "{{API_URL}}/api/v1/escalation_chains/" \
--request GET \
@ -103,6 +109,8 @@ The above command returns JSON structured in the following way:
## Delete an escalation chain
**Required permission**: `grafana-oncall-app.escalation-chains:write`
```shell
curl "{{API_URL}}/api/v1/escalation_chains/F5JU6KJET33FE/" \
--request DELETE \

View file

@ -14,6 +14,8 @@ refs:
## Create an escalation policy
**Required permission**: `grafana-oncall-app.escalation-chains:write`
```shell
curl "{{API_URL}}/api/v1/escalation_policies/" \
--request POST \
@ -61,6 +63,8 @@ The above command returns JSON structured in the following way:
## Get an escalation policy
**Required permission**: `grafana-oncall-app.escalation-chains:read`
```shell
curl "{{API_URL}}/api/v1/escalation_policies/E3GA6SJETWWJS/" \
--request GET \
@ -82,6 +86,8 @@ The above command returns JSON structured in the following way:
## Update an escalation policy
**Required permission**: `grafana-oncall-app.escalation-chains:write`
```shell
curl "{{API_URL}}/api/v1/escalation_policies/E3GA6SJETWWJS/" \
--request PUT \
@ -115,6 +121,8 @@ The above command returns JSON structured in the following way:
## List escalation policies
**Required permission**: `grafana-oncall-app.escalation-chains:read`
```shell
curl "{{API_URL}}/api/v1/escalation_policies/" \
--request GET \
@ -163,6 +171,8 @@ The following available filter parameter should be provided as a `GET` argument:
## Delete an escalation policy
**Required permission**: `grafana-oncall-app.escalation-chains:write`
```shell
curl "{{API_URL}}/api/v1/escalation_policies/E3GA6SJETWWJS/" \
--request DELETE \

View file

@ -23,6 +23,8 @@ refs:
## Create an integration
**Required permission**: `grafana-oncall-app.integrations:write`
```shell
curl "{{API_URL}}/api/v1/integrations/" \
--request POST \
@ -102,6 +104,8 @@ For example, to learn how to integrate Grafana OnCall with Alertmanager refer to
## Get integration
**Required permission**: `grafana-oncall-app.integrations:read`
```shell
curl "{{API_URL}}/api/v1/integrations/CFRPV98RPR1U8/" \
--request GET \
@ -177,6 +181,8 @@ This endpoint retrieves an integration. Integrations are sources of alerts and a
## List integrations
**Required permission**: `grafana-oncall-app.integrations:read`
```shell
curl "{{API_URL}}/api/v1/integrations/" \
--request GET \
@ -262,6 +268,8 @@ The above command returns JSON structured in the following way:
## Update integration
**Required permission**: `grafana-oncall-app.integrations:write`
```shell
curl "{{API_URL}}/api/v1/integrations/CFRPV98RPR1U8/" \
--request PUT \
@ -339,6 +347,8 @@ The above command returns JSON structured in the following way:
## Delete integration
**Required permission**: `grafana-oncall-app.integrations:write`
Deleted integrations will stop recording new alerts from monitoring. Integration removal won't trigger removal of
related alert groups or alerts.

View file

@ -14,6 +14,8 @@ refs:
## Create an OnCall shift
**Required permission**: `grafana-oncall-app.schedules:write`
```shell
curl "{{API_URL}}/api/v1/on_call_shifts/" \
--request POST \
@ -77,6 +79,8 @@ For more information about recurrence rules, refer to [RFC 5545](https://tools.i
## Get OnCall shifts
**Required permission**: `grafana-oncall-app.schedules:read`
```shell
curl "{{API_URL}}/api/v1/on_call_shifts/OH3V5FYQEYJ6M/" \
--request GET \
@ -106,6 +110,8 @@ The above command returns JSON structured in the following way:
## List OnCall shifts
**Required permission**: `grafana-oncall-app.schedules:read`
```shell
curl "{{API_URL}}/api/v1/on_call_shifts/" \
--request GET \
@ -169,6 +175,8 @@ The following available filter parameters should be provided as `GET` arguments:
## Update OnCall shift
**Required permission**: `grafana-oncall-app.schedules:write`
```shell
curl "{{API_URL}}/api/v1/on_call_shifts/OH3V5FYQEYJ6M/" \
--request PUT \
@ -208,6 +216,8 @@ The above command returns JSON structured in the following way:
## Delete OnCall shift
**Required permission**: `grafana-oncall-app.schedules:write`
```shell
curl "{{API_URL}}/api/v1/on_call_shifts/OH3V5FYQEYJ6M/" \
--request DELETE \

View file

@ -14,6 +14,8 @@ refs:
## Get an organization
**Required permission**: `grafana-oncall-app.other-settings:read`
This endpoint retrieves the organization object.
```shell
@ -41,6 +43,8 @@ The above command returns JSON structured in the following way:
## List Organizations
**Required permission**: `grafana-oncall-app.other-settings:read`
```shell
curl "{{API_URL}}/api/v1/organizations/" \
--request GET \

View file

@ -30,6 +30,8 @@ For more details about specific fields of a webhook, refer to [Outgoing webhooks
## List webhooks
**Required permission**: `grafana-oncall-app.outgoing-webhooks:read`
```shell
curl "{{API_URL}}/api/v1/webhooks/" \
--request GET \
@ -75,6 +77,8 @@ The above command returns JSON structured in the following way:
## Get webhook
**Required permission**: `grafana-oncall-app.outgoing-webhooks:read`
```shell
curl "{{API_URL}}/api/v1/webhooks/{{WEBHOOK_UID}}/" \
--request GET \
@ -108,6 +112,8 @@ The above command returns JSON structured in the following way:
## Create webhook
**Required permission**: `grafana-oncall-app.outgoing-webhooks:write`
```shell
curl "{{API_URL}}/api/v1/webhooks/" \
--request POST \
@ -167,6 +173,8 @@ The above command returns JSON structured in the following way:
## Update webhook
**Required permission**: `grafana-oncall-app.outgoing-webhooks:write`
```shell
curl "{{API_URL}}/api/v1/webhooks/{{WEBHOOK_UID}}/" \
--request PUT \
@ -201,6 +209,8 @@ The above command returns JSON structured in the following way:
## Delete webhook
**Required permission**: `grafana-oncall-app.outgoing-webhooks:write`
```shell
curl "{{API_URL}}/api/v1/webhooks/{{WEBHOOK_UID}}/" \
--request DELETE \

View file

@ -14,6 +14,8 @@ refs:
## Post a personal notification rule
**Required permission**: `grafana-oncall-app.user-settings:write` (user authentication only)
```shell
curl "{{API_URL}}/api/v1/personal_notification_rules/" \
--request POST \
@ -51,6 +53,8 @@ The above command returns JSON structured in the following way:
## Get personal notification rule
**Required permission**: `grafana-oncall-app.user-settings:read` (user authentication only)
```shell
curl "{{API_URL}}/api/v1/personal_notification_rules/ND9EHN5LN1DUU/" \
--request GET \
@ -77,6 +81,8 @@ The above command returns JSON structured in the following way:
## List personal notification rules
**Required permission**: `grafana-oncall-app.user-settings:read` (user authentication only)
```shell
curl "{{API_URL}}/api/v1/personal_notification_rules/" \
--request GET \
@ -141,6 +147,8 @@ The following available filter parameters should be provided as `GET` arguments:
## Delete a personal notification rule
**Required permission**: `grafana-oncall-app.user-settings:write` (user authentication only)
```shell
curl "{{API_URL}}/api/v1/personal_notification_rules/NWAL6WFJNWDD8/" \
--request DELETE \

View file

@ -14,6 +14,8 @@ refs:
## Create a resolution note
**Required permission**: `grafana-oncall-app.alert-groups:write`
```shell
curl "{{API_URL}}/api/v1/resolution_notes/" \
--request POST \
@ -49,6 +51,8 @@ The above command returns JSON structured in the following way:
## Get a resolution note
**Required permission**: `grafana-oncall-app.alert-groups:read`
```shell
curl "{{API_URL}}/api/v1/resolution_notes/M4BTQUS3PRHYQ/" \
--request GET \
@ -75,6 +79,8 @@ The above command returns JSON structured in the following way:
## List resolution notes
**Required permission**: `grafana-oncall-app.alert-groups:read`
```shell
curl "{{API_URL}}/api/v1/resolution_notes/" \
--request GET \
@ -117,6 +123,8 @@ The following available filter parameter should be provided as a `GET` argument:
## Update a resolution note
**Required permission**: `grafana-oncall-app.alert-groups:write`
```shell
curl "{{API_URL}}/api/v1/resolution_notes/M4BTQUS3PRHYQ/" \
--request PUT \
@ -146,6 +154,8 @@ The above command returns JSON structured in the following way:
## Delete a resolution note
**Required permission**: `grafana-oncall-app.alert-groups:write`
```shell
curl "{{API_URL}}/api/v1/resolution_notes/M4BTQUS3PRHYQ/" \
--request DELETE \

View file

@ -14,6 +14,8 @@ refs:
## Create a route
**Required permission**: `grafana-oncall-app.integrations:write`
```shell
curl "{{API_URL}}/api/v1/routes/" \
--request POST \
@ -67,6 +69,8 @@ Routes allow you to direct different alerts to different messenger channels and
## Get a route
**Required permission**: `grafana-oncall-app.integrations:read`
```shell
curl "{{API_URL}}/api/v1/routes/RIYGUJXCPFHXY/" \
--request GET \
@ -96,6 +100,8 @@ The above command returns JSON structured in the following way:
## List routes
**Required permission**: `grafana-oncall-app.integrations:read`
```shell
curl "{{API_URL}}/api/v1/routes/" \
--request GET \
@ -153,6 +159,8 @@ The following available filter parameters should be provided as `GET` arguments:
## Update route
**Required permission**: `grafana-oncall-app.integrations:write`
```shell
curl "{{API_URL}}/api/v1/routes/RIYGUJXCPFHXY/" \
--request PUT \
@ -189,6 +197,8 @@ The above command returns JSON structured in the following way:
## Delete a route
**Required permission**: `grafana-oncall-app.integrations:write`
```shell
curl "{{API_URL}}/api/v1/routes/RIYGUJXCPFHXY/" \
--request DELETE \

View file

@ -14,6 +14,8 @@ refs:
## Create a schedule
**Required permission**: `grafana-oncall-app.schedules:write`
```shell
curl "{{API_URL}}/api/v1/schedules/" \
--request POST \
@ -65,6 +67,8 @@ The above command returns JSON structured in the following way:
## Get a schedule
**Required permission**: `grafana-oncall-app.schedules:read`
```shell
curl "{{API_URL}}/api/v1/schedules/SBM7DV7BKFUYU/" \
--request GET \
@ -96,6 +100,8 @@ The above command returns JSON structured in the following way:
## List schedules
**Required permission**: `grafana-oncall-app.schedules:read`
```shell
curl "{{API_URL}}/api/v1/schedules/" \
--request GET \
@ -158,6 +164,8 @@ The following available filter parameter should be provided as a `GET` argument:
## Update a schedule
**Required permission**: `grafana-oncall-app.schedules:write`
```shell
curl "{{API_URL}}/api/v1/schedules/SBM7DV7BKFUYU/" \
--request PUT \
@ -196,6 +204,8 @@ The above command returns JSON structured in the following way:
## Delete a schedule
**Required permission**: `grafana-oncall-app.schedules:write`
```shell
curl "{{API_URL}}/api/v1/schedules/SBM7DV7BKFUYU/" \
--request DELETE \
@ -209,6 +219,8 @@ curl "{{API_URL}}/api/v1/schedules/SBM7DV7BKFUYU/" \
## Export a schedule's final shifts
**Required permission**: `grafana-oncall-app.schedules:read`
**HTTP request**
```shell

View file

@ -14,6 +14,8 @@ refs:
## Create a shift swap request
**Required permission**: `grafana-oncall-app.schedules:write`
```shell
curl "{{API_URL}}/api/v1/shift_swaps/" \
--request POST \
@ -87,6 +89,8 @@ The above command returns JSON structured in the following way:
## Get a shift swap request
**Required permission**: `grafana-oncall-app.schedules:read`
```shell
curl "{{API_URL}}/api/v1/shift_swaps/SSRG1TDNBMJQ1NC/" \
--request GET \
@ -145,6 +149,8 @@ The above command returns JSON structured in the following way:
## List shift swap requests
**Required permission**: `grafana-oncall-app.schedules:read`
```shell
curl "{{API_URL}}/api/v1/shift_swaps/" \
--request GET \
@ -207,6 +213,8 @@ The following available filter parameters may be provided as a `GET` arguments:
## Update a shift swap request
**Required permission**: `grafana-oncall-app.schedules:write`
```shell
curl "{{API_URL}}/api/v1/shift_swaps/SSRG1TDNBMJQ1NC/" \
--request PUT \
@ -271,6 +279,8 @@ The above command returns JSON structured in the following way:
## Delete a shift swap request
**Required permission**: `grafana-oncall-app.schedules:write`
```shell
curl "{{API_URL}}/api/v1/shift_swaps/SSRG1TDNBMJQ1NC/" \
--request DELETE \
@ -284,6 +294,8 @@ curl "{{API_URL}}/api/v1/shift_swaps/SSRG1TDNBMJQ1NC/" \
## Take a shift swap request
**Required permission**: `grafana-oncall-app.schedules:write`
```shell
curl "{{API_URL}}/api/v1/shift_swaps/SSRG1TDNBMJQ1NC/take" \
--request POST \

View file

@ -14,6 +14,8 @@ refs:
## List Slack Channels
**Required permission**: `grafana-oncall-app.chatops:read`
```shell
curl "{{API_URL}}/api/v1/slack_channels/" \
--request GET \

View file

@ -14,6 +14,8 @@ refs:
## Get a team
**Required permission**: `grafana-oncall-app.user-settings:read`
This endpoint retrieves the team object.
```shell
@ -49,6 +51,8 @@ The above command returns JSON structured in the following way:
## List Teams
**Required permission**: `grafana-oncall-app.user-settings:read`
```shell
curl "{{API_URL}}/api/v1/teams/" \
--request GET \

View file

@ -16,6 +16,8 @@ refs:
## List user groups
**Required permission**: `grafana-oncall-app.chatops:read`
```shell
curl "{{API_URL}}/api/v1/user_groups/" \
--request GET \

View file

@ -14,6 +14,8 @@ refs:
## Get a user
**Required permission**: `grafana-oncall-app.user-settings:read`
This endpoint retrieves the user object.
```shell
@ -62,6 +64,8 @@ Use `{{API_URL}}/api/v1/users/current` to retrieve the current user.
## List Users
**Required permission**: `grafana-oncall-app.user-settings:read`
```shell
curl "{{API_URL}}/api/v1/users/" \
--request GET \

View file

@ -135,11 +135,9 @@ class BasePluginAuthentication(BaseAuthentication):
user_id = context["UserID"]
if context.get("IsServiceAccount", False):
# no user involved in service account requests
logger.info(f"serviceaccount request - id={user_id}")
service_account_role = context.get("Role", "None")
if service_account_role.lower() != "admin":
raise exceptions.AuthenticationFailed("Service account requests must have Admin or Editor role.")
# no user involved in service account requests
logger.info(f"serviceaccount request - id={user_id} - role={service_account_role}")
return None
try:
@ -361,7 +359,7 @@ class GrafanaServiceAccountAuthentication(BaseAuthentication):
organization = self.get_organization(request, auth)
if not organization:
raise exceptions.AuthenticationFailed("Invalid organization.")
raise exceptions.AuthenticationFailed("Organization not found.")
if organization.is_moved:
raise OrganizationMovedException(organization)
if organization.deleted_at:
@ -374,9 +372,10 @@ class GrafanaServiceAccountAuthentication(BaseAuthentication):
if grafana_url:
organization = Organization.objects.filter(grafana_url=grafana_url).first()
if not organization:
success = setup_organization(grafana_url, auth)
if not success:
raise exceptions.AuthenticationFailed("Invalid Grafana URL.")
# trigger a request to sync the organization
# (ignore response since we can get a 400 if sync was already triggered;
# if organization exists, we are good)
setup_organization(grafana_url, auth)
organization = Organization.objects.filter(grafana_url=grafana_url).first()
return organization

View file

@ -93,7 +93,7 @@ def test_grafana_authentication_missing_org():
with pytest.raises(exceptions.AuthenticationFailed) as exc:
GrafanaServiceAccountAuthentication().authenticate(request)
assert exc.value.detail == "Invalid organization."
assert exc.value.detail == "Organization not found."
@pytest.mark.django_db
@ -112,7 +112,7 @@ def test_grafana_authentication_invalid_grafana_url():
with pytest.raises(exceptions.AuthenticationFailed) as exc:
GrafanaServiceAccountAuthentication().authenticate(request)
assert exc.value.detail == "Invalid Grafana URL."
assert exc.value.detail == "Organization not found."
@pytest.mark.django_db

View file

@ -6,7 +6,7 @@ from django.utils import timezone
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.test import APIRequestFactory
from apps.auth_token.auth import BasePluginAuthentication, PluginAuthentication
from apps.auth_token.auth import PluginAuthentication
INSTANCE_CONTEXT = '{"stack_id": 42, "org_id": 24, "grafana_token": "abc"}'
@ -176,33 +176,3 @@ def test_plugin_authentication_self_hosted_setup_new_user(make_organization, mak
assert ret_user.user_id == 12
assert ret_token.organization == organization
assert organization.users.count() == 1
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_raises", [("Admin", False), ("Editor", True), ("Viewer", True), ("Other", True)]
)
def test_plugin_authentication_service_account(make_organization, role, expected_raises):
# Setting gcom_token_org_last_time_synced to now, so it doesn't try to sync with gcom
organization = make_organization(
stack_id=42, org_id=24, gcom_token="123", api_token="abc", gcom_token_org_last_time_synced=timezone.now()
)
headers = {
"HTTP_AUTHORIZATION": "gcom:123",
"HTTP_X-Instance-Context": INSTANCE_CONTEXT,
"HTTP_X-Grafana-Context": json.dumps({"UserId": 12, "Role": role, "IsServiceAccount": True}),
}
request = APIRequestFactory().get("/", **headers)
if expected_raises:
with pytest.raises(AuthenticationFailed):
BasePluginAuthentication().authenticate(request)
else:
ret_user, ret_token = BasePluginAuthentication().authenticate(request)
assert ret_user is None
assert ret_token.organization == organization
# PluginAuthentication should always raise an exception if the request comes from a service account
with pytest.raises(AuthenticationFailed):
PluginAuthentication().authenticate(request)