Add RBAC Support (#777)

* Modify plugin.json to support RBAC role registration

* defines 26 new custom roles in plugin.json. The main roles are:

- Admin: read/write access to everything in OnCall
- Reader: read access to everything in OnCall
- OnCaller : read access to everything in OnCall + edit access to Alert Groups and Schedules
- <object-type> Editor: read/write access to everything related to <object-type>
- <object-type> Reader: read access for <object-type>
- User Settings Admin: read/write access to all user's settings, not just own settings. This is in comparison to User Settings Editor which can only read/write own settings

* update changelog and documentation (#686)

* implement RBAC for OnCall backend

This commit refactors backend authorization. It trys to use RBAC authorization if the org's grafana instance supports it, otherwise it falls back to basic role authorization.

* update RBAC backend tests

* add tests for RBAC changes
- run backend tests as matrix where RBAC is enabled/disabled. When RBAC is enabled, the permissions granted are read from the role grants in the frontend's plugin.json file (instead of relying what we specify in RBACPermission.Permissions)
- remove --reuse-db --nomigrations flags from engine/tox.ini
- minor autoformatting changes to docker-compose-developer.yml

* remove --ds=settings.ci-test from pytest CI command

DJANGO_SETTINGS_MODULE is already specified as an env var so this is just unecessary duplication

* update gitignore

* update github action job name for "test"

* RBAC frontend changes

* refactors the use of basic roles (ex. Viewer, Editor, Admin) use RBAC permissions (when supported), or falling back to basic roles when RBAC is not supported.

- updates the UserAction enum in grafana-plugin/src/state/userAction.ts. Previously this was hardcoded to a list of strings that were being returned by the OnCall API. Now the values here correspond to the permissions in plugin.json (plus a fallback role)

* changes per Gabriel's comments:
- get rid of group attribute in rbac roles
- remove displayName role attribute
- remove hidden role attribute
- add back role to includes section

* don't try to update user timezone if they don't have permission
This commit is contained in:
Joey Orlando 2022-11-29 09:41:56 +01:00 committed by GitHub
parent 132cf1da7f
commit 9e598385f4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
172 changed files with 4424 additions and 2194 deletions

View file

@ -27,7 +27,7 @@ jobs:
run: |
pre-commit run --all-files
test:
test-frontend:
runs-on: ubuntu-latest
container: python:3.9
steps:
@ -56,8 +56,12 @@ jobs:
docker run -v ${PWD}/docs/sources:/hugo/content/docs/oncall/latest -e HUGO_REFLINKSERRORLEVEL=ERROR --rm grafana/docs-base:latest /bin/bash -c 'make hugo'
unit-test-backend-mysql-rabbitmq:
name: "Backend Tests: MySQL + RabbitMQ (RBAC enabled: ${{ matrix.rbac_enabled }})"
runs-on: ubuntu-latest
container: python:3.9
strategy:
matrix:
rbac_enabled: ["True", "False"]
env:
DJANGO_SETTINGS_MODULE: settings.ci-test
SLACK_CLIENT_OAUTH_ID: 1
@ -72,7 +76,6 @@ jobs:
env:
MYSQL_DATABASE: oncall_local_dev
MYSQL_ROOT_PASSWORD: local_dev_pwd
steps:
- uses: actions/checkout@v2
- name: Unit Test Backend
@ -80,11 +83,15 @@ jobs:
apt-get update && apt-get install -y netcat
cd engine/
pip install -r requirements.txt
./wait_for_test_mysql_start.sh && pytest --ds=settings.ci-test -x
./wait_for_test_mysql_start.sh && ONCALL_TESTING_RBAC_ENABLED=${{ matrix.rbac_enabled }} pytest -x
unit-test-backend-postgresql-rabbitmq:
name: "Backend Tests: PostgreSQL + RabbitMQ (RBAC enabled: ${{ matrix.rbac_enabled }})"
runs-on: ubuntu-latest
container: python:3.9
strategy:
matrix:
rbac_enabled: ["True", "False"]
env:
DATABASE_TYPE: postgresql
DJANGO_SETTINGS_MODULE: settings.ci-test
@ -112,11 +119,15 @@ jobs:
run: |
cd engine/
pip install -r requirements.txt
pytest --ds=settings.ci-test -x
ONCALL_TESTING_RBAC_ENABLED=${{ matrix.rbac_enabled }} pytest -x
unit-test-backend-sqlite-redis:
name: "Backend Tests: SQLite + Redis (RBAC enabled: ${{ matrix.rbac_enabled }})"
runs-on: ubuntu-latest
container: python:3.9
strategy:
matrix:
rbac_enabled: ["True", "False"]
env:
DATABASE_TYPE: sqlite3
BROKER_TYPE: redis
@ -131,7 +142,6 @@ jobs:
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v2
- name: Unit Test Backend
@ -139,7 +149,7 @@ jobs:
apt-get update && apt-get install -y netcat
cd engine/
pip install -r requirements.txt
pytest --ds=settings.ci-test -x
ONCALL_TESTING_RBAC_ENABLED=${{ matrix.rbac_enabled }} pytest -x
docker-build:
runs-on: ubuntu-latest

View file

@ -5,14 +5,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## v1.1.6 (TBD)
## v1.2.0 (TBD)
### Added
- RBAC permission support
### Fixed
- Got 500 error when saving Outgoing Webhook ([#890](https://github.com/grafana/oncall/issues/890))
### Changed
- When editing templates for alert group presentation or outgoing webhooks, errors and warnings are now displayed in the UI as notification popups or displayed in the preview.
- Errors and warnings that occur when rendering templates during notification or webhooks will now render and display the error/warning as the result.
## v1.1.5 (2022-11-24)
### Added

View file

@ -13,6 +13,9 @@ x-oncall-volumes: &oncall-volumes
# https://stackoverflow.com/a/60456034
- ${ENTERPRISE_ENGINE:-/dev/null}:/etc/app/extensions/engine_enterprise
- ${SQLITE_DB_FILE:-/dev/null}:/var/lib/oncall/oncall.db
# this is mounted for testing purposes. Some of the authorization tests
# reference this file
- ./grafana-plugin/src/plugin.json:/etc/grafana-plugin/src/plugin.json
x-env-files: &oncall-env-files
- ./dev/.env.dev

View file

@ -53,13 +53,13 @@ def notify_user_task(
organization = alert_group.channel.organization
if not user.is_notification_allowed:
task_logger.info(f"notify_user_task: user {user.pk} notification is not allowed for role {user.role}")
task_logger.info(f"notify_user_task: user {user.pk} notification is not allowed")
UserNotificationPolicyLogRecord(
author=user,
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
reason=f"notification is not allowed for user with role {user.role}",
reason=f"notification is not allowed for user",
alert_group=alert_group,
notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE,
notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_FORBIDDEN,
).save()
return
@ -252,9 +252,9 @@ def perform_notification(log_record_pk):
UserNotificationPolicyLogRecord(
author=user,
type=UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED,
reason=f"notification is not allowed for user with role {user.role}",
reason=f"notification is not allowed for user",
alert_group=alert_group,
notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE,
notification_error_code=UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_FORBIDDEN,
).save()
return

View file

@ -4,7 +4,6 @@ from apps.alerts.incident_appearance.renderers.phone_call_renderer import AlertG
from apps.alerts.models import AlertGroup
from apps.alerts.tasks.delete_alert_group import delete_alert_group
from apps.slack.models import SlackMessage
from common.constants.role import Role
@pytest.mark.django_db
@ -14,7 +13,7 @@ def test_render_for_phone_call(
make_alert_group,
make_alert,
):
organization, slack_team_identity = make_organization_with_slack_team_identity()
organization, _ = make_organization_with_slack_team_identity()
alert_receive_channel = make_alert_receive_channel(organization, integration_slack_channel_id="CWER1ASD")
alert_group = make_alert_group(alert_receive_channel)
@ -59,7 +58,7 @@ def test_delete(
organization, slack_team_identity = make_organization_with_slack_team_identity()
slack_channel = make_slack_channel(slack_team_identity, name="general", slack_id="CWER1ASD")
user = make_user(organization=organization, role=Role.ADMIN)
user = make_user(organization=organization)
alert_receive_channel = make_alert_receive_channel(organization, integration_slack_channel_id="CWER1ASD")

View file

@ -8,9 +8,9 @@ from apps.alerts.escalation_snapshot.serializers.escalation_policy_snapshot impo
from apps.alerts.escalation_snapshot.snapshot_classes import EscalationPolicySnapshot
from apps.alerts.escalation_snapshot.utils import eta_for_escalation_step_notify_if_time
from apps.alerts.models import AlertGroupLogRecord, EscalationPolicy
from apps.api.permissions import LegacyAccessControlRole
from apps.schedules.ical_utils import list_users_to_notify_from_ical
from apps.schedules.models import CustomOnCallShift, OnCallScheduleCalendar
from common.constants.role import Role
def get_escalation_policy_snapshot_from_model(escalation_policy):
@ -213,8 +213,8 @@ def test_escalation_step_notify_on_call_schedule_viewer_user(
make_schedule,
make_on_call_shift,
):
organization, user, _, channel_filter, alert_group, reason = escalation_step_test_setup
viewer = make_user_for_organization(organization=organization, role=Role.VIEWER)
organization, _, _, channel_filter, alert_group, reason = escalation_step_test_setup
viewer = make_user_for_organization(organization=organization, role=LegacyAccessControlRole.VIEWER)
schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar)
# create on_call_shift with user to notify
@ -263,7 +263,7 @@ def test_escalation_step_notify_user_group(
make_slack_user_group,
make_escalation_policy,
):
organization, user, _, channel_filter, alert_group, reason = escalation_step_test_setup
organization, _, _, channel_filter, alert_group, reason = escalation_step_test_setup
slack_team_identity = make_slack_team_identity()
organization.slack_team_identity = slack_team_identity
organization.save()
@ -295,7 +295,7 @@ def test_escalation_step_notify_if_time(
escalation_step_test_setup,
make_escalation_policy,
):
organization, user, _, channel_filter, alert_group, reason = escalation_step_test_setup
_, _, _, channel_filter, alert_group, reason = escalation_step_test_setup
# current time is not between from_time and to_time, step returns eta
now = timezone.now()
@ -358,7 +358,7 @@ def test_escalation_step_notify_if_time(
def test_escalation_step_notify_if_num_alerts_in_window(
mocked_execute_tasks, escalation_step_test_setup, make_escalation_policy, make_alert
):
organization, user, _, channel_filter, alert_group, reason = escalation_step_test_setup
_, _, _, channel_filter, alert_group, reason = escalation_step_test_setup
make_alert(alert_group=alert_group, raw_request_data={})
make_alert(alert_group=alert_group, raw_request_data={})
@ -419,7 +419,7 @@ def test_escalation_step_trigger_custom_button(
make_custom_action,
make_escalation_policy,
):
organization, _, alert_receive_channel, channel_filter, alert_group, reason = escalation_step_test_setup
organization, _, _, channel_filter, alert_group, reason = escalation_step_test_setup
custom_button = make_custom_action(organization=organization)

View file

@ -3,9 +3,11 @@ from unittest.mock import patch
import pytest
from apps.alerts.tasks.notify_user import notify_user_task, perform_notification
from apps.api.permissions import LegacyAccessControlRole
from apps.base.models.user_notification_policy import UserNotificationPolicy
from apps.base.models.user_notification_policy_log_record import UserNotificationPolicyLogRecord
from common.constants.role import Role
NOTIFICATION_UNAUTHORIZED_MSG = "notification is not allowed for user"
@pytest.mark.django_db
@ -131,7 +133,9 @@ def test_notify_user_perform_notification_error_if_viewer(
make_user_notification_policy_log_record,
):
organization = make_organization()
user_1 = make_user(organization=organization, role=Role.VIEWER, _verified_phone_number="1234567890")
user_1 = make_user(
organization=organization, role=LegacyAccessControlRole.VIEWER, _verified_phone_number="1234567890"
)
user_notification_policy = make_user_notification_policy(
user=user_1,
step=UserNotificationPolicy.Step.NOTIFY,
@ -150,11 +154,8 @@ def test_notify_user_perform_notification_error_if_viewer(
error_log_record = UserNotificationPolicyLogRecord.objects.last()
assert error_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
assert error_log_record.reason == f"notification is not allowed for user with role {user_1.role}"
assert (
error_log_record.notification_error_code
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE
)
assert error_log_record.reason == NOTIFICATION_UNAUTHORIZED_MSG
assert error_log_record.notification_error_code == UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_FORBIDDEN
@pytest.mark.django_db
@ -165,7 +166,9 @@ def test_notify_user_error_if_viewer(
make_alert_group,
):
organization = make_organization()
user_1 = make_user(organization=organization, role=Role.VIEWER, _verified_phone_number="1234567890")
user_1 = make_user(
organization=organization, role=LegacyAccessControlRole.VIEWER, _verified_phone_number="1234567890"
)
alert_receive_channel = make_alert_receive_channel(organization=organization)
alert_group = make_alert_group(alert_receive_channel=alert_receive_channel)
@ -173,8 +176,5 @@ def test_notify_user_error_if_viewer(
error_log_record = UserNotificationPolicyLogRecord.objects.last()
assert error_log_record.type == UserNotificationPolicyLogRecord.TYPE_PERSONAL_NOTIFICATION_FAILED
assert error_log_record.reason == f"notification is not allowed for user with role {user_1.role}"
assert (
error_log_record.notification_error_code
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE
)
assert error_log_record.reason == NOTIFICATION_UNAUTHORIZED_MSG
assert error_log_record.notification_error_code == UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_FORBIDDEN

View file

@ -1,5 +1,294 @@
from .actions import ActionPermission # noqa: F401
from .constants import ALL_BASE_ACTIONS, MODIFY_ACTIONS, READ_ACTIONS # noqa: F401
from .methods import MethodPermission # noqa: F401
from .owner import IsOwner, IsOwnerOrAdmin, IsOwnerOrAdminOrEditor # noqa: F401
from .roles import AnyRole, IsAdmin, IsAdminOrEditor, IsEditor, IsStaff, IsViewer # noqa: F401
import enum
import typing
from rest_framework import permissions
from rest_framework.authentication import BasicAuthentication, SessionAuthentication
from rest_framework.request import Request
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSet, ViewSetMixin
from common.utils import getattrd
ACTION_PREFIX = "grafana-oncall-app"
RBAC_PERMISSIONS_ATTR = "rbac_permissions"
RBAC_OBJECT_PERMISSIONS_ATTR = "rbac_object_permissions"
ViewSetOrAPIView = typing.Union[ViewSet, APIView]
class GrafanaAPIPermission(typing.TypedDict):
action: str
class Resources(enum.Enum):
ALERT_GROUPS = "alert-groups"
INTEGRATIONS = "integrations"
ESCALATION_CHAINS = "escalation-chains"
SCHEDULES = "schedules"
CHATOPS = "chatops"
OUTGOING_WEBHOOKS = "outgoing-webhooks"
MAINTENANCE = "maintenance"
API_KEYS = "api-keys"
NOTIFICATIONS = "notifications"
NOTIFICATION_SETTINGS = "notification-settings"
USER_SETTINGS = "user-settings"
OTHER_SETTINGS = "other-settings"
class Actions(enum.Enum):
READ = "read"
WRITE = "write"
ADMIN = "admin"
TEST = "test"
EXPORT = "export"
UPDATE_SETTINGS = "update-settings"
class LegacyAccessControlRole(enum.IntEnum):
ADMIN = 0
EDITOR = 1
VIEWER = 2
@classmethod
def choices(cls):
return tuple((option.value, option.name) for option in cls)
class LegacyAccessControlCompatiblePermission:
def __init__(self, resource: Resources, action: Actions, fallback_role: LegacyAccessControlRole) -> None:
self.value = f"{ACTION_PREFIX}.{resource.value}:{action.value}"
self.fallback_role = fallback_role
def get_most_authorized_role(
permissions: typing.List[LegacyAccessControlCompatiblePermission],
) -> LegacyAccessControlRole:
if not permissions:
return LegacyAccessControlRole.VIEWER
# ex. Admin is 0, Viewer is 2, thereby min makes sense here
return min({p.fallback_role for p in permissions}, key=lambda r: r.value)
def user_is_authorized(user, required_permissions: typing.List[LegacyAccessControlCompatiblePermission]) -> bool:
if user.organization.is_rbac_permissions_enabled:
user_permissions = [u["action"] for u in user.permissions]
required_permissions = [p.value for p in required_permissions]
return all(permission in user_permissions for permission in required_permissions)
return user.role <= get_most_authorized_role(required_permissions).value
class RBACPermission(permissions.BasePermission):
class Permissions:
ALERT_GROUPS_READ = LegacyAccessControlCompatiblePermission(
Resources.ALERT_GROUPS, Actions.READ, LegacyAccessControlRole.VIEWER
)
ALERT_GROUPS_WRITE = LegacyAccessControlCompatiblePermission(
Resources.ALERT_GROUPS, Actions.WRITE, LegacyAccessControlRole.EDITOR
)
INTEGRATIONS_READ = LegacyAccessControlCompatiblePermission(
Resources.INTEGRATIONS, Actions.READ, LegacyAccessControlRole.VIEWER
)
INTEGRATIONS_TEST = LegacyAccessControlCompatiblePermission(
Resources.INTEGRATIONS, Actions.TEST, LegacyAccessControlRole.EDITOR
)
INTEGRATIONS_WRITE = LegacyAccessControlCompatiblePermission(
Resources.INTEGRATIONS, Actions.WRITE, LegacyAccessControlRole.ADMIN
)
ESCALATION_CHAINS_READ = LegacyAccessControlCompatiblePermission(
Resources.ESCALATION_CHAINS, Actions.READ, LegacyAccessControlRole.VIEWER
)
ESCALATION_CHAINS_WRITE = LegacyAccessControlCompatiblePermission(
Resources.ESCALATION_CHAINS, Actions.WRITE, LegacyAccessControlRole.ADMIN
)
SCHEDULES_READ = LegacyAccessControlCompatiblePermission(
Resources.SCHEDULES, Actions.READ, LegacyAccessControlRole.VIEWER
)
SCHEDULES_WRITE = LegacyAccessControlCompatiblePermission(
Resources.SCHEDULES, Actions.WRITE, LegacyAccessControlRole.ADMIN
)
SCHEDULES_EXPORT = LegacyAccessControlCompatiblePermission(
Resources.SCHEDULES, Actions.EXPORT, LegacyAccessControlRole.EDITOR
)
CHATOPS_READ = LegacyAccessControlCompatiblePermission(
Resources.CHATOPS, Actions.READ, LegacyAccessControlRole.VIEWER
)
CHATOPS_WRITE = LegacyAccessControlCompatiblePermission(
Resources.CHATOPS, Actions.WRITE, LegacyAccessControlRole.EDITOR
)
CHATOPS_UPDATE_SETTINGS = LegacyAccessControlCompatiblePermission(
Resources.CHATOPS, Actions.UPDATE_SETTINGS, LegacyAccessControlRole.ADMIN
)
OUTGOING_WEBHOOKS_READ = LegacyAccessControlCompatiblePermission(
Resources.OUTGOING_WEBHOOKS, Actions.READ, LegacyAccessControlRole.VIEWER
)
OUTGOING_WEBHOOKS_WRITE = LegacyAccessControlCompatiblePermission(
Resources.OUTGOING_WEBHOOKS, Actions.WRITE, LegacyAccessControlRole.ADMIN
)
MAINTENANCE_READ = LegacyAccessControlCompatiblePermission(
Resources.MAINTENANCE, Actions.READ, LegacyAccessControlRole.VIEWER
)
MAINTENANCE_WRITE = LegacyAccessControlCompatiblePermission(
Resources.MAINTENANCE, Actions.WRITE, LegacyAccessControlRole.EDITOR
)
API_KEYS_READ = LegacyAccessControlCompatiblePermission(
Resources.API_KEYS, Actions.READ, LegacyAccessControlRole.VIEWER
)
API_KEYS_WRITE = LegacyAccessControlCompatiblePermission(
Resources.API_KEYS, Actions.WRITE, LegacyAccessControlRole.EDITOR
)
NOTIFICATIONS_READ = LegacyAccessControlCompatiblePermission(
Resources.NOTIFICATIONS, Actions.READ, LegacyAccessControlRole.EDITOR
)
NOTIFICATION_SETTINGS_READ = LegacyAccessControlCompatiblePermission(
Resources.NOTIFICATION_SETTINGS, Actions.READ, LegacyAccessControlRole.VIEWER
)
NOTIFICATION_SETTINGS_WRITE = LegacyAccessControlCompatiblePermission(
Resources.NOTIFICATION_SETTINGS, Actions.WRITE, LegacyAccessControlRole.EDITOR
)
USER_SETTINGS_READ = LegacyAccessControlCompatiblePermission(
Resources.USER_SETTINGS, Actions.READ, LegacyAccessControlRole.VIEWER
)
USER_SETTINGS_WRITE = LegacyAccessControlCompatiblePermission(
Resources.USER_SETTINGS, Actions.WRITE, LegacyAccessControlRole.EDITOR
)
USER_SETTINGS_ADMIN = LegacyAccessControlCompatiblePermission(
Resources.USER_SETTINGS, Actions.ADMIN, LegacyAccessControlRole.ADMIN
)
OTHER_SETTINGS_READ = LegacyAccessControlCompatiblePermission(
Resources.OTHER_SETTINGS, Actions.READ, LegacyAccessControlRole.VIEWER
)
OTHER_SETTINGS_WRITE = LegacyAccessControlCompatiblePermission(
Resources.OTHER_SETTINGS, Actions.WRITE, LegacyAccessControlRole.ADMIN
)
@staticmethod
def _get_view_action(request: Request, view: ViewSetOrAPIView) -> str:
"""
For right now this needs to support being used in both a ViewSet as well as APIView, we use both interchangably
Note: `request.method` is returned uppercase
"""
return view.action if isinstance(view, ViewSetMixin) else request.method.lower()
def has_permission(self, request: Request, view: ViewSetOrAPIView) -> bool:
action = self._get_view_action(request, view)
rbac_permissions: RBACPermissionsAttribute = getattr(view, RBAC_PERMISSIONS_ATTR, None)
# first check that the rbac_permissions dict attribute is defined
assert (
rbac_permissions is not None
), f"Must define a {RBAC_PERMISSIONS_ATTR} dict on the ViewSet that is consuming the RBACPermission class"
action_required_permissions: typing.Union[None, typing.List] = rbac_permissions.get(action, None)
# next check that the action in question is defined within the rbac_permissions dict attribute
assert (
action_required_permissions is not None
), f"""Each action must be defined within the {RBAC_PERMISSIONS_ATTR} dict on the ViewSet.
\nIf an action requires no permissions, its value should explicitly be set to an empty list"""
return user_is_authorized(request.user, action_required_permissions)
def has_object_permission(self, request: Request, view: ViewSetOrAPIView, obj: typing.Any) -> bool:
rbac_object_permissions: RBACObjectPermissionsAttribute = getattr(view, RBAC_OBJECT_PERMISSIONS_ATTR, None)
if rbac_object_permissions:
action = self._get_view_action(request, view)
for permission_class, actions in rbac_object_permissions.items():
if action in actions:
return permission_class.has_object_permission(request, view, obj)
return False
# has_object_permission is called after has_permission, so return True if in view there is not
# RBAC_OBJECT_PERMISSIONS_ATTR attr which mean no additional check involving object required
return True
class IsOwner(permissions.BasePermission):
def __init__(self, ownership_field: typing.Optional[str] = None) -> None:
self.ownership_field = ownership_field
def has_object_permission(self, request: Request, _view: ViewSet, obj: typing.Any) -> bool:
owner = obj if self.ownership_field is None else getattrd(obj, self.ownership_field)
return owner == request.user
class HasRBACPermissions(permissions.BasePermission):
def __init__(self, required_permissions: typing.List[LegacyAccessControlCompatiblePermission]) -> None:
self.required_permissions = required_permissions
def has_object_permission(self, request: Request, _view: ViewSetOrAPIView, _obj: typing.Any) -> bool:
return user_is_authorized(request.user, self.required_permissions)
class IsOwnerOrHasRBACPermissions(permissions.BasePermission):
def __init__(
self,
required_permissions: typing.List[LegacyAccessControlCompatiblePermission],
ownership_field: typing.Optional[str] = None,
) -> None:
self.IsOwner = IsOwner(ownership_field)
self.HasRBACPermissions = HasRBACPermissions(required_permissions)
def has_object_permission(self, request: Request, view: ViewSetOrAPIView, obj: typing.Any) -> bool:
return self.IsOwner.has_object_permission(request, view, obj) or self.HasRBACPermissions.has_object_permission(
request, view, obj
)
class IsStaff(permissions.BasePermission):
STAFF_AUTH_CLASSES = [BasicAuthentication, SessionAuthentication]
def has_permission(self, request: Request, _view: ViewSet) -> bool:
user = request.user
if not any(isinstance(request._authenticator, x) for x in self.STAFF_AUTH_CLASSES):
return False
if user and user.is_authenticated:
return user.is_staff
return False
RBACPermissionsAttribute = typing.Dict[str, typing.List[LegacyAccessControlCompatiblePermission]]
RBACObjectPermissionsAttribute = typing.Dict[permissions.BasePermission, typing.List[str]]
# The below is legacy, it is only needed currently for backward compatibility w/ users running
# older "pinned" version of Grafana in Grafana Cloud
_DONT_USE_LEGACY_VIEWER_PERMISSIONS = []
_DONT_USE_LEGACY_EDITOR_PERMISSIONS = ["update_incidents", "update_own_settings", "view_other_users"]
_DONT_USE_LEGACY_ADMIN_PERMISSIONS = _DONT_USE_LEGACY_EDITOR_PERMISSIONS + [
"update_alert_receive_channels",
"update_escalation_policies",
"update_notification_policies",
"update_general_log_channel_id",
"update_other_users_settings",
"update_integrations",
"update_schedules",
"update_custom_actions",
"update_api_tokens",
"update_teams",
"update_maintenances",
"update_global_settings",
"send_demo_alert",
]
DONT_USE_LEGACY_PERMISSION_MAPPING: typing.Dict[LegacyAccessControlRole, typing.List[str]] = {
LegacyAccessControlRole.VIEWER: _DONT_USE_LEGACY_VIEWER_PERMISSIONS,
LegacyAccessControlRole.EDITOR: _DONT_USE_LEGACY_EDITOR_PERMISSIONS,
LegacyAccessControlRole.ADMIN: _DONT_USE_LEGACY_ADMIN_PERMISSIONS,
}

View file

@ -1,27 +0,0 @@
from typing import Any
from rest_framework import permissions
from rest_framework.request import Request
from rest_framework.viewsets import ViewSet
class ActionPermission(permissions.BasePermission):
def has_permission(self, request: Request, view: ViewSet) -> bool:
for permission, actions in getattr(view, "action_permissions", {}).items():
if view.action in actions:
return permission().has_permission(request, view)
return False
def has_object_permission(self, request: Request, view: ViewSet, obj: Any) -> bool:
# action_object_permissions attr should be used in case permission check require lookup
# for some object's properties e.g. team.
if getattr(view, "action_object_permissions", None):
for permission, actions in getattr(view, "action_object_permissions", {}).items():
if view.action in actions:
return permission().has_object_permission(request, view, obj)
return False
else:
# has_object_permission is called after has_permission, so return True if in view there is not
# action_object_permission attr which mean no additional check involving object required
return True

View file

@ -1,14 +0,0 @@
READ_ACTIONS = (
"list",
"retrieve",
"metadata",
)
MODIFY_ACTIONS = (
"create",
"update",
"partial_update",
"destroy",
)
ALL_BASE_ACTIONS = READ_ACTIONS + MODIFY_ACTIONS

View file

@ -1,12 +0,0 @@
from rest_framework import permissions
from rest_framework.request import Request
from rest_framework.viewsets import ViewSet
class MethodPermission(permissions.BasePermission):
def has_permission(self, request: Request, view: ViewSet) -> bool:
for permission, methods in getattr(view, "method_permissions", {}).items():
if request.method in methods:
return permission().has_permission(request, view)
return False

View file

@ -1,24 +0,0 @@
from typing import Any
from rest_framework import permissions
from rest_framework.request import Request
from rest_framework.viewsets import ViewSet
from apps.api.permissions.roles import IsAdmin, IsEditor
from common.utils import getattrd
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request: Request, view: ViewSet, obj: Any) -> bool:
ownership_field = getattr(view, "ownership_field", None)
if ownership_field is None:
owner = obj
else:
owner = getattrd(obj, ownership_field)
return owner == request.user
IsOwnerOrAdmin = IsOwner | IsAdmin
IsOwnerOrAdminOrEditor = IsOwner | IsAdmin | IsEditor

View file

@ -1,49 +0,0 @@
from typing import Any
from rest_framework import permissions
from rest_framework.authentication import BasicAuthentication, SessionAuthentication
from rest_framework.request import Request
from rest_framework.viewsets import ViewSet
from common.constants.role import Role
class RolePermission(permissions.BasePermission):
ROLE = None
def has_permission(self, request: Request, view: ViewSet) -> bool:
return request.user.role == type(self).ROLE
def has_object_permission(self, request: Request, view: ViewSet, obj: Any) -> bool:
return self.has_permission(request, view)
class IsAdmin(RolePermission):
ROLE = Role.ADMIN
class IsEditor(RolePermission):
ROLE = Role.EDITOR
class IsViewer(RolePermission):
ROLE = Role.VIEWER
IsAdminOrEditor = IsAdmin | IsEditor
AnyRole = IsAdmin | IsEditor | IsViewer
class IsStaff(permissions.BasePermission):
STAFF_AUTH_CLASSES = [BasicAuthentication, SessionAuthentication]
def has_permission(self, request: Request, view: ViewSet) -> bool:
user = request.user
if not any(isinstance(request._authenticator, x) for x in self.STAFF_AUTH_CLASSES):
return False
if user and user.is_authenticated:
return user.is_staff
return False
def has_object_permission(self, request: Request, view: ViewSet, obj: Any) -> bool:
return self.has_permission(request, view)

View file

@ -0,0 +1,428 @@
import typing
import pytest
from rest_framework.views import APIView
from rest_framework.viewsets import ViewSetMixin
from . import (
RBAC_PERMISSIONS_ATTR,
GrafanaAPIPermission,
HasRBACPermissions,
IsOwner,
IsOwnerOrHasRBACPermissions,
LegacyAccessControlCompatiblePermission,
RBACObjectPermissionsAttribute,
RBACPermission,
RBACPermissionsAttribute,
get_most_authorized_role,
user_is_authorized,
)
class MockedOrg:
def __init__(self, org_has_rbac_enabled: bool) -> None:
self.is_rbac_permissions_enabled = org_has_rbac_enabled
class MockedUser:
def __init__(
self, permissions: typing.List[LegacyAccessControlCompatiblePermission], org_has_rbac_enabled=True
) -> None:
self.permissions = [GrafanaAPIPermission(action=perm.value) for perm in permissions]
self.role = get_most_authorized_role(permissions)
self.organization = MockedOrg(org_has_rbac_enabled)
class MockedSchedule:
def __init__(self, user: MockedUser) -> None:
self.user = user
class MockedRequest:
def __init__(self, user: typing.Optional[MockedUser] = None, method: typing.Optional[str] = None) -> None:
if user:
self.user = user
if method:
self.method = method
class MockedViewSet(ViewSetMixin):
def __init__(
self,
action: str,
rbac_permissions: typing.Optional[RBACPermissionsAttribute] = None,
rbac_object_permissions: typing.Optional[RBACObjectPermissionsAttribute] = None,
) -> None:
super().__init__()
self.action = action
if rbac_permissions:
self.rbac_permissions = rbac_permissions
if rbac_object_permissions:
self.rbac_object_permissions = rbac_object_permissions
class MockedAPIView(APIView):
def __init__(
self,
rbac_permissions: typing.Optional[RBACPermissionsAttribute] = None,
rbac_object_permissions: typing.Optional[RBACObjectPermissionsAttribute] = None,
) -> None:
super().__init__()
if rbac_permissions:
self.rbac_permissions = rbac_permissions
if rbac_object_permissions:
self.rbac_object_permissions = rbac_object_permissions
@pytest.mark.parametrize(
"user_permissions,required_permissions,org_has_rbac_enabled,expected_result",
[
(
[RBACPermission.Permissions.ALERT_GROUPS_READ],
[RBACPermission.Permissions.ALERT_GROUPS_READ],
True,
True,
),
(
[RBACPermission.Permissions.ALERT_GROUPS_READ],
[RBACPermission.Permissions.ALERT_GROUPS_READ],
False,
True,
),
(
[RBACPermission.Permissions.ALERT_GROUPS_READ, RBACPermission.Permissions.ALERT_GROUPS_WRITE],
[RBACPermission.Permissions.ALERT_GROUPS_READ, RBACPermission.Permissions.ALERT_GROUPS_WRITE],
True,
True,
),
(
[RBACPermission.Permissions.ALERT_GROUPS_READ, RBACPermission.Permissions.ALERT_GROUPS_WRITE],
[RBACPermission.Permissions.ALERT_GROUPS_READ, RBACPermission.Permissions.ALERT_GROUPS_WRITE],
False,
True,
),
(
[RBACPermission.Permissions.ALERT_GROUPS_WRITE],
[RBACPermission.Permissions.ALERT_GROUPS_READ],
True,
False,
),
(
[RBACPermission.Permissions.ALERT_GROUPS_WRITE],
[RBACPermission.Permissions.ALERT_GROUPS_READ],
False,
True,
),
(
[RBACPermission.Permissions.ALERT_GROUPS_READ],
[RBACPermission.Permissions.ALERT_GROUPS_READ, RBACPermission.Permissions.ALERT_GROUPS_WRITE],
False,
False,
),
(
[RBACPermission.Permissions.ALERT_GROUPS_READ],
[RBACPermission.Permissions.ALERT_GROUPS_READ, RBACPermission.Permissions.ALERT_GROUPS_WRITE],
True,
False,
),
],
)
def test_user_is_authorized(user_permissions, required_permissions, org_has_rbac_enabled, expected_result) -> None:
user = MockedUser(user_permissions, org_has_rbac_enabled=org_has_rbac_enabled)
assert user_is_authorized(user, required_permissions) == expected_result
@pytest.mark.parametrize(
"permissions,expected_role",
[
([RBACPermission.Permissions.ALERT_GROUPS_READ], RBACPermission.Permissions.ALERT_GROUPS_READ.fallback_role),
(
[RBACPermission.Permissions.ALERT_GROUPS_READ, RBACPermission.Permissions.ALERT_GROUPS_WRITE],
RBACPermission.Permissions.ALERT_GROUPS_WRITE.fallback_role,
),
(
[
RBACPermission.Permissions.USER_SETTINGS_READ,
RBACPermission.Permissions.USER_SETTINGS_WRITE,
RBACPermission.Permissions.USER_SETTINGS_ADMIN,
],
RBACPermission.Permissions.USER_SETTINGS_ADMIN.fallback_role,
),
],
)
def test_get_most_authorized_role(permissions, expected_role) -> None:
assert get_most_authorized_role(permissions) == expected_role
class TestRBACPermission:
def test_get_view_action(self) -> None:
viewset_action = "viewset_action"
viewset = MockedViewSet(viewset_action)
apiview = MockedAPIView()
method = "APIVIEW_ACTION"
request = MockedRequest(method=method)
assert RBACPermission._get_view_action(request, viewset) == viewset_action, "it works with a ViewSet"
assert RBACPermission._get_view_action(request, apiview) == method.lower(), "it works with an APIView"
def test_has_permission_works_on_a_viewset_view(self) -> None:
required_permission = RBACPermission.Permissions.ALERT_GROUPS_READ
action = "hello"
viewset = MockedViewSet(
action=action,
rbac_permissions={
action: [required_permission],
},
)
viewset_with_no_required_permissions = MockedViewSet(
action=action,
rbac_permissions={
action: [],
},
)
user_with_permission = MockedUser([required_permission])
user_without_permission = MockedUser([RBACPermission.Permissions.ALERT_GROUPS_WRITE])
assert (
RBACPermission().has_permission(MockedRequest(user_with_permission), viewset) is True
), "it works on a viewset when the user does have permission"
assert (
RBACPermission().has_permission(MockedRequest(user_without_permission), viewset) is False
), "it works on a viewset when the user does have permission"
assert (
RBACPermission().has_permission(
MockedRequest(user_without_permission), viewset_with_no_required_permissions
)
is True
), "it works on a viewset when the viewset action does not require permissions"
def test_has_permission_works_on_an_apiview_view(self) -> None:
required_permission = RBACPermission.Permissions.ALERT_GROUPS_READ
method = "hello"
apiview = MockedAPIView(
rbac_permissions={
method: [required_permission],
}
)
apiview_with_no_permissions = MockedAPIView(
rbac_permissions={
method: [],
}
)
user1 = MockedUser([required_permission])
user2 = MockedUser([RBACPermission.Permissions.ALERT_GROUPS_WRITE])
class Request(MockedRequest):
def __init__(self, user: typing.Optional[MockedUser] = None) -> None:
super().__init__(user, method)
assert (
RBACPermission().has_permission(Request(user1), apiview) is True
), "it works on an APIView when the user has permission"
assert (
RBACPermission().has_permission(Request(user2), apiview) is False
), "it works on an APIView when the user does not have permission"
assert (
RBACPermission().has_permission(Request(user2), apiview_with_no_permissions) is True
), "it works on a viewset when the viewset action does not require permissions"
def test_has_permission_throws_assertion_error_if_developer_forgets_to_specify_rbac_permissions(self) -> None:
action_slash_method = "hello"
error_msg = (
f"Must define a {RBAC_PERMISSIONS_ATTR} dict on the ViewSet that is consuming the RBACPermission class"
)
viewset = MockedViewSet(action_slash_method)
apiview = MockedAPIView()
with pytest.raises(AssertionError, match=error_msg):
RBACPermission().has_permission(MockedRequest(), viewset)
with pytest.raises(AssertionError, match=error_msg):
RBACPermission().has_permission(MockedRequest(method=action_slash_method), apiview)
def test_has_permission_throws_assertion_error_if_developer_forgets_to_specify_an_action_in_rbac_permissions(
self,
) -> None:
action_slash_method = "hello"
other_action_rbac_permissions = {"bonjour": []}
error_msg = f"""Each action must be defined within the {RBAC_PERMISSIONS_ATTR} dict on the ViewSet.
\nIf an action requires no permissions, its value should explicitly be set to an empty list"""
viewset = MockedViewSet(action_slash_method, other_action_rbac_permissions)
apiview = MockedAPIView(rbac_permissions=other_action_rbac_permissions)
with pytest.raises(AssertionError, match=error_msg):
RBACPermission().has_permission(MockedRequest(), viewset)
with pytest.raises(AssertionError, match=error_msg):
RBACPermission().has_permission(MockedRequest(method=action_slash_method), apiview)
def test_has_object_permission_returns_true_if_rbac_object_permissions_not_specified(self) -> None:
request = MockedRequest()
assert RBACPermission().has_object_permission(request, MockedAPIView(), None) is True
assert RBACPermission().has_object_permission(request, MockedViewSet("potato"), None) is True
def test_has_object_permission_works_if_no_permission_class_specified_for_action(self) -> None:
action = "hello"
request = MockedRequest(None, action)
apiview = MockedAPIView(rbac_object_permissions={})
viewset = MockedViewSet(action, rbac_object_permissions={})
assert RBACPermission().has_object_permission(request, apiview, None) is True
assert RBACPermission().has_object_permission(request, viewset, None) is True
def test_has_object_permission_works_when_permission_class_specified_for_action(self) -> None:
action = "hello"
mocked_permission_class_response = "asdfasdfasdf"
class MockedPermissionClass:
def has_object_permission(self, _req, _view, _obj) -> None:
return mocked_permission_class_response
rbac_object_permissions = {MockedPermissionClass(): (action,)}
request = MockedRequest(None, action)
apiview = MockedAPIView(rbac_object_permissions=rbac_object_permissions)
viewset = MockedViewSet(action, rbac_object_permissions=rbac_object_permissions)
assert RBACPermission().has_object_permission(request, apiview, None) == mocked_permission_class_response
assert RBACPermission().has_object_permission(request, viewset, None) == mocked_permission_class_response
class TestIsOwner:
def test_it_works_when_comparing_user_to_object(self) -> None:
user1 = MockedUser([])
user2 = MockedUser([])
request = MockedRequest(user1)
IsUser = IsOwner()
assert IsUser.has_object_permission(request, None, user1) is True
assert IsUser.has_object_permission(request, None, user2) is False
def test_it_works_when_comparing_user_to_ownership_field_object(self) -> None:
user1 = MockedUser([])
user2 = MockedUser([])
schedule = MockedSchedule(user1)
IsScheduleOwner = IsOwner("user")
assert IsScheduleOwner.has_object_permission(MockedRequest(user1), None, schedule) is True
assert IsScheduleOwner.has_object_permission(MockedRequest(user2), None, schedule) is False
def test_it_works_when_comparing_user_to_nested_ownership_field_object(self) -> None:
class Thingy:
def __init__(self, schedule: MockedSchedule) -> None:
self.schedule = schedule
user1 = MockedUser([])
user2 = MockedUser([])
schedule = MockedSchedule(user1)
thingy = Thingy(schedule)
IsScheduleOwner = IsOwner("schedule.user")
assert IsScheduleOwner.has_object_permission(MockedRequest(user1), None, thingy) is True
assert IsScheduleOwner.has_object_permission(MockedRequest(user2), None, thingy) is False
@pytest.mark.parametrize(
"user_permissions,required_permissions,expected_result",
[
(
[RBACPermission.Permissions.ALERT_GROUPS_READ],
[RBACPermission.Permissions.ALERT_GROUPS_READ],
True,
),
(
[RBACPermission.Permissions.ALERT_GROUPS_READ, RBACPermission.Permissions.ALERT_GROUPS_WRITE],
[RBACPermission.Permissions.ALERT_GROUPS_READ, RBACPermission.Permissions.ALERT_GROUPS_WRITE],
True,
),
(
[RBACPermission.Permissions.ALERT_GROUPS_WRITE],
[RBACPermission.Permissions.ALERT_GROUPS_READ],
False,
),
(
[RBACPermission.Permissions.ALERT_GROUPS_READ],
[RBACPermission.Permissions.ALERT_GROUPS_READ, RBACPermission.Permissions.ALERT_GROUPS_WRITE],
False,
),
],
)
def test_HasRBACPermission(user_permissions, required_permissions, expected_result) -> None:
request = MockedRequest(MockedUser(user_permissions))
assert HasRBACPermissions(required_permissions).has_object_permission(request, None, None) == expected_result
class TestIsOwnerOrHasRBACPermissions:
required_permission = RBACPermission.Permissions.SCHEDULES_READ
required_permissions = [required_permission]
def test_it_works_when_user_is_owner_and_does_not_have_permissions(self) -> None:
user1 = MockedUser([])
schedule = MockedSchedule(user1)
request = MockedRequest(user1)
PermClass = IsOwnerOrHasRBACPermissions(self.required_permissions)
assert PermClass.has_object_permission(request, None, user1) is True
PermClass = IsOwnerOrHasRBACPermissions(self.required_permissions, "user")
assert PermClass.has_object_permission(request, None, schedule) is True
def test_it_works_when_user_is_owner_and_has_permissions(self) -> None:
user1 = MockedUser(self.required_permissions)
schedule = MockedSchedule(user1)
request = MockedRequest(user1)
PermClass = IsOwnerOrHasRBACPermissions(self.required_permissions)
assert PermClass.has_object_permission(request, None, user1) is True
PermClass = IsOwnerOrHasRBACPermissions(self.required_permissions, "user")
assert PermClass.has_object_permission(request, None, schedule) is True
def test_it_works_when_user_is_not_owner_and_does_not_have_permissions(self) -> None:
user1 = MockedUser([])
user2 = MockedUser([])
schedule = MockedSchedule(user1)
request = MockedRequest(user2)
PermClass = IsOwnerOrHasRBACPermissions(self.required_permissions)
assert PermClass.has_object_permission(request, None, user1) is False
PermClass = IsOwnerOrHasRBACPermissions(self.required_permissions, "user")
assert PermClass.has_object_permission(request, None, schedule) is False
def test_it_works_when_user_is_not_owner_and_has_permissions(self) -> None:
user1 = MockedUser([])
user2 = MockedUser(self.required_permissions)
schedule = MockedSchedule(user1)
request = MockedRequest(user2)
PermClass = IsOwnerOrHasRBACPermissions(self.required_permissions)
assert PermClass.has_object_permission(request, None, user1) is True
PermClass = IsOwnerOrHasRBACPermissions(self.required_permissions, "user")
assert PermClass.has_object_permission(request, None, schedule) is True
class Thingy:
def __init__(self, schedule: MockedSchedule) -> None:
self.schedule = schedule
thingy = Thingy(schedule)
PermClass = IsOwnerOrHasRBACPermissions(self.required_permissions, "schedule.user")
assert PermClass.has_object_permission(request, None, thingy) is True
assert PermClass.has_object_permission(MockedRequest(MockedUser([])), None, thingy) is False

View file

@ -1,12 +1,13 @@
import math
import time
import typing
import pytz
from django.conf import settings
from rest_framework import serializers
from apps.api.permissions import DONT_USE_LEGACY_PERMISSION_MAPPING
from apps.api.serializers.telegram import TelegramToUserConnectorSerializer
from apps.base.constants import ADMIN_PERMISSIONS, ALL_ROLES_PERMISSIONS, EDITOR_PERMISSIONS
from apps.base.messaging import get_messaging_backends
from apps.base.models import UserNotificationPolicy
from apps.base.utils import live_settings
@ -16,7 +17,6 @@ from apps.user_management.models import User
from apps.user_management.models.user import default_working_hours
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
from common.api_helpers.mixins import EagerLoadingMixin
from common.constants.role import Role
from .custom_serializers import DynamicFieldsModelSerializer
from .organization import FastOrganizationSerializer
@ -52,7 +52,7 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
"email",
"username",
"name",
"role",
"role", # LEGACY.. this should get removed eventually
"avatar",
"avatar_full",
"timezone",
@ -62,7 +62,7 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
"slack_user_identity",
"telegram_configuration",
"messaging_backends",
"permissions",
"permissions", # LEGACY.. this should get removed eventually
"notification_chain_verbal",
"cloud_connection_status",
"hide_phone_number",
@ -71,7 +71,7 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
"email",
"username",
"name",
"role",
"role", # LEGACY.. this should get removed eventually
"verified_phone_number",
]
@ -139,13 +139,8 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
serialized_data[backend_id] = backend.serialize_user(obj)
return serialized_data
def get_permissions(self, obj):
if obj.role == Role.ADMIN:
return ADMIN_PERMISSIONS
elif obj.role == Role.EDITOR:
return EDITOR_PERMISSIONS
else:
return ALL_ROLES_PERMISSIONS
def get_permissions(self, obj) -> typing.List[str]:
return DONT_USE_LEGACY_PERMISSION_MAPPING[obj.role]
def get_notification_chain_verbal(self, obj):
default, important = UserNotificationPolicy.get_short_verbals_for_user(user=obj)
@ -180,7 +175,7 @@ class UserSerializer(DynamicFieldsModelSerializer, EagerLoadingMixin):
class UserHiddenFieldsSerializer(UserSerializer):
available_for_all_roles_fields = [
fields_available_for_all_users = [
"pk",
"organization",
"current_team",
@ -196,7 +191,7 @@ class UserHiddenFieldsSerializer(UserSerializer):
ret = super(UserSerializer, self).to_representation(instance)
if instance.id != self.context["request"].user.id:
for field in ret:
if field not in self.available_for_all_roles_fields:
if field not in self.fields_available_for_all_users:
ret[field] = "******"
ret["hidden_fields"] = True
return ret

View file

@ -9,7 +9,7 @@ from rest_framework.response import Response
from rest_framework.test import APIClient
from apps.alerts.models import AlertGroup, AlertGroupLogRecord
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
alert_raw_request_data = {
"evalMatches": [
@ -25,15 +25,6 @@ alert_raw_request_data = {
}
# # This function is for creating token and do not to change fixture alert_group_internal_api_setup return values.
# # To create token amixr team is needed but in most tests using fixture alert_group_internal_api_setup team is redundant
# # So it just extract amixr team form alert_groups.
# def create_token_from_initial_test_data(make_func, alert_groups, role):
# organization = alert_groups[0].channel.organization
# _, token_user_role = make_func(organization, role)
# return token_user_role
@pytest.fixture()
def alert_group_internal_api_setup(
make_organization_and_user_with_plugin_token,
@ -52,7 +43,7 @@ def alert_group_internal_api_setup(
@pytest.mark.django_db
def test_get_filter_started_at(alert_group_internal_api_setup, make_user_auth_headers):
user, token, alert_groups = alert_group_internal_api_setup
user, token, _ = alert_group_internal_api_setup
client = APIClient()
url = reverse("api-internal:alertgroup-list")
@ -69,7 +60,7 @@ def test_get_filter_started_at(alert_group_internal_api_setup, make_user_auth_he
@pytest.mark.django_db
def test_get_filter_resolved_at_alertgroup_empty_result(alert_group_internal_api_setup, make_user_auth_headers):
client = APIClient()
user, token, alert_groups = alert_group_internal_api_setup
user, token, _ = alert_group_internal_api_setup
url = reverse("api-internal:alertgroup-list")
response = client.get(
@ -84,7 +75,7 @@ def test_get_filter_resolved_at_alertgroup_empty_result(alert_group_internal_api
@pytest.mark.django_db
def test_get_filter_resolved_at_alertgroup_invalid_format(alert_group_internal_api_setup, make_user_auth_headers):
client = APIClient()
user, token, alert_groups = alert_group_internal_api_setup
user, token, _ = alert_group_internal_api_setup
url = reverse("api-internal:alertgroup-list")
response = client.get(
@ -660,19 +651,25 @@ def test_get_filter_with_resolution_note_after_delete_resolution_note(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_group_acknowledge_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-acknowledge", kwargs={"pk": new_alert_group.public_primary_key})
with patch(
@ -689,19 +686,24 @@ def test_alert_group_acknowledge_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_group_unacknowledge_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-unacknowledge", kwargs={"pk": new_alert_group.public_primary_key})
with patch(
@ -718,19 +720,24 @@ def test_alert_group_unacknowledge_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_group_resolve_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-resolve", kwargs={"pk": new_alert_group.public_primary_key})
with patch(
@ -747,19 +754,24 @@ def test_alert_group_resolve_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_group_unresolve_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-unresolve", kwargs={"pk": new_alert_group.public_primary_key})
with patch(
@ -776,19 +788,24 @@ def test_alert_group_unresolve_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_group_silence_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-silence", kwargs={"pk": new_alert_group.public_primary_key})
with patch(
@ -805,19 +822,24 @@ def test_alert_group_silence_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_group_unsilence_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-unsilence", kwargs={"pk": new_alert_group.public_primary_key})
with patch(
@ -834,19 +856,24 @@ def test_alert_group_unsilence_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_group_attach_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-attach", kwargs={"pk": new_alert_group.public_primary_key})
with patch(
@ -863,19 +890,24 @@ def test_alert_group_attach_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_group_unattach_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-unattach", kwargs={"pk": new_alert_group.public_primary_key})
with patch(
@ -892,19 +924,24 @@ def test_alert_group_unattach_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_alert_group_list_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-list")
with patch(
@ -921,19 +958,24 @@ def test_alert_group_list_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_alert_group_stats_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-stats")
with patch(
@ -950,19 +992,24 @@ def test_alert_group_stats_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_group_bulk_action_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-bulk-action")
with patch(
@ -977,19 +1024,24 @@ def test_alert_group_bulk_action_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_alert_group_filters_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-filters")
with patch(
@ -1006,19 +1058,24 @@ def test_alert_group_filters_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_alert_group_detail_permissions(
make_user_for_organization, alert_group_internal_api_setup, make_user_auth_headers, role, expected_status
alert_group_internal_api_setup,
make_user_for_organization,
make_user_auth_headers,
role,
expected_status,
):
client = APIClient()
_, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
organization = new_alert_group.channel.organization
user = make_user_for_organization(organization, role)
client = APIClient()
url = reverse("api-internal:alertgroup-detail", kwargs={"pk": new_alert_group.public_primary_key})
with patch(
@ -1032,10 +1089,7 @@ def test_alert_group_detail_permissions(
@pytest.mark.django_db
def test_silence(
alert_group_internal_api_setup,
make_user_auth_headers,
):
def test_silence(alert_group_internal_api_setup, make_user_auth_headers):
client = APIClient()
user, token, alert_groups = alert_group_internal_api_setup
_, _, new_alert_group, _ = alert_groups
@ -1396,9 +1450,9 @@ def test_alert_group_status_field(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_group_preview_template_permissions(
@ -1414,6 +1468,7 @@ def test_alert_group_preview_template_permissions(
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.example_payload)
client = APIClient()
url = reverse("api-internal:alertgroup-preview-template", kwargs={"pk": alert_group.public_primary_key})
@ -1436,7 +1491,7 @@ def test_alert_group_preview_body_non_existent_template_var(
make_alert_group,
make_alert,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=Role.ADMIN)
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.example_payload)
@ -1459,7 +1514,7 @@ def test_alert_group_preview_body_invalid_template_syntax(
make_alert_group,
make_alert,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=Role.ADMIN)
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_alert(alert_group=alert_group, raw_request_data=alert_receive_channel.config.example_payload)

View file

@ -8,7 +8,7 @@ from rest_framework.response import Response
from rest_framework.test import APIClient
from apps.alerts.models import AlertReceiveChannel, EscalationPolicy
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.fixture()
@ -205,9 +205,9 @@ def test_integration_search(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_receive_channel_create_permissions(
@ -235,9 +235,9 @@ def test_alert_receive_channel_create_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_receive_channel_update_permissions(
@ -272,9 +272,9 @@ def test_alert_receive_channel_update_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_204_NO_CONTENT),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_receive_channel_delete_permissions(
@ -303,7 +303,11 @@ def test_alert_receive_channel_delete_permissions(
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[(Role.ADMIN, status.HTTP_200_OK), (Role.EDITOR, status.HTTP_200_OK), (Role.VIEWER, status.HTTP_200_OK)],
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_alert_receive_channel_list_permissions(
make_organization_and_user_with_plugin_token,
@ -311,7 +315,7 @@ def test_alert_receive_channel_list_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:alert_receive_channel-list")
@ -330,7 +334,11 @@ def test_alert_receive_channel_list_permissions(
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[(Role.ADMIN, status.HTTP_200_OK), (Role.EDITOR, status.HTTP_200_OK), (Role.VIEWER, status.HTTP_200_OK)],
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_alert_receive_channel_detail_permissions(
make_organization_and_user_with_plugin_token,
@ -360,9 +368,9 @@ def test_alert_receive_channel_detail_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_receive_channel_send_demo_alert_permissions(
@ -395,9 +403,9 @@ def test_alert_receive_channel_send_demo_alert_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_alert_receive_channel_integration_options_permissions(
@ -426,9 +434,9 @@ def test_alert_receive_channel_integration_options_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_receive_channel_preview_template_permissions(
@ -501,9 +509,9 @@ def test_alert_receive_channel_preview_template_require_notification_channel(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_receive_channel_change_team_permissions(
@ -597,9 +605,9 @@ def test_alert_receive_channel_change_team(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_alert_receive_channel_counters_permissions(
@ -608,7 +616,7 @@ def test_alert_receive_channel_counters_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse(
@ -630,9 +638,9 @@ def test_alert_receive_channel_counters_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_alert_receive_channel_counters_per_integration_permissions(

View file

@ -6,17 +6,17 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient
from apps.api.permissions import LegacyAccessControlRole
from apps.base.messaging import BaseMessagingBackend
from common.constants.role import Role
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_receive_channel_template_update_permissions(
@ -48,9 +48,9 @@ def test_alert_receive_channel_template_update_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_alert_receive_channel_template_detail_permissions(
@ -83,7 +83,7 @@ def test_alert_receive_channel_template_include_additional_backend_templates(
make_user_auth_headers,
make_alert_receive_channel,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=Role.ADMIN)
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(
organization,
messaging_backends_templates={"TESTONLY": {"title": "the-title", "message": "the-message", "image_url": "url"}},
@ -109,7 +109,7 @@ def test_alert_receive_channel_template_include_additional_backend_templates_usi
make_user_auth_headers,
make_alert_receive_channel,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=Role.ADMIN)
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(organization, messaging_backends_templates=None)
client = APIClient()
@ -138,7 +138,7 @@ def test_update_alert_receive_channel_backend_template_invalid_template(
make_user_auth_headers,
make_alert_receive_channel,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=Role.ADMIN)
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(organization, messaging_backends_templates=None)
client = APIClient()
@ -160,7 +160,7 @@ def test_update_alert_receive_channel_backend_template_invalid_url(
make_user_auth_headers,
make_alert_receive_channel,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=Role.ADMIN)
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(organization, messaging_backends_templates=None)
client = APIClient()
@ -182,7 +182,7 @@ def test_update_alert_receive_channel_backend_template_empty_values_allowed(
make_user_auth_headers,
make_alert_receive_channel,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=Role.ADMIN)
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(organization, messaging_backends_templates=None)
client = APIClient()
@ -208,7 +208,7 @@ def test_update_alert_receive_channel_backend_template_update_values(
make_user_auth_headers,
make_alert_receive_channel,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=Role.ADMIN)
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(
organization,
messaging_backends_templates={
@ -249,7 +249,7 @@ def test_preview_alert_receive_channel_backend_templater(
make_alert_group,
make_alert,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=Role.ADMIN)
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(organization)
default_channel_filter = make_channel_filter(alert_receive_channel, is_default=True)
alert_group = make_alert_group(alert_receive_channel, channel_filter=default_channel_filter)
@ -280,7 +280,7 @@ def test_update_alert_receive_channel_templates(
# set url here to pass *_url templates validation
return "https://grafana.com"
organization, user, token = make_organization_and_user_with_plugin_token(role=Role.ADMIN)
organization, user, token = make_organization_and_user_with_plugin_token()
alert_receive_channel = make_alert_receive_channel(
organization,
messaging_backends_templates={"TESTONLY": {"title": "the-title", "message": "the-message", "image_url": "url"}},

View file

@ -6,21 +6,20 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_channel_filter_create_permissions(
make_organization_and_user_with_plugin_token,
make_alert_receive_channel,
make_user_auth_headers,
role,
expected_status,
@ -45,9 +44,9 @@ def test_channel_filter_create_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_channel_filter_update_permissions(
@ -83,7 +82,11 @@ def test_channel_filter_update_permissions(
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[(Role.ADMIN, status.HTTP_200_OK), (Role.EDITOR, status.HTTP_200_OK), (Role.VIEWER, status.HTTP_200_OK)],
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_channel_filter_list_permissions(
make_organization_and_user_with_plugin_token,
@ -114,7 +117,11 @@ def test_channel_filter_list_permissions(
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[(Role.ADMIN, status.HTTP_200_OK), (Role.EDITOR, status.HTTP_200_OK), (Role.VIEWER, status.HTTP_200_OK)],
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_channel_filter_retrieve_permissions(
make_organization_and_user_with_plugin_token,
@ -146,9 +153,9 @@ def test_channel_filter_retrieve_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_204_NO_CONTENT),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_channel_filter_delete_permissions(
@ -181,9 +188,9 @@ def test_channel_filter_delete_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_channel_filter_move_to_position_permissions(
@ -216,9 +223,9 @@ def test_channel_filter_move_to_position_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_alert_receive_channel_send_demo_alert_permissions(

View file

@ -8,7 +8,7 @@ from rest_framework.response import Response
from rest_framework.test import APIClient
from apps.alerts.models import CustomButton
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
TEST_URL = "https://amixr.io"
@ -275,14 +275,13 @@ def test_delete_custom_button(custom_button_internal_api_setup, make_user_auth_h
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_custom_button_create_permissions(
make_organization_and_user_with_plugin_token,
make_custom_action,
make_user_auth_headers,
role,
expected_status,
@ -307,9 +306,9 @@ def test_custom_button_create_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_custom_button_update_permissions(
@ -343,7 +342,11 @@ def test_custom_button_update_permissions(
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[(Role.ADMIN, status.HTTP_200_OK), (Role.EDITOR, status.HTTP_200_OK), (Role.VIEWER, status.HTTP_200_OK)],
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_custom_button_list_permissions(
make_organization_and_user_with_plugin_token,
@ -372,7 +375,11 @@ def test_custom_button_list_permissions(
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[(Role.ADMIN, status.HTTP_200_OK), (Role.EDITOR, status.HTTP_200_OK), (Role.VIEWER, status.HTTP_200_OK)],
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_custom_button_retrieve_permissions(
make_organization_and_user_with_plugin_token,
@ -402,9 +409,9 @@ def test_custom_button_retrieve_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_204_NO_CONTENT),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_custom_button_delete_permissions(

View file

@ -24,7 +24,7 @@ def test_delete_escalation_chain(escalation_chain_internal_api_setup, make_user_
@pytest.mark.django_db
def test_update_escalation_chain(escalation_chain_internal_api_setup, make_user_auth_headers, make_organization):
def test_update_escalation_chain(escalation_chain_internal_api_setup, make_user_auth_headers):
user, token, escalation_chain = escalation_chain_internal_api_setup
client = APIClient()
url = reverse("api-internal:escalation_chain-detail", kwargs={"pk": escalation_chain.public_primary_key})

View file

@ -9,7 +9,7 @@ from rest_framework.response import Response
from rest_framework.test import APIClient
from apps.alerts.models import EscalationPolicy
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.fixture()
@ -93,9 +93,9 @@ def test_move_to_position(escalation_policy_internal_api_setup, make_user_auth_h
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_escalation_policy_create_permissions(
@ -130,9 +130,9 @@ def test_escalation_policy_create_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_escalation_policy_update_permissions(
@ -171,9 +171,9 @@ def test_escalation_policy_update_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_escalation_policy_list_permissions(
@ -208,9 +208,9 @@ def test_escalation_policy_list_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_escalation_policy_retrieve_permissions(
@ -245,9 +245,9 @@ def test_escalation_policy_retrieve_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_204_NO_CONTENT),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_escalation_policy_delete_permissions(
@ -282,9 +282,9 @@ def test_escalation_policy_delete_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_escalation_policy_escalation_options_permissions(
@ -319,9 +319,9 @@ def test_escalation_policy_escalation_options_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_escalation_policy_delay_options_permissions(
@ -357,9 +357,9 @@ def test_escalation_policy_delay_options_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_escalation_policy_move_to_position_permissions(

View file

@ -3,16 +3,16 @@ from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_terraform_gitops_permissions(
@ -22,7 +22,7 @@ def test_terraform_gitops_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role)
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
make_escalation_chain(organization)
client = APIClient()
@ -38,15 +38,15 @@ def test_terraform_gitops_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_terraform_state_permissions(
make_organization_and_user_with_plugin_token, make_user_auth_headers, role, expected_status
):
_, user, token = make_organization_and_user_with_plugin_token(role)
_, user, token = make_organization_and_user_with_plugin_token(role=role)
client = APIClient()
url = reverse("api-internal:terraform_imports")

View file

@ -8,8 +8,8 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient
from apps.api.permissions import LegacyAccessControlRole
from apps.heartbeat.models import IntegrationHeartBeat
from common.constants.role import Role
MOCK_LAST_HEARTBEAT_TIME_VERBAL = "a moment"
@ -151,7 +151,7 @@ def test_create_empty_alert_receive_channel_integration_heartbeat(
integration_heartbeat_internal_api_setup,
make_user_auth_headers,
):
user, token, alert_receive_channel, integration_heartbeat = integration_heartbeat_internal_api_setup
user, token, _, _ = integration_heartbeat_internal_api_setup
client = APIClient()
url = reverse("api-internal:integration_heartbeat-list")
@ -185,9 +185,39 @@ def test_update_integration_heartbeat(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_integration_heartbeat_create_permissions(
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
role,
expected_status,
):
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:integration_heartbeat-list")
with patch(
"apps.api.views.integration_heartbeat.IntegrationHeartBeatView.create",
return_value=Response(
status=status.HTTP_200_OK,
),
):
response = client.post(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == expected_status
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_integration_heartbeat_update_permissions(
@ -223,7 +253,11 @@ def test_integration_heartbeat_update_permissions(
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[(Role.ADMIN, status.HTTP_200_OK), (Role.EDITOR, status.HTTP_200_OK), (Role.VIEWER, status.HTTP_200_OK)],
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_integration_heartbeat_list_permissions(
make_organization_and_user_with_plugin_token,
@ -255,9 +289,40 @@ def test_integration_heartbeat_list_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_integration_heartbeat_timeout_options_permissions(
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
role,
expected_status,
):
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:integration_heartbeat-timeout-options")
with patch(
"apps.api.views.integration_heartbeat.IntegrationHeartBeatView.timeout_options",
return_value=Response(
status=status.HTTP_200_OK,
),
):
response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == expected_status
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_integration_heartbeat_retrieve_permissions(

View file

@ -6,6 +6,8 @@ from rest_framework.test import APIClient
from apps.alerts.models import AlertReceiveChannel
from apps.user_management.models import Organization
# TODO: should probably modify these tests to take into account new rbac permissions
@pytest.fixture()
def maintenance_internal_api_setup(
@ -23,7 +25,7 @@ def maintenance_internal_api_setup(
def test_start_maintenance_integration(
maintenance_internal_api_setup, mock_start_disable_maintenance_task, make_user_auth_headers
):
token, organization, user, alert_receive_channel = maintenance_internal_api_setup
token, _, user, alert_receive_channel = maintenance_internal_api_setup
client = APIClient()
url = reverse("api-internal:start_maintenance")
@ -50,7 +52,7 @@ def test_stop_maintenance_integration(
mock_start_disable_maintenance_task,
make_user_auth_headers,
):
token, organization, user, alert_receive_channel = maintenance_internal_api_setup
token, _, user, alert_receive_channel = maintenance_internal_api_setup
client = APIClient()
mode = AlertReceiveChannel.MAINTENANCE
duration = AlertReceiveChannel.DURATION_ONE_HOUR.seconds
@ -161,7 +163,7 @@ def test_maintenances_list(
def test_empty_maintenances_list(
maintenance_internal_api_setup, mock_start_disable_maintenance_task, make_user_auth_headers
):
token, organization, user, alert_receive_channel = maintenance_internal_api_setup
token, _, user, alert_receive_channel = maintenance_internal_api_setup
client = APIClient()
url = reverse("api-internal:maintenance")
response = client.get(url, format="json", **make_user_auth_headers(user, token))

View file

@ -7,8 +7,8 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient
from apps.api.permissions import LegacyAccessControlRole
from apps.schedules.models import CustomOnCallShift, OnCallSchedule, OnCallScheduleWeb
from common.constants.role import Role
@pytest.fixture()
@ -26,7 +26,7 @@ def on_call_shift_internal_api_setup(
@pytest.mark.django_db
def test_create_on_call_shift_rotation(on_call_shift_internal_api_setup, make_user_auth_headers):
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
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)
@ -58,7 +58,7 @@ def test_create_on_call_shift_rotation(on_call_shift_internal_api_setup, make_us
@pytest.mark.django_db
def test_create_on_call_shift_override(on_call_shift_internal_api_setup, make_user_auth_headers):
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
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)
@ -98,7 +98,7 @@ def test_get_on_call_shift(
make_on_call_shift,
make_user_auth_headers,
):
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, user2, _, schedule = on_call_shift_internal_api_setup
client = APIClient()
start_date = timezone.now().replace(microsecond=0)
@ -144,7 +144,7 @@ def test_list_on_call_shift(
make_on_call_shift,
make_user_auth_headers,
):
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, user2, _, schedule = on_call_shift_internal_api_setup
client = APIClient()
start_date = timezone.now().replace(microsecond=0)
@ -270,7 +270,7 @@ def test_update_future_on_call_shift(
make_user_auth_headers,
):
"""Test updating the shift that has not started (rotation_start > now)"""
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, schedule = on_call_shift_internal_api_setup
client = APIClient()
start_date = (timezone.now() + timezone.timedelta(days=1)).replace(microsecond=0)
@ -337,7 +337,7 @@ def test_update_started_on_call_shift(
):
"""Test updating the shift that has started (rotation_start < now)"""
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, schedule = on_call_shift_internal_api_setup
client = APIClient()
start_date = (timezone.now() - timezone.timedelta(hours=1)).replace(microsecond=0)
@ -409,7 +409,7 @@ def test_update_old_on_call_shift_with_future_version(
make_user_auth_headers,
):
"""Test updating the shift that has the newer version (updated_shift is not None)"""
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, schedule = on_call_shift_internal_api_setup
client = APIClient()
now = timezone.now().replace(microsecond=0)
@ -498,7 +498,7 @@ def test_update_started_on_call_shift_title(
):
"""Test updating the title for the shift that has started (rotation_start < now)"""
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, schedule = on_call_shift_internal_api_setup
client = APIClient()
start_date = (timezone.now() - timezone.timedelta(hours=1)).replace(microsecond=0)
@ -560,7 +560,7 @@ def test_delete_started_on_call_shift(
):
"""Test deleting the shift that has started (rotation_start < now)"""
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, schedule = on_call_shift_internal_api_setup
client = APIClient()
start_date = (timezone.now() - timezone.timedelta(hours=1)).replace(microsecond=0)
@ -598,7 +598,7 @@ def test_delete_future_on_call_shift(
):
"""Test deleting the shift that has not started (rotation_start > now)"""
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, schedule = on_call_shift_internal_api_setup
client = APIClient()
start_date = (timezone.now() + timezone.timedelta(days=1)).replace(microsecond=0)
@ -631,7 +631,7 @@ def test_create_on_call_shift_invalid_data_rotation_start(
on_call_shift_internal_api_setup,
make_user_auth_headers,
):
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, 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)
@ -660,7 +660,7 @@ def test_create_on_call_shift_invalid_data_rotation_start(
@pytest.mark.django_db
def test_create_on_call_shift_invalid_data_until(on_call_shift_internal_api_setup, make_user_auth_headers):
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
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)
@ -713,7 +713,7 @@ def test_create_on_call_shift_invalid_data_until(on_call_shift_internal_api_setu
@pytest.mark.django_db
def test_create_on_call_shift_invalid_data_by_day(on_call_shift_internal_api_setup, make_user_auth_headers):
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, 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)
@ -763,7 +763,7 @@ def test_create_on_call_shift_invalid_data_by_day(on_call_shift_internal_api_set
@pytest.mark.django_db
def test_create_on_call_shift_invalid_data_interval(on_call_shift_internal_api_setup, make_user_auth_headers):
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, 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)
@ -813,7 +813,7 @@ def test_create_on_call_shift_invalid_data_interval(on_call_shift_internal_api_s
@pytest.mark.django_db
def test_create_on_call_shift_invalid_data_shift_end(on_call_shift_internal_api_setup, make_user_auth_headers):
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, 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)
@ -866,7 +866,7 @@ def test_create_on_call_shift_invalid_data_rolling_users(
on_call_shift_internal_api_setup,
make_user_auth_headers,
):
token, user1, user2, organization, schedule = on_call_shift_internal_api_setup
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)
@ -894,7 +894,7 @@ def test_create_on_call_shift_invalid_data_rolling_users(
@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, user2, organization, schedule = on_call_shift_internal_api_setup
token, user1, _, _, 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)
@ -925,9 +925,9 @@ def test_create_on_call_shift_override_invalid_data(on_call_shift_internal_api_s
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_201_CREATED),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_201_CREATED),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_on_call_shift_create_permissions(
@ -936,7 +936,7 @@ def test_on_call_shift_create_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
@ -957,9 +957,9 @@ def test_on_call_shift_create_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_on_call_shift_update_permissions(
@ -1005,9 +1005,9 @@ def test_on_call_shift_update_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_on_call_shift_list_permissions(
@ -1016,7 +1016,7 @@ def test_on_call_shift_list_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:oncall_shifts-list")
@ -1036,9 +1036,9 @@ def test_on_call_shift_list_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_on_call_shift_retrieve_permissions(
@ -1079,9 +1079,9 @@ def test_on_call_shift_retrieve_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_204_NO_CONTENT),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_on_call_shift_delete_permissions(
@ -1122,9 +1122,9 @@ def test_on_call_shift_delete_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_on_call_shift_frequency_options_permissions(
@ -1153,9 +1153,9 @@ def test_on_call_shift_frequency_options_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_on_call_shift_days_options_permissions(
@ -1184,9 +1184,9 @@ def test_on_call_shift_days_options_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_on_call_shift_preview_permissions(

View file

@ -6,30 +6,25 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_current_team_retrieve_permissions(
make_organization,
make_user_for_organization,
make_token_for_organization,
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
role,
expected_status,
):
org = make_organization()
tester = make_user_for_organization(org, role=role)
_, token = make_token_for_organization(org)
_, tester, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:api-current-team")
@ -48,23 +43,18 @@ def test_current_team_retrieve_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_current_team_update_permissions(
make_organization,
make_user_for_organization,
make_token_for_organization,
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
role,
expected_status,
):
org = make_organization()
tester = make_user_for_organization(org, role=role)
_, token = make_token_for_organization(org)
_, tester, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:api-current-team")
@ -84,9 +74,9 @@ def test_current_team_update_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_current_team_get_telegram_verification_code_permissions(
@ -95,8 +85,7 @@ def test_current_team_get_telegram_verification_code_permissions(
role,
expected_status,
):
organization, tester, token = make_organization_and_user_with_plugin_token(role)
_, tester, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:api-get-telegram-verification-code")
@ -109,9 +98,9 @@ def test_current_team_get_telegram_verification_code_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_current_team_get_channel_verification_code_permissions(
@ -120,8 +109,7 @@ def test_current_team_get_channel_verification_code_permissions(
role,
expected_status,
):
organization, tester, token = make_organization_and_user_with_plugin_token(role)
_, tester, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:api-get-channel-verification-code") + "?backend=TESTONLY"
@ -135,8 +123,7 @@ def test_current_team_get_channel_verification_code_ok(
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
):
organization, tester, token = make_organization_and_user_with_plugin_token(Role.ADMIN)
organization, tester, token = make_organization_and_user_with_plugin_token()
client = APIClient()
url = reverse("api-internal:api-get-channel-verification-code") + "?backend=TESTONLY"
@ -156,8 +143,7 @@ def test_current_team_get_channel_verification_code_invalid(
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
):
organization, tester, token = make_organization_and_user_with_plugin_token(Role.ADMIN)
_, tester, token = make_organization_and_user_with_plugin_token()
client = APIClient()
url = reverse("api-internal:api-get-channel-verification-code") + "?backend=INVALID"

View file

@ -7,7 +7,7 @@ from rest_framework.response import Response
from rest_framework.test import APIClient
from apps.alerts.models import ResolutionNote
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
@ -212,9 +212,9 @@ def test_delete_resolution_note(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_resolution_note_create_permissions(
@ -224,7 +224,7 @@ def test_resolution_note_create_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:resolution_note-list")
@ -245,9 +245,9 @@ def test_resolution_note_create_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_resolution_note_update_permissions(
@ -260,7 +260,7 @@ def test_resolution_note_update_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
resolution_note = make_resolution_note(
@ -289,9 +289,9 @@ def test_resolution_note_update_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_204_NO_CONTENT),
(Role.EDITOR, status.HTTP_204_NO_CONTENT),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_resolution_note_delete_permissions(
@ -304,7 +304,7 @@ def test_resolution_note_delete_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
resolution_note = make_resolution_note(
@ -331,9 +331,9 @@ def test_resolution_note_delete_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_resolution_note_list_permissions(
@ -343,7 +343,7 @@ def test_resolution_note_list_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:resolution_note-list")
@ -363,9 +363,9 @@ def test_resolution_note_list_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_resolution_note_detail_permissions(
@ -378,7 +378,7 @@ def test_resolution_note_detail_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
resolution_note = make_resolution_note(

View file

@ -0,0 +1,115 @@
import pytest
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_public_api_tokens_retrieve_permissions(
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
make_public_api_token,
role,
expected_status,
):
organization, user, plugin_token = make_organization_and_user_with_plugin_token(role)
api_token, _ = make_public_api_token(user, organization)
client = APIClient()
url = reverse("api-internal:api_token-detail", kwargs={"pk": api_token.id})
response = client.get(url, format="json", **make_user_auth_headers(user, plugin_token))
assert response.status_code == expected_status
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_public_api_tokens_list_permissions(
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
make_public_api_token,
role,
expected_status,
):
organization, user, plugin_token = make_organization_and_user_with_plugin_token(role)
make_public_api_token(user, organization)
client = APIClient()
url = reverse("api-internal:api_token-list")
response = client.get(url, format="json", **make_user_auth_headers(user, plugin_token))
assert response.status_code == expected_status
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(LegacyAccessControlRole.ADMIN, status.HTTP_201_CREATED),
(LegacyAccessControlRole.EDITOR, status.HTTP_201_CREATED),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_public_api_tokens_create_permissions(
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
role,
expected_status,
):
_, user, plugin_token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:api_token-list")
response = client.post(
url,
data={
"name": "helloooo",
},
format="json",
**make_user_auth_headers(user, plugin_token),
)
assert response.status_code == expected_status
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_public_api_tokens_delete_permissions(
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
make_public_api_token,
role,
expected_status,
):
organization, user, plugin_token = make_organization_and_user_with_plugin_token(role)
api_token, _ = make_public_api_token(user, organization)
client = APIClient()
url = reverse("api-internal:api_token-detail", kwargs={"pk": api_token.id})
response = client.delete(url, format="json", **make_user_auth_headers(user, plugin_token))
assert response.status_code == expected_status

View file

@ -3,9 +3,9 @@ from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from apps.api.permissions import LegacyAccessControlRole
from apps.auth_token.models import ScheduleExportAuthToken
from apps.schedules.models import OnCallScheduleICal
from common.constants.role import Role
ICAL_URL = "https://calendar.google.com/calendar/ical/amixr.io_37gttuakhrtr75ano72p69rt78%40group.calendar.google.com/private-1d00a680ba5be7426c3eb3ef1616e26d/basic.ics" # noqa
@ -14,9 +14,9 @@ ICAL_URL = "https://calendar.google.com/calendar/ical/amixr.io_37gttuakhrtr75ano
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_get_schedule_export_token(
@ -26,8 +26,7 @@ def test_get_schedule_export_token(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
schedule = make_schedule(
organization,
schedule_class=OnCallScheduleICal,
@ -50,9 +49,9 @@ def test_get_schedule_export_token(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_404_NOT_FOUND),
(Role.EDITOR, status.HTTP_404_NOT_FOUND),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_404_NOT_FOUND),
(LegacyAccessControlRole.EDITOR, status.HTTP_404_NOT_FOUND),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_schedule_export_token_not_found(
@ -62,8 +61,7 @@ def test_schedule_export_token_not_found(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
schedule = make_schedule(
organization,
schedule_class=OnCallScheduleICal,
@ -84,9 +82,9 @@ def test_schedule_export_token_not_found(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_201_CREATED),
(Role.EDITOR, status.HTTP_201_CREATED),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_201_CREATED),
(LegacyAccessControlRole.EDITOR, status.HTTP_201_CREATED),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_schedule_create_export_token(
@ -96,8 +94,7 @@ def test_schedule_create_export_token(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
schedule = make_schedule(
organization,
schedule_class=OnCallScheduleICal,
@ -118,9 +115,9 @@ def test_schedule_create_export_token(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_204_NO_CONTENT),
(Role.EDITOR, status.HTTP_204_NO_CONTENT),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_schedule_delete_export_token(
@ -130,8 +127,7 @@ def test_schedule_delete_export_token(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
schedule = make_schedule(
organization,
schedule_class=OnCallScheduleICal,

View file

@ -10,6 +10,7 @@ from rest_framework.serializers import ValidationError
from rest_framework.test import APIClient
from apps.alerts.models import EscalationPolicy
from apps.api.permissions import LegacyAccessControlRole
from apps.schedules.ical_utils import memoized_users_in_ical
from apps.schedules.models import (
CustomOnCallShift,
@ -18,7 +19,6 @@ from apps.schedules.models import (
OnCallScheduleICal,
OnCallScheduleWeb,
)
from common.constants.role import Role
ICAL_URL = "https://calendar.google.com/calendar/ical/amixr.io_37gttuakhrtr75ano72p69rt78%40group.calendar.google.com/private-1d00a680ba5be7426c3eb3ef1616e26d/basic.ics"
@ -1062,7 +1062,7 @@ def test_merging_same_shift_events(
user_a = make_user_for_organization(organization)
user_b = make_user_for_organization(organization)
user_c = make_user_for_organization(organization, role=Role.VIEWER)
user_c = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
# clear users pks <-> organization cache (persisting between tests)
memoized_users_in_ical.cache_clear()
@ -1158,9 +1158,9 @@ def test_filter_events_invalid_type(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_schedule_create_permissions(
@ -1170,7 +1170,7 @@ def test_schedule_create_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
make_schedule(
organization,
schedule_class=OnCallScheduleICal,
@ -1196,9 +1196,9 @@ def test_schedule_create_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_schedule_update_permissions(
@ -1208,7 +1208,7 @@ def test_schedule_update_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
schedule = make_schedule(
organization,
schedule_class=OnCallScheduleICal,
@ -1237,7 +1237,11 @@ def test_schedule_update_permissions(
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[(Role.ADMIN, status.HTTP_200_OK), (Role.EDITOR, status.HTTP_200_OK), (Role.VIEWER, status.HTTP_200_OK)],
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_schedule_list_permissions(
make_organization_and_user_with_plugin_token,
@ -1246,7 +1250,7 @@ def test_schedule_list_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
make_schedule(
organization,
schedule_class=OnCallScheduleICal,
@ -1271,7 +1275,11 @@ def test_schedule_list_permissions(
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[(Role.ADMIN, status.HTTP_200_OK), (Role.EDITOR, status.HTTP_200_OK), (Role.VIEWER, status.HTTP_200_OK)],
[
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_schedule_retrieve_permissions(
make_organization_and_user_with_plugin_token,
@ -1280,7 +1288,7 @@ def test_schedule_retrieve_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
schedule = make_schedule(
organization,
schedule_class=OnCallScheduleICal,
@ -1306,9 +1314,9 @@ def test_schedule_retrieve_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_204_NO_CONTENT),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_schedule_delete_permissions(
@ -1318,7 +1326,7 @@ def test_schedule_delete_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
schedule = make_schedule(
organization,
schedule_class=OnCallScheduleICal,
@ -1344,9 +1352,9 @@ def test_schedule_delete_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_events_permissions(
@ -1356,7 +1364,7 @@ def test_events_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
schedule = make_schedule(
organization,
schedule_class=OnCallScheduleICal,
@ -1382,9 +1390,9 @@ def test_events_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_reload_ical_permissions(
@ -1394,7 +1402,7 @@ def test_reload_ical_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
schedule = make_schedule(
organization,
schedule_class=OnCallScheduleICal,
@ -1420,9 +1428,9 @@ def test_reload_ical_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_schedule_notify_oncall_shift_freq_options_permissions(
@ -1432,7 +1440,7 @@ def test_schedule_notify_oncall_shift_freq_options_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
url = reverse("api-internal:schedule-notify-oncall-shift-freq-options")
client = APIClient()
response = client.get(url, format="json", **make_user_auth_headers(user, token))
@ -1444,9 +1452,9 @@ def test_schedule_notify_oncall_shift_freq_options_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_schedule_notify_empty_oncall_options_permissions(
@ -1456,7 +1464,7 @@ def test_schedule_notify_empty_oncall_options_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
url = reverse("api-internal:schedule-notify-empty-oncall-options")
client = APIClient()
response = client.get(url, format="json", **make_user_auth_headers(user, token))
@ -1468,9 +1476,9 @@ def test_schedule_notify_empty_oncall_options_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_schedule_mention_options_permissions(
@ -1480,7 +1488,7 @@ def test_schedule_mention_options_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
url = reverse("api-internal:schedule-mention-options")
client = APIClient()
response = client.get(url, format="json", **make_user_auth_headers(user, token))

View file

@ -6,7 +6,7 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
# Testing permissions, not view itself. So mock is ok here
@ -14,13 +14,16 @@ from common.constants.role import Role
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_set_general_log_channel_permissions(
make_organization_and_user_with_plugin_token, make_user_auth_headers, role, expected_status
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
role,
expected_status,
):
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()

View file

@ -6,20 +6,23 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_slack_channels_list_permissions(
make_organization_and_user_with_plugin_token, make_user_auth_headers, role, expected_status
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
role,
expected_status,
):
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
@ -40,13 +43,17 @@ def test_slack_channels_list_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_slack_channels_detail_permissions(
make_organization_and_user_with_plugin_token, make_user_auth_headers, role, make_slack_channel, expected_status
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
make_slack_channel,
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role)
slack_channel = make_slack_channel(organization.slack_team_identity)

View file

@ -6,16 +6,16 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_get_slack_settings_permissions(
@ -24,7 +24,7 @@ def test_get_slack_settings_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:slack-settings")
@ -43,9 +43,9 @@ def test_get_slack_settings_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_update_slack_settings_permissions(
@ -54,7 +54,7 @@ def test_update_slack_settings_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:slack-settings")
@ -73,9 +73,9 @@ def test_update_slack_settings_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_get_acknowledge_remind_options_permissions(
@ -84,7 +84,7 @@ def test_get_acknowledge_remind_options_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:acknowledge-reminder-options")
@ -103,9 +103,9 @@ def test_get_acknowledge_remind_options_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_get_unacknowledge_timeout_options_permissions(
@ -114,7 +114,7 @@ def test_get_unacknowledge_timeout_options_permissions(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:unacknowledge-timeout-options")

View file

@ -6,16 +6,16 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_subscription_retrieve_permissions(

View file

@ -3,9 +3,9 @@ from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from apps.api.permissions import LegacyAccessControlRole
from apps.schedules.models import OnCallScheduleCalendar
from apps.user_management.models import Team
from common.constants.role import Role
GENERAL_TEAM = Team(public_primary_key=None, name="General", email=None, avatar_url=None)
@ -64,28 +64,24 @@ def test_list_teams_for_non_member(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_list_teams_permissions(
make_organization,
make_token_for_organization,
make_user_for_organization,
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
role,
expected_status,
):
organization = make_organization()
_, token = make_token_for_organization(organization)
user = make_user_for_organization(organization, role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()
url = reverse("api-internal:team-list")
response = client.get(url, format="json", **make_user_auth_headers(user, token))
assert response.status_code == status.HTTP_200_OK
assert response.status_code == expected_status
@pytest.mark.django_db

View file

@ -3,14 +3,14 @@ from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
def test_not_authorized(make_organization_and_user_with_plugin_token, make_telegram_channel):
client = APIClient()
organization, user, _ = make_organization_and_user_with_plugin_token()
organization, _, _ = make_organization_and_user_with_plugin_token()
telegram_channel = make_telegram_channel(organization=organization)
url = reverse("api-internal:telegram_channel-list")
@ -34,9 +34,9 @@ def test_not_authorized(make_organization_and_user_with_plugin_token, make_teleg
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_list_telegram_channels_permissions(
@ -46,8 +46,7 @@ def test_list_telegram_channels_permissions(
expected_status,
):
client = APIClient()
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
url = reverse("api-internal:telegram_channel-list")
response = client.get(url, **make_user_auth_headers(user, token))
@ -59,9 +58,9 @@ def test_list_telegram_channels_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_get_telegram_channels_permissions(
@ -72,8 +71,7 @@ def test_get_telegram_channels_permissions(
expected_status,
):
client = APIClient()
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
telegram_channel = make_telegram_channel(organization=organization)
url = reverse("api-internal:telegram_channel-detail", kwargs={"pk": telegram_channel.public_primary_key})
@ -86,9 +84,9 @@ def test_get_telegram_channels_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_204_NO_CONTENT),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_delete_telegram_channels_permissions(
@ -100,7 +98,7 @@ def test_delete_telegram_channels_permissions(
):
client = APIClient()
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
telegram_channel = make_telegram_channel(organization=organization)
url = reverse("api-internal:telegram_channel-detail", kwargs={"pk": telegram_channel.public_primary_key})
@ -113,9 +111,9 @@ def test_delete_telegram_channels_permissions(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_set_default_telegram_channels_permissions(
@ -127,8 +125,7 @@ def test_set_default_telegram_channels_permissions(
):
client = APIClient()
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
telegram_channel = make_telegram_channel(organization=organization)
url = reverse("api-internal:telegram_channel-set-default", kwargs={"pk": telegram_channel.public_primary_key})

View file

@ -18,7 +18,7 @@ def test_get_terraform_file(
@pytest.mark.django_db
def test_get_terraform_imports(make_organization_and_user_with_plugin_token, make_user_auth_headers):
organization, user, token = make_organization_and_user_with_plugin_token()
_, user, token = make_organization_and_user_with_plugin_token()
client = APIClient()
url = reverse("api-internal:terraform_imports")
response = client.get(url, format="text/plain", **make_user_auth_headers(user, token))

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
@ -52,13 +52,16 @@ def test_usergroup_list_without_slack_installed(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_200_OK),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_200_OK),
],
)
def test_usergroup_permissions(
make_organization_and_user_with_plugin_token, make_user_auth_headers, role, expected_status
make_organization_and_user_with_plugin_token,
make_user_auth_headers,
role,
expected_status,
):
_, user, token = make_organization_and_user_with_plugin_token(role)
client = APIClient()

View file

@ -6,8 +6,8 @@ from django.utils import timezone
from rest_framework import status
from rest_framework.test import APIClient
from apps.api.permissions import LegacyAccessControlRole
from apps.base.models import UserNotificationPolicy
from common.constants.role import Role
DEFAULT_NOTIFICATION_CHANNEL = UserNotificationPolicy.NotificationChannel.SLACK
@ -17,7 +17,7 @@ def user_notification_policy_internal_api_setup(
make_organization_and_user_with_plugin_token, make_user_for_organization, make_user_notification_policy
):
organization, admin, token = make_organization_and_user_with_plugin_token()
user = make_user_for_organization(organization, Role.EDITOR)
user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
wait_notification_step = make_user_notification_policy(
admin, UserNotificationPolicy.Step.WAIT, wait_delay=timezone.timedelta(minutes=15), important=False
@ -49,7 +49,7 @@ def user_notification_policy_internal_api_setup(
@pytest.mark.django_db
def test_create_notification_policy(user_notification_policy_internal_api_setup, make_user_auth_headers):
token, steps, users = user_notification_policy_internal_api_setup
token, _, users = user_notification_policy_internal_api_setup
admin, _ = users
client = APIClient()
url = reverse("api-internal:notification_policy-list")
@ -69,7 +69,7 @@ def test_create_notification_policy(user_notification_policy_internal_api_setup,
def test_admin_can_create_notification_policy_for_user(
user_notification_policy_internal_api_setup, make_user_auth_headers
):
token, steps, users = user_notification_policy_internal_api_setup
token, _, users = user_notification_policy_internal_api_setup
admin, user = users
client = APIClient()
url = reverse("api-internal:notification_policy-list")

View file

@ -3,8 +3,8 @@ from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from apps.api.permissions import LegacyAccessControlRole
from apps.auth_token.models import UserScheduleExportAuthToken
from common.constants.role import Role
ICAL_URL = "https://calendar.google.com/calendar/ical/amixr.io_37gttuakhrtr75ano72p69rt78%40group.calendar.google.com/private-1d00a680ba5be7426c3eb3ef1616e26d/basic.ics" # noqa
@ -13,9 +13,9 @@ ICAL_URL = "https://calendar.google.com/calendar/ical/amixr.io_37gttuakhrtr75ano
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_200_OK),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_200_OK),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_get_user_schedule_export_token(
@ -24,8 +24,7 @@ def test_get_user_schedule_export_token(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
UserScheduleExportAuthToken.create_auth_token(
user=user,
@ -45,9 +44,9 @@ def test_get_user_schedule_export_token(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_404_NOT_FOUND),
(Role.EDITOR, status.HTTP_404_NOT_FOUND),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_404_NOT_FOUND),
(LegacyAccessControlRole.EDITOR, status.HTTP_404_NOT_FOUND),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_user_schedule_export_token_not_found(
@ -56,8 +55,7 @@ def test_user_schedule_export_token_not_found(
role,
expected_status,
):
_, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
url = reverse("api-internal:user-export-token", kwargs={"pk": user.public_primary_key})
@ -72,9 +70,9 @@ def test_user_schedule_export_token_not_found(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_201_CREATED),
(Role.EDITOR, status.HTTP_201_CREATED),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_201_CREATED),
(LegacyAccessControlRole.EDITOR, status.HTTP_201_CREATED),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_user_schedule_create_export_token(
@ -83,8 +81,7 @@ def test_user_schedule_create_export_token(
role,
expected_status,
):
_, user, token = make_organization_and_user_with_plugin_token(role=role)
_, user, token = make_organization_and_user_with_plugin_token(role)
url = reverse("api-internal:user-export-token", kwargs={"pk": user.public_primary_key})
@ -99,9 +96,9 @@ def test_user_schedule_create_export_token(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_409_CONFLICT),
(Role.EDITOR, status.HTTP_409_CONFLICT),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_409_CONFLICT),
(LegacyAccessControlRole.EDITOR, status.HTTP_409_CONFLICT),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_user_schedule_create_multiple_export_tokens_fails(
@ -110,8 +107,7 @@ def test_user_schedule_create_multiple_export_tokens_fails(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
UserScheduleExportAuthToken.create_auth_token(
user=user,
@ -131,9 +127,9 @@ def test_user_schedule_create_multiple_export_tokens_fails(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_204_NO_CONTENT),
(Role.EDITOR, status.HTTP_204_NO_CONTENT),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.EDITOR, status.HTTP_204_NO_CONTENT),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_user_schedule_delete_export_token(
@ -142,8 +138,7 @@ def test_user_schedule_delete_export_token(
role,
expected_status,
):
organization, user, token = make_organization_and_user_with_plugin_token(role=role)
organization, user, token = make_organization_and_user_with_plugin_token(role)
instance, _ = UserScheduleExportAuthToken.create_auth_token(
user=user,
@ -168,9 +163,9 @@ def test_user_schedule_delete_export_token(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_404_NOT_FOUND),
(Role.EDITOR, status.HTTP_404_NOT_FOUND),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_404_NOT_FOUND),
(LegacyAccessControlRole.EDITOR, status.HTTP_404_NOT_FOUND),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_user_cannot_get_another_users_schedule_token(
@ -179,9 +174,8 @@ def test_user_cannot_get_another_users_schedule_token(
role,
expected_status,
):
organization1, user1, _ = make_organization_and_user_with_plugin_token(role=role)
_, user2, token2 = make_organization_and_user_with_plugin_token(role=role)
organization1, user1, _ = make_organization_and_user_with_plugin_token(role)
_, user2, token2 = make_organization_and_user_with_plugin_token(role)
UserScheduleExportAuthToken.create_auth_token(
user=user1,
@ -201,9 +195,9 @@ def test_user_cannot_get_another_users_schedule_token(
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_404_NOT_FOUND),
(Role.EDITOR, status.HTTP_404_NOT_FOUND),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_404_NOT_FOUND),
(LegacyAccessControlRole.EDITOR, status.HTTP_404_NOT_FOUND),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_user_cannot_delete_another_users_schedule_token(
@ -212,9 +206,8 @@ def test_user_cannot_delete_another_users_schedule_token(
role,
expected_status,
):
organization1, user1, _ = make_organization_and_user_with_plugin_token(role=role)
_, user2, token2 = make_organization_and_user_with_plugin_token(role=role)
organization1, user1, _ = make_organization_and_user_with_plugin_token(role)
_, user2, token2 = make_organization_and_user_with_plugin_token(role)
UserScheduleExportAuthToken.create_auth_token(
user=user1,

View file

@ -12,7 +12,7 @@ from rest_framework.response import Response
from apps.alerts.constants import ActionSource
from apps.alerts.models import Alert, AlertGroup, AlertReceiveChannel
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdminOrEditor
from apps.api.permissions import RBACPermission
from apps.api.serializers.alert_group import AlertGroupListSerializer, AlertGroupSerializer
from apps.auth_token.auth import PluginAuthentication
from apps.mobile_app.auth import MobileAppAuthTokenAuthentication
@ -160,29 +160,29 @@ class AlertGroupView(
MobileAppAuthTokenAuthentication,
PluginAuthentication,
)
permission_classes = (IsAuthenticated, ActionPermission)
permission_classes = (IsAuthenticated, RBACPermission)
action_permissions = {
IsAdminOrEditor: (
*MODIFY_ACTIONS,
"acknowledge",
"unacknowledge",
"resolve",
"unresolve",
"attach",
"unattach",
"silence",
"unsilence",
"bulk_action",
"preview_template",
),
AnyRole: (
*READ_ACTIONS,
"stats",
"filters",
"silence_options",
"bulk_action_options",
),
rbac_permissions = {
"metadata": [RBACPermission.Permissions.ALERT_GROUPS_READ],
"list": [RBACPermission.Permissions.ALERT_GROUPS_READ],
"retrieve": [RBACPermission.Permissions.ALERT_GROUPS_READ],
"stats": [RBACPermission.Permissions.ALERT_GROUPS_READ],
"filters": [RBACPermission.Permissions.ALERT_GROUPS_READ],
"silence_options": [RBACPermission.Permissions.ALERT_GROUPS_READ],
"bulk_action_options": [RBACPermission.Permissions.ALERT_GROUPS_READ],
"create": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"update": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"destroy": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"acknowledge": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"unacknowledge": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"resolve": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"unresolve": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"attach": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"unattach": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"silence": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"unsilence": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"bulk_action": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"preview_template": [RBACPermission.Permissions.INTEGRATIONS_TEST],
}
http_method_names = ["get", "post"]

View file

@ -9,7 +9,7 @@ from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from apps.alerts.models import AlertReceiveChannel
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin, IsAdminOrEditor
from apps.api.permissions import RBACPermission
from apps.api.serializers.alert_receive_channel import (
AlertReceiveChannelSerializer,
AlertReceiveChannelUpdateSerializer,
@ -66,19 +66,7 @@ class AlertReceiveChannelView(
ModelViewSet,
):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
action_permissions = {
IsAdmin: (*MODIFY_ACTIONS, "stop_maintenance", "start_maintenance", "change_team"),
IsAdminOrEditor: ("send_demo_alert", "preview_template"),
AnyRole: (
*READ_ACTIONS,
"integration_options",
"maintenance_duration_options",
"maintenance_mode_options",
"counters",
"counters_per_integration",
),
}
permission_classes = (IsAuthenticated, RBACPermission)
model = AlertReceiveChannel
serializer_class = AlertReceiveChannelSerializer
@ -90,6 +78,22 @@ class AlertReceiveChannelView(
filterset_class = AlertReceiveChannelFilter
rbac_permissions = {
"metadata": [RBACPermission.Permissions.INTEGRATIONS_READ],
"list": [RBACPermission.Permissions.INTEGRATIONS_READ],
"retrieve": [RBACPermission.Permissions.INTEGRATIONS_READ],
"integration_options": [RBACPermission.Permissions.INTEGRATIONS_READ],
"counters": [RBACPermission.Permissions.INTEGRATIONS_READ],
"counters_per_integration": [RBACPermission.Permissions.INTEGRATIONS_READ],
"send_demo_alert": [RBACPermission.Permissions.INTEGRATIONS_TEST],
"preview_template": [RBACPermission.Permissions.INTEGRATIONS_TEST],
"create": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"update": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"partial_update": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"destroy": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"change_team": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
}
def create(self, request, *args, **kwargs):
if request.data["integration"] is not None and (
request.data["integration"] in AlertReceiveChannel.WEB_INTEGRATION_CHOICES

View file

@ -3,7 +3,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from apps.alerts.models import AlertReceiveChannel
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin
from apps.api.permissions import RBACPermission
from apps.api.serializers.alert_receive_channel import AlertReceiveChannelTemplatesSerializer
from apps.auth_token.auth import PluginAuthentication
from common.api_helpers.mixins import PublicPrimaryKeyMixin
@ -18,11 +18,14 @@ class AlertReceiveChannelTemplateView(
viewsets.GenericViewSet,
):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
permission_classes = (IsAuthenticated, RBACPermission)
action_permissions = {
IsAdmin: MODIFY_ACTIONS,
AnyRole: READ_ACTIONS,
rbac_permissions = {
"metadata": [RBACPermission.Permissions.INTEGRATIONS_READ],
"list": [RBACPermission.Permissions.INTEGRATIONS_READ],
"retrieve": [RBACPermission.Permissions.INTEGRATIONS_READ],
"update": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"partial_update": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
}
model = AlertReceiveChannel

View file

@ -6,7 +6,7 @@ from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from apps.alerts.models import ChannelFilter
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin, IsAdminOrEditor
from apps.api.permissions import RBACPermission
from apps.api.serializers.channel_filter import (
ChannelFilterCreateSerializer,
ChannelFilterSerializer,
@ -23,11 +23,17 @@ from common.insight_log import EntityEvent, write_resource_insight_log
class ChannelFilterView(PublicPrimaryKeyMixin, CreateSerializerMixin, UpdateSerializerMixin, ModelViewSet):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
action_permissions = {
IsAdmin: (*MODIFY_ACTIONS, "move_to_position"),
IsAdminOrEditor: ("send_demo_alert",),
AnyRole: READ_ACTIONS,
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"metadata": [RBACPermission.Permissions.INTEGRATIONS_READ],
"list": [RBACPermission.Permissions.INTEGRATIONS_READ],
"retrieve": [RBACPermission.Permissions.INTEGRATIONS_READ],
"create": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"update": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"partial_update": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"destroy": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"move_to_position": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"send_demo_alert": [RBACPermission.Permissions.INTEGRATIONS_TEST],
}
model = ChannelFilter

View file

@ -4,7 +4,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.viewsets import ModelViewSet
from apps.alerts.models import CustomButton
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin
from apps.api.permissions import RBACPermission
from apps.api.serializers.custom_button import CustomButtonSerializer
from apps.auth_token.auth import PluginAuthentication
from common.api_helpers.mixins import PublicPrimaryKeyMixin, TeamFilteringMixin
@ -13,10 +13,16 @@ from common.insight_log import EntityEvent, write_resource_insight_log
class CustomButtonView(TeamFilteringMixin, PublicPrimaryKeyMixin, ModelViewSet):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
action_permissions = {
IsAdmin: MODIFY_ACTIONS,
AnyRole: READ_ACTIONS,
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"metadata": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ],
"list": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ],
"retrieve": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ],
"create": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_WRITE],
"update": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_WRITE],
"partial_update": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_WRITE],
"destroy": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_WRITE],
}
model = CustomButton

View file

@ -7,7 +7,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from apps.alerts.models import EscalationChain
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin
from apps.api.permissions import RBACPermission
from apps.api.serializers.escalation_chain import EscalationChainListSerializer, EscalationChainSerializer
from apps.auth_token.auth import PluginAuthentication
from common.api_helpers.exceptions import BadRequest
@ -17,11 +17,17 @@ from common.insight_log import EntityEvent, write_resource_insight_log
class EscalationChainViewSet(TeamFilteringMixin, PublicPrimaryKeyMixin, ListSerializerMixin, viewsets.ModelViewSet):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
permission_classes = (IsAuthenticated, RBACPermission)
action_permissions = {
IsAdmin: (*MODIFY_ACTIONS, "copy"),
AnyRole: (*READ_ACTIONS, "details"),
rbac_permissions = {
"metadata": [RBACPermission.Permissions.ESCALATION_CHAINS_READ],
"list": [RBACPermission.Permissions.ESCALATION_CHAINS_READ],
"retrieve": [RBACPermission.Permissions.ESCALATION_CHAINS_READ],
"details": [RBACPermission.Permissions.ESCALATION_CHAINS_READ],
"create": [RBACPermission.Permissions.ESCALATION_CHAINS_WRITE],
"update": [RBACPermission.Permissions.ESCALATION_CHAINS_WRITE],
"destroy": [RBACPermission.Permissions.ESCALATION_CHAINS_WRITE],
"copy": [RBACPermission.Permissions.ESCALATION_CHAINS_WRITE],
}
filter_backends = [SearchFilter]

View file

@ -7,7 +7,7 @@ from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from apps.alerts.models import EscalationPolicy
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin
from apps.api.permissions import RBACPermission
from apps.api.serializers.escalation_policy import (
EscalationPolicyCreateSerializer,
EscalationPolicySerializer,
@ -21,15 +21,19 @@ from common.insight_log import EntityEvent, write_resource_insight_log
class EscalationPolicyView(PublicPrimaryKeyMixin, CreateSerializerMixin, UpdateSerializerMixin, ModelViewSet):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
action_permissions = {
IsAdmin: (*MODIFY_ACTIONS, "move_to_position"),
AnyRole: (
*READ_ACTIONS,
"escalation_options",
"delay_options",
"num_minutes_in_window_options",
),
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"metadata": [RBACPermission.Permissions.ESCALATION_CHAINS_READ],
"list": [RBACPermission.Permissions.ESCALATION_CHAINS_READ],
"retrieve": [RBACPermission.Permissions.ESCALATION_CHAINS_READ],
"escalation_options": [RBACPermission.Permissions.ESCALATION_CHAINS_READ],
"delay_options": [RBACPermission.Permissions.ESCALATION_CHAINS_READ],
"num_minutes_in_window_options": [RBACPermission.Permissions.ESCALATION_CHAINS_READ],
"create": [RBACPermission.Permissions.ESCALATION_CHAINS_WRITE],
"update": [RBACPermission.Permissions.ESCALATION_CHAINS_WRITE],
"partial_update": [RBACPermission.Permissions.ESCALATION_CHAINS_WRITE],
"destroy": [RBACPermission.Permissions.ESCALATION_CHAINS_WRITE],
"move_to_position": [RBACPermission.Permissions.ESCALATION_CHAINS_WRITE],
}
model = EscalationPolicy

View file

@ -3,7 +3,7 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin
from apps.api.permissions import RBACPermission
from apps.api.serializers.integration_heartbeat import IntegrationHeartBeatSerializer
from apps.auth_token.auth import PluginAuthentication
from apps.heartbeat.models import IntegrationHeartBeat
@ -20,10 +20,17 @@ class IntegrationHeartBeatView(
viewsets.GenericViewSet,
):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
action_permissions = {
IsAdmin: (*MODIFY_ACTIONS, "activate", "deactivate"),
AnyRole: (*READ_ACTIONS, "timeout_options"),
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"metadata": [RBACPermission.Permissions.INTEGRATIONS_READ],
"list": [RBACPermission.Permissions.INTEGRATIONS_READ],
"retrieve": [RBACPermission.Permissions.INTEGRATIONS_READ],
"timeout_options": [RBACPermission.Permissions.INTEGRATIONS_READ],
"create": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"update": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"partial_update": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"activate": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
"deactivate": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
}
model = IntegrationHeartBeat

View file

@ -6,7 +6,7 @@ from rest_framework import status, viewsets
from rest_framework.permissions import IsAuthenticated
from telegram import error
from apps.api.permissions import IsAdmin
from apps.api.permissions import RBACPermission
from apps.api.serializers.live_setting import LiveSettingSerializer
from apps.auth_token.auth import PluginAuthentication
from apps.base.models import LiveSetting
@ -21,7 +21,14 @@ from common.api_helpers.mixins import PublicPrimaryKeyMixin
class LiveSettingViewSet(PublicPrimaryKeyMixin, viewsets.ModelViewSet):
serializer_class = LiveSettingSerializer
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"list": [RBACPermission.Permissions.OTHER_SETTINGS_READ],
"retrieve": [RBACPermission.Permissions.OTHER_SETTINGS_READ],
"create": [RBACPermission.Permissions.OTHER_SETTINGS_WRITE],
"update": [RBACPermission.Permissions.OTHER_SETTINGS_WRITE],
"destroy": [RBACPermission.Permissions.OTHER_SETTINGS_WRITE],
}
def dispatch(self, request, *args, **kwargs):
if not settings.FEATURE_LIVE_SETTINGS_ENABLED:

View file

@ -5,7 +5,7 @@ from rest_framework.views import APIView
from apps.alerts.models import AlertReceiveChannel
from apps.alerts.models.maintainable_object import MaintainableObject
from apps.api.permissions import IsAdmin
from apps.api.permissions import RBACPermission
from apps.auth_token.auth import PluginAuthentication
from common.api_helpers.exceptions import BadRequest
from common.exceptions import MaintenanceCouldNotBeStartedError
@ -39,7 +39,11 @@ class GetObjectMixin:
class MaintenanceAPIView(APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated,)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"get": [RBACPermission.Permissions.MAINTENANCE_READ],
}
def get(self, request):
organization = self.request.auth.organization
@ -77,7 +81,10 @@ class MaintenanceAPIView(APIView):
class MaintenanceStartAPIView(GetObjectMixin, APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"post": [RBACPermission.Permissions.MAINTENANCE_WRITE],
}
def post(self, request):
mode = request.data.get("mode", None)
@ -110,7 +117,10 @@ class MaintenanceStartAPIView(GetObjectMixin, APIView):
class MaintenanceStopAPIView(GetObjectMixin, APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"post": [RBACPermission.Permissions.MAINTENANCE_WRITE],
}
def post(self, request):
instance = self.get_object(request)

View file

@ -6,7 +6,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin
from apps.api.permissions import RBACPermission
from apps.api.serializers.on_call_shifts import OnCallShiftSerializer, OnCallShiftUpdateSerializer
from apps.auth_token.auth import PluginAuthentication
from apps.schedules.models import CustomOnCallShift
@ -18,11 +18,20 @@ from common.insight_log import EntityEvent, write_resource_insight_log
class OnCallShiftView(PublicPrimaryKeyMixin, UpdateSerializerMixin, ModelViewSet):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
permission_classes = (IsAuthenticated, RBACPermission)
action_permissions = {
IsAdmin: (*MODIFY_ACTIONS, "preview"),
AnyRole: (*READ_ACTIONS, "details", "frequency_options", "days_options"),
rbac_permissions = {
"metadata": [RBACPermission.Permissions.SCHEDULES_READ],
"list": [RBACPermission.Permissions.SCHEDULES_READ],
"retrieve": [RBACPermission.Permissions.SCHEDULES_READ],
"details": [RBACPermission.Permissions.SCHEDULES_READ],
"frequency_options": [RBACPermission.Permissions.SCHEDULES_READ],
"days_options": [RBACPermission.Permissions.SCHEDULES_READ],
"create": [RBACPermission.Permissions.SCHEDULES_WRITE],
"update": [RBACPermission.Permissions.SCHEDULES_WRITE],
"partial_update": [RBACPermission.Permissions.SCHEDULES_WRITE],
"destroy": [RBACPermission.Permissions.SCHEDULES_WRITE],
"preview": [RBACPermission.Permissions.SCHEDULES_WRITE],
}
model = CustomOnCallShift

View file

@ -6,7 +6,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from apps.api.permissions import AnyRole, IsAdmin, MethodPermission
from apps.api.permissions import RBACPermission
from apps.api.serializers.organization import CurrentOrganizationSerializer
from apps.auth_token.auth import PluginAuthentication
from apps.base.messaging import get_messaging_backend_from_id
@ -16,9 +16,12 @@ from common.insight_log import EntityEvent, write_resource_insight_log
class CurrentOrganizationView(APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, MethodPermission)
permission_classes = (IsAuthenticated, RBACPermission)
method_permissions = {IsAdmin: ("PUT",), AnyRole: ("GET",)}
rbac_permissions = {
"get": [],
"put": [RBACPermission.Permissions.OTHER_SETTINGS_WRITE],
}
def get(self, request):
organization = request.auth.organization
@ -46,7 +49,11 @@ class CurrentOrganizationView(APIView):
class GetTelegramVerificationCode(APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"get": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
}
def get(self, request):
organization = request.auth.organization
@ -66,7 +73,11 @@ class GetTelegramVerificationCode(APIView):
class GetChannelVerificationCode(APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"get": [RBACPermission.Permissions.INTEGRATIONS_WRITE],
}
def get(self, request):
organization = request.auth.organization
@ -81,7 +92,11 @@ class GetChannelVerificationCode(APIView):
class SetGeneralChannel(APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"post": [RBACPermission.Permissions.CHATOPS_UPDATE_SETTINGS],
}
def post(self, request):
SlackChannel = apps.get_model("slack", "SlackChannel")

View file

@ -2,7 +2,7 @@ from rest_framework import mixins, status, viewsets
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, IsAdmin
from apps.api.permissions import RBACPermission
from apps.api.serializers.public_api_token import PublicApiTokenSerializer
from apps.auth_token.auth import PluginAuthentication
from apps.auth_token.constants import MAX_PUBLIC_API_TOKENS_PER_USER
@ -19,9 +19,14 @@ class PublicApiTokenView(
viewsets.GenericViewSet,
):
authentication_classes = [PluginAuthentication]
permission_classes = [IsAuthenticated]
action_permissions = {IsAdmin: (*MODIFY_ACTIONS, *READ_ACTIONS)}
permission_classes = [IsAuthenticated, RBACPermission]
rbac_permissions = {
"metadata": [RBACPermission.Permissions.API_KEYS_READ],
"list": [RBACPermission.Permissions.API_KEYS_READ],
"retrieve": [RBACPermission.Permissions.API_KEYS_READ],
"create": [RBACPermission.Permissions.API_KEYS_WRITE],
"destroy": [RBACPermission.Permissions.API_KEYS_WRITE],
}
model = ApiAuthToken
serializer_class = PublicApiTokenSerializer

View file

@ -3,7 +3,7 @@ from rest_framework.viewsets import ModelViewSet
from apps.alerts.models import ResolutionNote
from apps.alerts.tasks import send_update_resolution_note_signal
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdminOrEditor
from apps.api.permissions import RBACPermission
from apps.api.serializers.resolution_note import ResolutionNoteSerializer, ResolutionNoteUpdateSerializer
from apps.auth_token.auth import PluginAuthentication
from common.api_helpers.mixins import PublicPrimaryKeyMixin, UpdateSerializerMixin
@ -11,11 +11,16 @@ from common.api_helpers.mixins import PublicPrimaryKeyMixin, UpdateSerializerMix
class ResolutionNoteView(PublicPrimaryKeyMixin, UpdateSerializerMixin, ModelViewSet):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
permission_classes = (IsAuthenticated, RBACPermission)
action_permissions = {
IsAdminOrEditor: MODIFY_ACTIONS,
AnyRole: READ_ACTIONS,
rbac_permissions = {
"metadata": [RBACPermission.Permissions.ALERT_GROUPS_READ],
"list": [RBACPermission.Permissions.ALERT_GROUPS_READ],
"retrieve": [RBACPermission.Permissions.ALERT_GROUPS_READ],
"create": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"update": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"partial_update": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
"destroy": [RBACPermission.Permissions.ALERT_GROUPS_WRITE],
}
model = ResolutionNote

View file

@ -14,7 +14,7 @@ from rest_framework.views import Response
from rest_framework.viewsets import ModelViewSet
from apps.alerts.models import EscalationChain, EscalationPolicy
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin, IsAdminOrEditor
from apps.api.permissions import RBACPermission
from apps.api.serializers.schedule_base import ScheduleFastSerializer
from apps.api.serializers.schedule_polymorphic import (
PolymorphicScheduleCreateSerializer,
@ -56,24 +56,26 @@ class ScheduleView(
ModelViewSet,
):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
action_permissions = {
IsAdmin: (
*MODIFY_ACTIONS,
"reload_ical",
),
IsAdminOrEditor: ("export_token",),
AnyRole: (
*READ_ACTIONS,
"events",
"filter_events",
"next_shifts_per_user",
"notify_empty_oncall_options",
"notify_oncall_shift_freq_options",
"mention_options",
"related_escalation_chains",
),
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"metadata": [RBACPermission.Permissions.SCHEDULES_READ],
"list": [RBACPermission.Permissions.SCHEDULES_READ],
"retrieve": [RBACPermission.Permissions.SCHEDULES_READ],
"events": [RBACPermission.Permissions.SCHEDULES_READ],
"filter_events": [RBACPermission.Permissions.SCHEDULES_READ],
"next_shifts_per_user": [RBACPermission.Permissions.SCHEDULES_READ],
"notify_empty_oncall_options": [RBACPermission.Permissions.SCHEDULES_READ],
"notify_oncall_shift_freq_options": [RBACPermission.Permissions.SCHEDULES_READ],
"mention_options": [RBACPermission.Permissions.SCHEDULES_READ],
"related_escalation_chains": [RBACPermission.Permissions.SCHEDULES_READ],
"create": [RBACPermission.Permissions.SCHEDULES_WRITE],
"update": [RBACPermission.Permissions.SCHEDULES_WRITE],
"partial_update": [RBACPermission.Permissions.SCHEDULES_WRITE],
"destroy": [RBACPermission.Permissions.SCHEDULES_WRITE],
"reload_ical": [RBACPermission.Permissions.SCHEDULES_WRITE],
"export_token": [RBACPermission.Permissions.SCHEDULES_EXPORT],
}
filter_backends = [SearchFilter]
search_fields = ("name",)

View file

@ -2,7 +2,7 @@ from rest_framework import views
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from apps.api.permissions import AnyRole, IsAdmin, MethodPermission
from apps.api.permissions import RBACPermission
from apps.api.serializers.organization_slack_settings import OrganizationSlackSettingsSerializer
from apps.auth_token.auth import PluginAuthentication
from apps.user_management.models import Organization
@ -11,11 +11,11 @@ from common.insight_log import EntityEvent, write_resource_insight_log
class SlackTeamSettingsAPIView(views.APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, MethodPermission)
permission_classes = (IsAuthenticated, RBACPermission)
method_permissions = {
IsAdmin: ("PUT",),
AnyRole: ("GET",),
rbac_permissions = {
"get": [RBACPermission.Permissions.CHATOPS_READ],
"put": [RBACPermission.Permissions.CHATOPS_UPDATE_SETTINGS],
}
serializer_class = OrganizationSlackSettingsSerializer

View file

@ -4,7 +4,7 @@ from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from apps.api.permissions import MODIFY_ACTIONS, READ_ACTIONS, ActionPermission, AnyRole, IsAdmin
from apps.api.permissions import RBACPermission
from apps.api.serializers.telegram import TelegramToOrganizationConnectorSerializer
from apps.auth_token.auth import PluginAuthentication
from common.api_helpers.mixins import PublicPrimaryKeyMixin
@ -19,11 +19,14 @@ class TelegramChannelViewSet(
viewsets.GenericViewSet,
):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
permission_classes = (IsAuthenticated, RBACPermission)
action_permissions = {
IsAdmin: (*MODIFY_ACTIONS, "set_default"),
AnyRole: READ_ACTIONS,
rbac_permissions = {
"metadata": [RBACPermission.Permissions.CHATOPS_READ],
"list": [RBACPermission.Permissions.CHATOPS_READ],
"retrieve": [RBACPermission.Permissions.CHATOPS_READ],
"destroy": [RBACPermission.Permissions.CHATOPS_UPDATE_SETTINGS],
"set_default": [RBACPermission.Permissions.CHATOPS_UPDATE_SETTINGS],
}
serializer_class = TelegramToOrganizationConnectorSerializer

View file

@ -16,12 +16,10 @@ from rest_framework.response import Response
from rest_framework.views import APIView
from apps.api.permissions import (
MODIFY_ACTIONS,
READ_ACTIONS,
ActionPermission,
AnyRole,
IsAdminOrEditor,
IsOwnerOrAdmin,
IsOwnerOrHasRBACPermissions,
LegacyAccessControlRole,
RBACPermission,
user_is_authorized,
)
from apps.api.serializers.team import TeamSerializer
from apps.api.serializers.user import FilterUserSerializer, UserHiddenFieldsSerializer, UserSerializer
@ -41,7 +39,6 @@ from common.api_helpers.exceptions import Conflict
from common.api_helpers.mixins import FilterSerializerMixin, PublicPrimaryKeyMixin
from common.api_helpers.paginators import HundredPageSizePaginator
from common.api_helpers.utils import create_engine_url
from common.constants.role import Role
from common.insight_log import (
ChatOpsEvent,
ChatOpsType,
@ -51,6 +48,7 @@ from common.insight_log import (
)
logger = logging.getLogger(__name__)
IsOwnerOrHasUserSettingsAdminPermission = IsOwnerOrHasRBACPermissions([RBACPermission.Permissions.USER_SETTINGS_ADMIN])
class CurrentUserView(APIView):
@ -89,7 +87,9 @@ class UserFilter(filters.FilterSet):
"""
email = filters.CharFilter(field_name="email", lookup_expr="icontains")
roles = filters.MultipleChoiceFilter(field_name="role", choices=Role.choices())
roles = filters.MultipleChoiceFilter(
field_name="role", choices=LegacyAccessControlRole.choices()
) # LEGACY.. this should get removed eventually
class Meta:
model = User
@ -109,35 +109,36 @@ class UserView(
PluginAuthentication,
)
permission_classes = (IsAuthenticated, ActionPermission)
permission_classes = (IsAuthenticated, RBACPermission)
# Non-admin users are allowed to list and retrieve users
# The overridden get_serializer_class will return
# another Serializer for non-admin users with sensitive information hidden
action_permissions = {
IsAdminOrEditor: (
*MODIFY_ACTIONS,
"list",
"metadata",
"verify_number",
"forget_number",
"get_verification_code",
"get_backend_verification_code",
"get_telegram_verification_code",
"unlink_slack",
"unlink_telegram",
"unlink_backend",
"make_test_call",
"export_token",
"mobile_app_auth_token",
),
AnyRole: ("retrieve", "timezone_options"),
rbac_permissions = {
"retrieve": [RBACPermission.Permissions.USER_SETTINGS_READ],
"timezone_options": [RBACPermission.Permissions.USER_SETTINGS_READ],
"metadata": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"list": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"update": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"partial_update": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"verify_number": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"forget_number": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"get_verification_code": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"get_backend_verification_code": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"get_telegram_verification_code": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"unlink_slack": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"unlink_telegram": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"unlink_backend": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"make_test_call": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"export_token": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"mobile_app_auth_token": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
}
action_object_permissions = {
IsOwnerOrAdmin: (
*MODIFY_ACTIONS,
*READ_ACTIONS,
rbac_object_permissions = {
IsOwnerOrHasUserSettingsAdminPermission: [
"metadata",
"list",
"retrieve",
"update",
"partial_update",
"destroy",
"verify_number",
"forget_number",
"get_verification_code",
@ -149,7 +150,7 @@ class UserView(
"make_test_call",
"export_token",
"mobile_app_auth_token",
),
],
}
filter_serializer_class = FilterUserSerializer
@ -172,14 +173,18 @@ class UserView(
filterset_class = UserFilter
def get_serializer_class(self):
is_filters_request = self.request.query_params.get("filters", "false") == "true"
request = self.request
user = request.user
kwargs = self.kwargs
is_filters_request = request.query_params.get("filters", "false") == "true"
if self.action in ["list"] and is_filters_request:
return self.get_filter_serializer_class()
is_users_own_data = (
self.kwargs.get("pk") is not None and self.kwargs.get("pk") == self.request.user.public_primary_key
)
if is_users_own_data or self.request.user.role == Role.ADMIN:
is_users_own_data = kwargs.get("pk") is not None and kwargs.get("pk") == user.public_primary_key
has_admin_permission = user_is_authorized(user, [RBACPermission.Permissions.USER_SETTINGS_ADMIN])
if is_users_own_data or has_admin_permission:
return UserSerializer
return UserHiddenFieldsSerializer

View file

@ -6,14 +6,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet
from apps.api.permissions import (
MODIFY_ACTIONS,
READ_ACTIONS,
ActionPermission,
AnyRole,
IsAdminOrEditor,
IsOwnerOrAdmin,
)
from apps.api.permissions import IsOwnerOrHasRBACPermissions, RBACPermission
from apps.api.serializers.user_notification_policy import (
UserNotificationPolicySerializer,
UserNotificationPolicyUpdateSerializer,
@ -31,18 +24,34 @@ from common.insight_log import EntityEvent, write_resource_insight_log
class UserNotificationPolicyView(UpdateSerializerMixin, ModelViewSet):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
permission_classes = (IsAuthenticated, RBACPermission)
action_permissions = {
IsAdminOrEditor: (*MODIFY_ACTIONS, "move_to_position"),
AnyRole: (*READ_ACTIONS, "delay_options", "notify_by_options"),
}
action_object_permissions = {
IsOwnerOrAdmin: (*MODIFY_ACTIONS, "move_to_position"),
AnyRole: READ_ACTIONS,
rbac_permissions = {
"metadata": [RBACPermission.Permissions.USER_SETTINGS_READ],
"list": [RBACPermission.Permissions.USER_SETTINGS_READ],
"retrieve": [RBACPermission.Permissions.USER_SETTINGS_READ],
"delay_options": [RBACPermission.Permissions.USER_SETTINGS_READ],
"notify_by_options": [RBACPermission.Permissions.USER_SETTINGS_READ],
"create": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"update": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"partial_update": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"destroy": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
"move_to_position": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
}
ownership_field = "user"
IsOwnerOrHasUserSettingsAdminPermission = IsOwnerOrHasRBACPermissions(
required_permissions=[RBACPermission.Permissions.USER_SETTINGS_ADMIN], ownership_field="user"
)
rbac_object_permissions = {
IsOwnerOrHasUserSettingsAdminPermission: [
"create",
"update",
"partial_update",
"destroy",
"move_to_position",
],
}
model = UserNotificationPolicy
serializer_class = UserNotificationPolicySerializer

View file

@ -8,11 +8,11 @@ from rest_framework import exceptions
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework.request import Request
from apps.api.permissions import RBACPermission, user_is_authorized
from apps.grafana_plugin.helpers.gcom import check_token
from apps.user_management.models import User
from apps.user_management.models.organization import Organization
from apps.user_management.models.region import OrganizationMovedException
from common.constants.role import Role
from .constants import SCHEDULE_EXPORT_TOKEN_NAME, SLACK_AUTH_TOKEN_NAME
from .exceptions import InvalidToken
@ -29,7 +29,7 @@ class ApiTokenAuthentication(BaseAuthentication):
auth = get_authorization_header(request).decode("utf-8")
user, auth_token = self.authenticate_credentials(auth)
if user.role != Role.ADMIN:
if not user_is_authorized(user, [RBACPermission.Permissions.API_KEYS_WRITE]):
raise exceptions.AuthenticationFailed(
"Only users with Admin permissions are allowed to perform this action."
)

View file

@ -1,23 +0,0 @@
# This is temporary solution to not to hardcode permissions on frontend
# Is should be removed with one which will collect permission from action_permission views' attribute
ALL_PERMISSIONS = [
"update_incidents",
"update_alert_receive_channels",
"update_escalation_policies",
"update_notification_policies",
"update_general_log_channel_id",
"update_own_settings",
"update_other_users_settings",
"update_integrations",
"update_schedules",
"update_custom_actions",
"update_api_tokens",
"update_teams",
"update_maintenances",
"update_global_settings",
"send_demo_alert",
"view_other_users",
]
ADMIN_PERMISSIONS = ALL_PERMISSIONS
EDITOR_PERMISSIONS = ["update_incidents", "update_own_settings", "view_other_users"]
ALL_ROLES_PERMISSIONS = []

View file

@ -68,7 +68,7 @@ class UserNotificationPolicyLogRecord(models.Model):
ERROR_NOTIFICATION_IN_SLACK_CHANNEL_IS_ARCHIVED,
ERROR_NOTIFICATION_IN_SLACK_RATELIMIT,
ERROR_NOTIFICATION_MESSAGING_BACKEND_ERROR,
ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE,
ERROR_NOTIFICATION_FORBIDDEN,
ERROR_NOTIFICATION_TELEGRAM_USER_IS_DEACTIVATED,
) = range(27)
@ -258,10 +258,8 @@ class UserNotificationPolicyLogRecord(models.Model):
result += f"failed to notify {user_verbal} in Slack, because channel is archived"
elif self.notification_error_code == UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_IN_SLACK_RATELIMIT:
result += f"failed to notify {user_verbal} in Slack due to Slack rate limit"
elif (
self.notification_error_code == UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_NOT_ALLOWED_USER_ROLE
):
result += f"failed to notify {user_verbal}, not allowed role"
elif self.notification_error_code == UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_FORBIDDEN:
result += f"failed to notify {user_verbal}, not allowed"
elif (
self.notification_error_code
== UserNotificationPolicyLogRecord.ERROR_NOTIFICATION_TELEGRAM_USER_IS_DEACTIVATED

View file

@ -1,7 +1,7 @@
import json
import logging
import time
from typing import Optional, Tuple
from typing import Dict, List, Optional, Tuple, TypedDict
from urllib.parse import urljoin
import requests
@ -9,23 +9,51 @@ from django.conf import settings
from rest_framework import status
from rest_framework.response import Response
from apps.api.permissions import ACTION_PREFIX, GrafanaAPIPermission
logger = logging.getLogger(__name__)
class GrafanaUser(TypedDict):
orgId: int
userId: int
email: str
name: str
avatarUrl: str
login: str
role: str
lastSeenAt: str
lastSeenAtAge: str
class GrafanaUserWithPermissions(GrafanaUser):
permissions: List[GrafanaAPIPermission]
class GCOMInstanceInfo(TypedDict):
id: int
orgId: int
slug: str
orgSlug: str
orgName: str
url: str
status: str
class APIClient:
def __init__(self, api_url: str, api_token: str):
self.api_url = api_url
self.api_token = api_token
def api_head(self, endpoint: str, body: dict = None) -> Tuple[Optional[Response], dict]:
return self.call_api(endpoint, requests.head, body)
def api_get(self, endpoint: str) -> Tuple[Optional[Response], dict]:
return self.call_api(endpoint, requests.get)
def api_post(self, endpoint: str, body: dict = None) -> Tuple[Optional[Response], dict]:
return self.call_api(endpoint, requests.post, body)
def api_head(self, endpoint: str, body: dict = None) -> Tuple[Optional[Response], dict]:
return self.call_api(endpoint, requests.head, body)
def call_api(self, endpoint: str, http_method, body: dict = None) -> Tuple[Optional[Response], dict]:
request_start = time.perf_counter()
call_status = {
@ -72,30 +100,60 @@ class APIClient:
class GrafanaAPIClient(APIClient):
USER_PERMISSION_ENDPOINT = f"api/access-control/users/permissions?actionPrefix={ACTION_PREFIX}"
def __init__(self, api_url: str, api_token: str):
super().__init__(api_url, api_token)
def check_token(self) -> Tuple[Optional[Response], dict]:
return self.api_head("api/org")
def get_users(self) -> Tuple[Optional[Response], dict]:
def get_users_permissions(self, rbac_is_enabled_for_org: bool) -> Dict[str, List[GrafanaAPIPermission]]:
"""
Response example:
[
{
'orgId': 1,
'userId': 1,
'email': 'user@example.com',
'name': 'User User',
'avatarUrl': '/avatar/79163f696e9e08958c0d3f73c160e2cc',
'login': 'user',
'role': 'Admin',
'lastSeenAt': '2021-06-21T07:01:45Z',
'lastSeenAtAge': '9m'
},
]
It is possible that this endpoint may not be available for certain Grafana orgs.
Ex: for Grafana Cloud orgs whom have pinned their Grafana version to an earlier version
where this endpoint is not available
The response from the Grafana endpoint will look something like this:
{
"1": {
"grafana-oncall-app.alert-groups:read": [
""
],
"grafana-oncall-app.alert-groups:write": [
""
]
}
}
"""
return self.api_get("api/org/users")
if not rbac_is_enabled_for_org:
return {}
data, _ = self.api_get(self.USER_PERMISSION_ENDPOINT)
if data is None:
return {}
all_users_permissions = {}
for user_id, user_permissions in data.items():
all_users_permissions[user_id] = [GrafanaAPIPermission(action=key) for key, _ in user_permissions.items()]
return all_users_permissions
def is_rbac_enabled_for_organization(self) -> bool:
_, resp_status = self.api_head(self.USER_PERMISSION_ENDPOINT)
return resp_status["status_code"] == status.HTTP_200_OK
def get_users(self, rbac_is_enabled_for_org: bool) -> List[GrafanaUserWithPermissions]:
users, _ = self.api_get("api/org/users")
if not users:
return []
user_permissions = self.get_users_permissions(rbac_is_enabled_for_org)
# merge the users permissions response into the org users response
for user in users:
user["permissions"] = user_permissions.get(str(user["userId"]), [])
return users
def get_teams(self):
return self.api_get("api/teams/search?perpage=1000000")
@ -127,6 +185,7 @@ class GcomAPIClient(APIClient):
ACTIVE_INSTANCE_QUERY = "instances?status=active"
DELETED_INSTANCE_QUERY = "instances?status=deleted&includeDeleted=true"
STACK_STATUS_DELETED = "deleted"
STACK_STATUS_ACTIVE = "active"
def __init__(self, api_token: str):
super().__init__(settings.GRAFANA_COM_API_URL, api_token)
@ -134,14 +193,15 @@ class GcomAPIClient(APIClient):
def check_token(self):
return self.api_post("api-keys/check", {"token": self.api_token})
def get_instance_info(self, stack_id: str):
return self.api_get(f"instances/{stack_id}")
def get_instance_info(self, stack_id: str) -> Optional[GCOMInstanceInfo]:
data, _ = self.api_get(f"instances/{stack_id}?config=true")
return data
def get_instances(self, query: str):
return self.api_get(query)
def is_stack_deleted(self, stack_id: str) -> bool:
instance_info, call_status = self.get_instance_info(stack_id)
instance_info = self.get_instance_info(stack_id)
return instance_info and instance_info.get("status") == self.STACK_STATUS_DELETED
def post_active_users(self, body):

View file

@ -40,10 +40,12 @@ def check_gcom_permission(token_string: str, context) -> Optional["GcomToken"]:
logger.debug(f"Start authenticate by making request to gcom api for org={org_id}, stack_id={stack_id}")
client = GcomAPIClient(token_string)
instance_info, status = client.get_instance_info(stack_id)
instance_info = client.get_instance_info(stack_id)
if not instance_info or str(instance_info["orgId"]) != org_id:
raise InvalidToken
rbac_is_enabled = client.is_rbac_enabled_for_organization()
if not organization:
DynamicSetting = apps.get_model("base", "DynamicSetting")
allow_signup = DynamicSetting.objects.get_or_create(
@ -60,6 +62,7 @@ def check_gcom_permission(token_string: str, context) -> Optional["GcomToken"]:
region_slug=instance_info["regionSlug"],
gcom_token=token_string,
gcom_token_org_last_time_synced=timezone.now(),
is_rbac_permissions_enabled=rbac_is_enabled,
)
else:
organization.stack_slug = instance_info["slug"]
@ -69,6 +72,7 @@ def check_gcom_permission(token_string: str, context) -> Optional["GcomToken"]:
organization.grafana_url = instance_info["url"]
organization.gcom_token = token_string
organization.gcom_token_org_last_time_synced = timezone.now()
organization.is_rbac_permissions_enabled = rbac_is_enabled
organization.save(
update_fields=[
"stack_slug",
@ -78,6 +82,7 @@ def check_gcom_permission(token_string: str, context) -> Optional["GcomToken"]:
"grafana_url",
"gcom_token",
"gcom_token_org_last_time_synced",
"is_rbac_permissions_enabled",
]
)
logger.debug(f"Finish authenticate by making request to gcom api for org={org_id}, stack_id={stack_id}")

View file

@ -68,8 +68,8 @@ def run_organization_sync(organization_pk, force_sync):
return
if settings.GRAFANA_COM_API_TOKEN and settings.LICENSE == settings.CLOUD_LICENSE_NAME:
client = GcomAPIClient(settings.GRAFANA_COM_API_TOKEN)
instance_info, status = client.get_instance_info(organization.stack_id)
if not instance_info or instance_info["status"] != "active":
instance_info = client.get_instance_info(organization.stack_id)
if not instance_info or instance_info["status"] != client.STACK_STATUS_ACTIVE:
logger.debug(f"Canceling sync for Organization {organization_pk}, as it is no longer active.")
return

View file

@ -0,0 +1,60 @@
from unittest.mock import patch
import pytest
from rest_framework import status
from apps.grafana_plugin.helpers.client import GrafanaAPIClient
API_URL = "/foo/bar"
API_TOKEN = "dfjkfdjkfd"
class TestGetUsersPermissions:
def test_rbac_is_not_enabled_for_org(self):
api_client = GrafanaAPIClient(API_URL, API_TOKEN)
permissions = api_client.get_users_permissions(False)
assert len(permissions.keys()) == 0
@patch("apps.grafana_plugin.views.self_hosted_install.GrafanaAPIClient.api_get")
def test_api_call_returns_none(self, mocked_grafana_api_client_api_get):
mocked_grafana_api_client_api_get.return_value = (None, "dfkjfdkj")
api_client = GrafanaAPIClient(API_URL, API_TOKEN)
permissions = api_client.get_users_permissions(True)
assert len(permissions.keys()) == 0
@patch("apps.grafana_plugin.views.self_hosted_install.GrafanaAPIClient.api_get")
def test_it_properly_transforms_the_data(self, mocked_grafana_api_client_api_get):
mocked_grafana_api_client_api_get.return_value = (
{"1": {"grafana-oncall-app.alert-groups:read": [""], "grafana-oncall-app.alert-groups:write": [""]}},
"asdfasdf",
)
api_client = GrafanaAPIClient(API_URL, API_TOKEN)
permissions = api_client.get_users_permissions(True)
assert permissions == {
"1": [
{"action": "grafana-oncall-app.alert-groups:read"},
{"action": "grafana-oncall-app.alert-groups:write"},
]
}
class TestIsRbacEnabledForOrganization:
@pytest.mark.parametrize(
"grafana_api_status_code,expected",
[
(status.HTTP_200_OK, True),
(status.HTTP_404_NOT_FOUND, False),
],
)
@patch("apps.grafana_plugin.views.self_hosted_install.GrafanaAPIClient.api_head")
def test_it_returns_based_on_status_code_of_head_call(
self, mocked_grafana_api_client_api_head, grafana_api_status_code, expected
):
mocked_grafana_api_client_api_head.return_value = (None, {"status_code": grafana_api_status_code})
api_client = GrafanaAPIClient(API_URL, API_TOKEN)
assert api_client.is_rbac_enabled_for_organization() == expected

View file

@ -99,6 +99,7 @@ def test_if_organization_exists_it_is_updated(
mocked_provision_plugin.return_value = provision_plugin_response
mocked_grafana_api_client.return_value.check_token.return_value = (None, {"status_code": status.HTTP_200_OK})
mocked_grafana_api_client.return_value.is_rbac_enabled_for_organization.return_value = True
client = APIClient()
url = reverse("grafana-plugin:self-hosted-install")
@ -106,6 +107,8 @@ def test_if_organization_exists_it_is_updated(
assert mocked_grafana_api_client.called_once_with(api_url=GRAFANA_API_URL, api_token=GRAFANA_TOKEN)
assert mocked_grafana_api_client.return_value.check_token.called_once_with()
assert mocked_grafana_api_client.return_value.is_rbac_enabled_for_organization.called_once_with()
assert mocked_sync_organization.called_once_with(organization)
assert mocked_provision_plugin.called_once_with()
assert mocked_revoke_plugin.called_once_with()
@ -117,6 +120,7 @@ def test_if_organization_exists_it_is_updated(
assert organization.grafana_url == GRAFANA_API_URL
assert organization.api_token == GRAFANA_TOKEN
assert organization.is_rbac_permissions_enabled is True
@override_settings(SELF_HOSTED_SETTINGS=SELF_HOSTED_SETTINGS)
@ -136,6 +140,7 @@ def test_if_organization_does_not_exist_it_is_created(
mocked_provision_plugin.return_value = provision_plugin_response
mocked_grafana_api_client.return_value.check_token.return_value = (None, {"status_code": status.HTTP_200_OK})
mocked_grafana_api_client.return_value.is_rbac_enabled_for_organization.return_value = True
client = APIClient()
url = reverse("grafana-plugin:self-hosted-install")
@ -146,6 +151,8 @@ def test_if_organization_does_not_exist_it_is_created(
assert mocked_grafana_api_client.called_once_with(api_url=GRAFANA_API_URL, api_token=GRAFANA_TOKEN)
assert mocked_grafana_api_client.return_value.check_token.called_once_with()
assert mocked_grafana_api_client.return_value.is_rbac_enabled_for_organization.called_once_with()
assert mocked_sync_organization.called_once_with(organization)
assert mocked_provision_plugin.called_once_with()
assert not mocked_revoke_plugin.called
@ -160,3 +167,4 @@ def test_if_organization_does_not_exist_it_is_created(
assert organization.region_slug == REGION_SLUG
assert organization.grafana_url == GRAFANA_API_URL
assert organization.api_token == GRAFANA_TOKEN
assert organization.is_rbac_permissions_enabled is True

View file

@ -26,6 +26,8 @@ class TestGcomAPIClient:
info = None
status = None
STACK_STATUS_ACTIVE = "active"
def reset(self):
self.called = False
self.info = None
@ -39,7 +41,7 @@ class TestGcomAPIClient:
def get_instance_info(self, stack_id: str):
self.called = True
return self.info, self.status
return self.info
@pytest.mark.django_db

View file

@ -43,11 +43,14 @@ class SelfHostedInstallView(GrafanaHeadersMixin, APIView):
return Response(data=provisioning_info, status=status.HTTP_400_BAD_REQUEST)
organization = Organization.objects.filter(stack_id=stack_id, org_id=org_id).first()
rbac_is_enabled = grafana_api_client.is_rbac_enabled_for_organization()
if organization:
organization.revoke_plugin()
organization.grafana_url = grafana_url
organization.api_token = grafana_api_token
organization.save(update_fields=["grafana_url", "api_token"])
organization.is_rbac_permissions_enabled = rbac_is_enabled
organization.save(update_fields=["grafana_url", "api_token", "is_rbac_permissions_enabled"])
else:
organization = Organization.objects.create(
stack_id=stack_id,
@ -58,6 +61,7 @@ class SelfHostedInstallView(GrafanaHeadersMixin, APIView):
region_slug=settings.SELF_HOSTED_SETTINGS["REGION_SLUG"],
grafana_url=grafana_url,
api_token=grafana_api_token,
is_rbac_permissions_enabled=rbac_is_enabled,
)
sync_organization(organization)

View file

@ -8,7 +8,6 @@ from apps.base.utils import live_settings
from apps.oss_installation.models.cloud_user_identity import CloudUserIdentity
from apps.user_management.models import User
from common.api_helpers.utils import create_engine_url
from common.constants.role import Role
from settings.base import GRAFANA_CLOUD_ONCALL_API_URL
logger = logging.getLogger(__name__)
@ -61,7 +60,7 @@ class CloudConnector(models.Model):
logger.warning("Unable to sync with cloud. GRAFANA_CLOUD_ONCALL_TOKEN is not set")
error_msg = "GRAFANA_CLOUD_ONCALL_TOKEN is not set"
existing_emails = list(User.objects.filter(role__in=(Role.ADMIN, Role.EDITOR)).values_list("email", flat=True))
existing_emails = [user.email for user in User.objects.all() if user.is_notification_allowed]
matching_users = []
users_url = create_engine_url("api/v1/users", override_base=GRAFANA_CLOUD_ONCALL_API_URL)

View file

@ -3,7 +3,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from apps.api.permissions import IsAdmin
from apps.api.permissions import RBACPermission
from apps.auth_token.auth import PluginAuthentication
from apps.base.models import LiveSetting
from apps.base.utils import live_settings
@ -13,7 +13,11 @@ from apps.oss_installation.models import CloudConnector, CloudHeartbeat
class CloudConnectionView(APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"get": [RBACPermission.Permissions.OTHER_SETTINGS_WRITE],
"delete": [RBACPermission.Permissions.OTHER_SETTINGS_WRITE],
}
def get(self, request):
connector = CloudConnector.objects.first()

View file

@ -3,7 +3,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from apps.api.permissions import IsAdmin
from apps.api.permissions import RBACPermission
from apps.auth_token.auth import PluginAuthentication
from apps.oss_installation.cloud_heartbeat import get_heartbeat_link, setup_heartbeat_integration
from apps.oss_installation.models import CloudConnector, CloudHeartbeat
@ -11,7 +11,10 @@ from apps.oss_installation.models import CloudConnector, CloudHeartbeat
class CloudHeartbeatView(APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"post": [RBACPermission.Permissions.OTHER_SETTINGS_WRITE],
}
def post(self, request):
connector = CloudConnector.objects.first()

View file

@ -6,7 +6,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from apps.api.permissions import ActionPermission, AnyRole, IsAdmin, IsOwnerOrAdmin
from apps.api.permissions import IsOwnerOrHasRBACPermissions, RBACPermission
from apps.auth_token.auth import PluginAuthentication
from apps.oss_installation.models import CloudConnector, CloudUserIdentity
from apps.oss_installation.serializers import CloudUserSerializer
@ -14,17 +14,26 @@ from apps.oss_installation.utils import cloud_user_identity_status
from apps.user_management.models import User
from common.api_helpers.mixins import PublicPrimaryKeyMixin
from common.api_helpers.paginators import HundredPageSizePaginator
from common.constants.role import Role
PERMISSIONS = [RBACPermission.Permissions.OTHER_SETTINGS_WRITE]
class CloudUsersView(HundredPageSizePaginator, APIView):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, IsAdmin)
permission_classes = (IsAuthenticated, RBACPermission)
rbac_permissions = {
"get": PERMISSIONS,
"post": PERMISSIONS,
}
def get(self, request):
organization = request.user.organization
queryset = User.objects.filter(organization=organization, role__in=[Role.ADMIN, Role.EDITOR])
queryset = User.objects.filter(
organization=organization,
**User.build_permissions_query(RBACPermission.Permissions.NOTIFICATIONS_READ, organization),
)
if request.user.current_team is not None:
queryset = queryset.filter(teams=request.user.current_team).distinct()
@ -81,15 +90,24 @@ class CloudUserView(
viewsets.GenericViewSet,
):
authentication_classes = (PluginAuthentication,)
permission_classes = (IsAuthenticated, ActionPermission)
permission_classes = (IsAuthenticated, RBACPermission)
action_permissions = {
AnyRole: ("retrieve",),
IsAdmin: ("sync",),
rbac_permissions = {
"retrieve": PERMISSIONS,
"sync": PERMISSIONS,
}
action_object_permissions = {
IsOwnerOrAdmin: ("retrieve", "sync"),
IsOwnerOrHasUserSettingsAdminPermission = IsOwnerOrHasRBACPermissions(
[RBACPermission.Permissions.USER_SETTINGS_ADMIN]
)
rbac_object_permissions = {
IsOwnerOrHasUserSettingsAdminPermission: [
"retrieve",
"sync",
],
}
serializer_class = CloudUserSerializer
def get_queryset(self):

View file

@ -1,9 +1,9 @@
from rest_framework import serializers
from apps.api.permissions import LegacyAccessControlRole
from apps.slack.models import SlackUserIdentity
from apps.user_management.models import User
from common.api_helpers.mixins import EagerLoadingMixin
from common.constants.role import Role
class SlackUserIdentitySerializer(serializers.ModelSerializer):
@ -21,7 +21,7 @@ class SlackUserIdentitySerializer(serializers.ModelSerializer):
class FastUserSerializer(serializers.ModelSerializer):
id = serializers.ReadOnlyField(read_only=True, source="public_primary_key")
email = serializers.EmailField(read_only=True)
role = serializers.SerializerMethodField()
role = serializers.SerializerMethodField() # LEGACY, should be removed eventually
is_phone_number_verified = serializers.SerializerMethodField()
class Meta:
@ -30,7 +30,10 @@ class FastUserSerializer(serializers.ModelSerializer):
@staticmethod
def get_role(obj):
return Role(obj.role).name.lower()
"""
LEGACY, should be removed eventually
"""
return LegacyAccessControlRole(obj.role).name.lower()
def get_is_phone_number_verified(self, obj):
return obj.verified_phone_number is not None
@ -39,8 +42,8 @@ class FastUserSerializer(serializers.ModelSerializer):
class UserSerializer(serializers.ModelSerializer, EagerLoadingMixin):
id = serializers.ReadOnlyField(read_only=True, source="public_primary_key")
email = serializers.EmailField(read_only=True)
role = serializers.SerializerMethodField()
slack = SlackUserIdentitySerializer(read_only=True, source="slack_user_identity")
role = serializers.SerializerMethodField() # LEGACY, should be removed eventually
is_phone_number_verified = serializers.SerializerMethodField()
SELECT_RELATED = [
@ -54,7 +57,10 @@ class UserSerializer(serializers.ModelSerializer, EagerLoadingMixin):
@staticmethod
def get_role(obj):
return Role(obj.role).name.lower()
"""
LEGACY, should be removed eventually
"""
return LegacyAccessControlRole(obj.role).name.lower()
def get_is_phone_number_verified(self, obj):
return obj.verified_phone_number is not None

View file

@ -3,7 +3,7 @@ from django.urls import reverse
from rest_framework import status
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.fixture()
@ -20,7 +20,7 @@ def user_public_api_setup(
def test_get_user(
user_public_api_setup,
):
organization, user, token, slack_team_identity, slack_user_identity = user_public_api_setup
_, user, token, slack_team_identity, slack_user_identity = user_public_api_setup
client = APIClient()
@ -93,7 +93,7 @@ def test_get_users_list_short(
user_public_api_setup,
make_user_for_organization,
):
organization, user_1, token, slack_team_identity, slack_user_identity = user_public_api_setup
organization, user_1, token, _, _ = user_public_api_setup
user_2 = make_user_for_organization(organization)
client = APIClient()
@ -145,13 +145,10 @@ def test_forbidden_access(
@pytest.mark.django_db
def test_get_users_list_all_role_users(
user_public_api_setup,
make_user_for_organization,
):
def test_get_users_list_all_role_users(user_public_api_setup, make_user_for_organization):
organization, admin, token, _, _ = user_public_api_setup
editor = make_user_for_organization(organization, role=Role.EDITOR)
viewer = make_user_for_organization(organization, role=Role.VIEWER)
editor = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
viewer = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
client = APIClient()

View file

@ -5,6 +5,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.views import Response
from rest_framework.viewsets import ReadOnlyModelViewSet
from apps.api.permissions import LegacyAccessControlRole
from apps.auth_token.auth import ApiTokenAuthentication, UserScheduleExportAuthentication
from apps.public_api.custom_renderers import CalendarRenderer
from apps.public_api.serializers import FastUserSerializer, UserSerializer
@ -14,7 +15,6 @@ from apps.schedules.models import OnCallSchedule
from apps.user_management.models import User
from common.api_helpers.mixins import RateLimitHeadersMixin, ShortSerializerMixin
from common.api_helpers.paginators import HundredPageSizePaginator
from common.constants.role import Role
class UserFilter(filters.FilterSet):
@ -23,7 +23,9 @@ class UserFilter(filters.FilterSet):
"""
email = filters.CharFilter(field_name="email", lookup_expr="iexact")
roles = filters.MultipleChoiceFilter(field_name="role", choices=Role.choices())
roles = filters.MultipleChoiceFilter(
field_name="role", choices=LegacyAccessControlRole.choices()
) # LEGACY, should be removed eventually
username = filters.CharFilter(field_name="username", lookup_expr="iexact")
class Meta:

View file

@ -13,6 +13,7 @@ from django.db.models import Q
from django.utils import timezone
from icalendar import Calendar
from apps.api.permissions import RBACPermission
from apps.schedules.constants import (
ICAL_ATTENDEE,
ICAL_DATETIME_END,
@ -25,7 +26,6 @@ from apps.schedules.constants import (
RE_PRIORITY,
)
from apps.schedules.ical_events import ical_events
from common.constants.role import Role
from common.utils import timed_lru_cache
"""
@ -40,11 +40,16 @@ if TYPE_CHECKING:
def users_in_ical(usernames_from_ical, organization, include_viewers=False):
"""
Parse ical file and return list of users found
NOTE: only grafana username will be used, consider adding grafana email and id
"""
# Only grafana username will be used, consider adding grafana email and id
from apps.user_management.models import User
users_found_in_ical = organization.users
if not include_viewers:
users_found_in_ical = users_found_in_ical.filter(role__in=(Role.ADMIN, Role.EDITOR))
# TODO: this is a breaking change....
users_found_in_ical = users_found_in_ical.filter(
**User.build_permissions_query(RBACPermission.Permissions.SCHEDULES_WRITE, organization)
)
user_emails = [v.lower() for v in usernames_from_ical]
users_found_in_ical = users_found_in_ical.filter(

View file

@ -5,6 +5,7 @@ import pytest
import pytz
from django.utils import timezone
from apps.api.permissions import LegacyAccessControlRole
from apps.schedules.ical_utils import (
list_of_oncall_shifts_from_ical,
list_users_to_notify_from_ical,
@ -12,7 +13,6 @@ from apps.schedules.ical_utils import (
users_in_ical,
)
from apps.schedules.models import CustomOnCallShift, OnCallScheduleCalendar
from common.constants.role import Role
@pytest.mark.django_db
@ -26,13 +26,10 @@ def test_users_in_ical_email_case_insensitive(make_organization_and_user, make_u
@pytest.mark.django_db
@pytest.mark.parametrize(
"include_viewers",
[True, False],
)
@pytest.mark.parametrize("include_viewers", [True, False])
def test_users_in_ical_viewers_inclusion(make_organization_and_user, make_user_for_organization, include_viewers):
organization, user = make_organization_and_user()
viewer = make_user_for_organization(organization, Role.VIEWER)
viewer = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
usernames = [user.username, viewer.username]
result = users_in_ical(usernames, organization, include_viewers=include_viewers)
@ -43,15 +40,12 @@ def test_users_in_ical_viewers_inclusion(make_organization_and_user, make_user_f
@pytest.mark.django_db
@pytest.mark.parametrize(
"include_viewers",
[True, False],
)
@pytest.mark.parametrize("include_viewers", [True, False])
def test_list_users_to_notify_from_ical_viewers_inclusion(
make_organization_and_user, make_user_for_organization, make_schedule, make_on_call_shift, include_viewers
):
organization, user = make_organization_and_user()
viewer = make_user_for_organization(organization, Role.VIEWER)
viewer = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
schedule = make_schedule(organization, schedule_class=OnCallScheduleCalendar)
date = timezone.now().replace(tzinfo=None, microsecond=0)

View file

@ -4,9 +4,9 @@ import pytest
import pytz
from django.utils import timezone
from apps.api.permissions import LegacyAccessControlRole
from apps.schedules.ical_utils import memoized_users_in_ical
from apps.schedules.models import CustomOnCallShift, OnCallSchedule, OnCallScheduleCalendar, OnCallScheduleWeb
from common.constants.role import Role
@pytest.mark.django_db
@ -18,7 +18,7 @@ def test_filter_events(make_organization, make_user_for_organization, make_sched
name="test_web_schedule",
)
user = make_user_for_organization(organization)
viewer = make_user_for_organization(organization, role=Role.VIEWER)
viewer = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
start_date = now - timezone.timedelta(days=7)
@ -190,7 +190,7 @@ def test_filter_events_include_empty(make_organization, make_user_for_organizati
schedule_class=OnCallScheduleWeb,
name="test_web_schedule",
)
user = make_user_for_organization(organization, role=Role.VIEWER)
user = make_user_for_organization(organization, role=LegacyAccessControlRole.VIEWER)
now = timezone.now().replace(hour=0, minute=0, second=0, microsecond=0)
start_date = now - timezone.timedelta(days=7)

View file

@ -4,10 +4,11 @@ from django.apps import apps
from django.db import models
from django.db.models import JSONField
from apps.api.permissions import RBACPermission
from apps.slack.constants import SLACK_INVALID_AUTH_RESPONSE, SLACK_WRONG_TEAM_NAMES
from apps.slack.slack_client import SlackClientWithErrorHandling
from apps.slack.slack_client.exceptions import SlackAPIException, SlackAPITokenException
from common.constants.role import Role
from apps.user_management.models.user import User
from common.insight_log.chatops_insight_logs import ChatOpsEvent, ChatOpsType, write_chatops_insight_log
logger = logging.getLogger(__name__)
@ -127,8 +128,10 @@ class SlackTeamIdentity(models.Model):
sc = SlackClientWithErrorHandling(self.bot_access_token)
members = self.get_conversation_members(sc, channel_id)
users = organization.users.filter(slack_user_identity__slack_id__in=members, role__in=[Role.ADMIN, Role.EDITOR])
return users
return organization.users.filter(
slack_user_identity__slack_id__in=members,
**User.build_permissions_query(RBACPermission.Permissions.CHATOPS_WRITE, organization),
)
def get_conversation_members(self, slack_client, channel_id):
try:

View file

@ -7,9 +7,10 @@ from django.db import models
from django.db.models import JSONField
from django.utils import timezone
from apps.api.permissions import RBACPermission
from apps.slack.slack_client import SlackClientWithErrorHandling
from apps.slack.slack_client.exceptions import SlackAPIException
from common.constants.role import Role
from apps.user_management.models.user import User
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
logger = logging.getLogger(__name__)
@ -105,7 +106,8 @@ class SlackUserGroup(models.Model):
def get_users_from_members_for_organization(self, organization):
return organization.users.filter(
slack_user_identity__slack_id__in=self.members, role__in=[Role.ADMIN, Role.EDITOR]
slack_user_identity__slack_id__in=self.members,
**User.build_permissions_query(RBACPermission.Permissions.CHATOPS_WRITE, organization),
)
@classmethod

View file

@ -5,8 +5,8 @@ from django.db import transaction
from jinja2 import TemplateSyntaxError
from rest_framework.response import Response
from apps.api.permissions import RBACPermission
from apps.slack.scenarios import scenario_step
from common.constants.role import Role
from common.insight_log import EntityEvent, write_resource_insight_log
from common.jinja_templater import jinja_template_env
@ -21,7 +21,7 @@ class OpenAlertAppearanceDialogStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "open Alert Appearance"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):

View file

@ -14,6 +14,7 @@ from apps.alerts.incident_appearance.renderers.slack_renderer import AlertSlackR
from apps.alerts.models import AlertGroup, AlertGroupLogRecord, AlertReceiveChannel, Invitation
from apps.alerts.tasks import custom_button_result
from apps.alerts.utils import render_curl_command
from apps.api.permissions import RBACPermission
from apps.slack.constants import CACHE_UPDATE_INCIDENT_SLACK_MESSAGE_LIFETIME, SLACK_RATE_LIMIT_DELAY
from apps.slack.scenarios import scenario_step
from apps.slack.scenarios.slack_renderer import AlertGroupLogSlackRenderer
@ -31,7 +32,6 @@ from apps.slack.tasks import (
update_incident_slack_message,
)
from apps.slack.utils import get_cache_key_update_incident_slack_message
from common.constants.role import Role
from common.utils import clean_markup, is_string_with_visible_characters
from .step_mixins import CheckAlertIsUnarchivedMixin, IncidentActionsAccessControlMixin
@ -222,7 +222,7 @@ class InviteOtherPersonToIncident(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "invite to incident"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -263,7 +263,7 @@ class SilenceGroupStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "silence incident"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -293,7 +293,7 @@ class UnSilenceGroupStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "unsilence incident"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -317,7 +317,7 @@ class SelectAttachGroupStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "Select Incident for Attaching to"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -473,7 +473,7 @@ class AttachGroupStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "Attach incident"
def process_signal(self, log_record):
@ -536,7 +536,7 @@ class UnAttachGroupStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "Unattach incident"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -555,7 +555,7 @@ class StopInvitationProcess(CheckAlertIsUnarchivedMixin, IncidentActionsAccessCo
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "stop invitation"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -580,7 +580,8 @@ class CustomButtonProcessStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
# TODO:
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "click custom button"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -642,7 +643,7 @@ class ResolveGroupStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "resolve incident"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -688,7 +689,7 @@ class UnResolveGroupStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "unresolve incident"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -711,7 +712,7 @@ class AcknowledgeGroupStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "acknowledge incident"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -737,7 +738,7 @@ class UnAcknowledgeGroupStep(
scenario_step.ScenarioStep.TAG_INCIDENT_ROUTINE,
]
ALLOWED_ROLES = [Role.ADMIN, Role.EDITOR]
REQUIRED_PERMISSIONS = [RBACPermission.Permissions.CHATOPS_WRITE]
ACTION_VERBOSE = "unacknowledge incident"
def process_scenario(self, slack_user_identity, slack_team_identity, payload, action=None):

View file

@ -13,7 +13,6 @@ from apps.slack.slack_client.exceptions import (
SlackAPIRateLimitException,
SlackAPITokenException,
)
from common.constants.role import Role
logger = logging.getLogger(__name__)
@ -162,15 +161,6 @@ class ScenarioStep(object):
step = step_class(slack_team_identity)
step.process_scenario(slack_user_identity, slack_team_identity, payload, action=action, **kwargs)
def get_permission_denied_prompt(self):
current_role = self.user.get_role_display()
admins_queryset = self.organization.users.filter(role=Role.ADMIN).select_related("slack_user_identity")
admins_verbal = "No admins"
if admins_queryset.count() > 0:
admins_verbal = ", ".join(["<@{}>".format(admin.slack_user_identity.slack_id) for admin in admins_queryset])
return current_role, admins_verbal
def open_warning_window(self, payload, warning_text, title=None):
if title is None:
title = ":warning: Warning"

View file

@ -1,11 +1,13 @@
import logging
from abc import ABC, abstractmethod
from apps.api.permissions import user_is_authorized
logger = logging.getLogger(__name__)
class AccessControl(ABC):
ALLOWED_ROLES = []
REQUIRED_PERMISSIONS = []
ACTION_VERBOSE = ""
def dispatch(self, slack_user_identity, slack_team_identity, payload, action=None):
@ -15,7 +17,7 @@ class AccessControl(ABC):
self.send_denied_message(payload)
def check_membership(self):
return self.user.role in self.ALLOWED_ROLES
return user_is_authorized(self.user, self.REQUIRED_PERMISSIONS)
@abstractmethod
def send_denied_message(self, payload):
@ -62,9 +64,7 @@ class IncidentActionsAccessControlMixin(AccessControl):
class CheckAlertIsUnarchivedMixin(object):
ALLOWED_ROLES = []
REQUIRED_PERMISSIONS = []
ACTION_VERBOSE = ""
def check_alert_is_unarchived(self, slack_team_identity, payload, alert_group, warning=True):

View file

@ -7,24 +7,24 @@ from rest_framework import status
from rest_framework.response import Response
from rest_framework.test import APIClient
from common.constants.role import Role
from apps.api.permissions import LegacyAccessControlRole
@pytest.mark.django_db
@pytest.mark.parametrize(
"role,expected_status",
[
(Role.ADMIN, status.HTTP_200_OK),
(Role.EDITOR, status.HTTP_403_FORBIDDEN),
(Role.VIEWER, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.ADMIN, status.HTTP_200_OK),
(LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN),
(LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN),
],
)
def test_reset_slack_integration_permissions(
make_organization_and_user_with_plugin_token, role, expected_status, load_slack_urls, make_user_auth_headers
make_organization_and_user_with_plugin_token, load_slack_urls, make_user_auth_headers, role, expected_status
):
settings.FEATURE_SLACK_INTEGRATION_ENABLED = True
_, user, token = make_organization_and_user_with_plugin_token(role)
_, user, token = make_organization_and_user_with_plugin_token(role=role)
client = APIClient()
url = reverse("reset-slack")

View file

@ -11,7 +11,7 @@ from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from apps.api.permissions import IsAdmin, MethodPermission
from apps.api.permissions import RBACPermission
from apps.auth_token.auth import PluginAuthentication
from apps.base.utils import live_settings
from apps.slack.scenarios.alertgroup_appearance import STEPS_ROUTING as ALERTGROUP_APPEARANCE_ROUTING
@ -533,10 +533,12 @@ class SlackEventApiEndpointView(APIView):
class ResetSlackView(APIView):
permission_classes = (IsAuthenticated, MethodPermission)
permission_classes = (IsAuthenticated, RBACPermission)
authentication_classes = [PluginAuthentication]
method_permissions = {IsAdmin: {"POST"}}
rbac_permissions = {
"post": [RBACPermission.Permissions.CHATOPS_UPDATE_SETTINGS],
}
def post(self, request):
organization = request.auth.organization

View file

@ -4,12 +4,12 @@ from typing import Callable, Optional, Tuple
from apps.alerts.constants import ActionSource
from apps.alerts.models import AlertGroup
from apps.api.permissions import RBACPermission, user_is_authorized
from apps.telegram.models import TelegramToUserConnector
from apps.telegram.renderers.keyboard import Action
from apps.telegram.updates.update_handlers import UpdateHandler
from apps.telegram.utils import CallbackQueryFactory
from apps.user_management.models import User
from common.constants.role import Role
logger = logging.getLogger(__name__)
@ -58,7 +58,8 @@ class ButtonPressHandler(UpdateHandler):
if not user:
return False
return user.organization == alert_group.channel.organization and user.role in [Role.ADMIN, Role.EDITOR]
has_permission = user_is_authorized(user, [RBACPermission.Permissions.CHATOPS_WRITE])
return user.organization == alert_group.channel.organization and has_permission
@staticmethod
def _get_action_context(data: str) -> ActionContext:

View file

@ -0,0 +1,23 @@
# Generated by Django 3.2.15 on 2022-10-25 11:18
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('user_management', '0004_auto_20221025_0316'),
]
operations = [
migrations.AddField(
model_name='organization',
name='is_rbac_permissions_enabled',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='user',
name='permissions',
field=models.JSONField(default=list),
),
]

View file

@ -192,6 +192,7 @@ class Organization(MaintainableObject):
pricing_version = models.PositiveIntegerField(choices=PRICING_CHOICES, default=FREE_PUBLIC_BETA_PRICING)
is_amixr_migration_started = models.BooleanField(default=False)
is_rbac_permissions_enabled = models.BooleanField(default=False)
class Meta:
unique_together = ("stack_id", "org_id")

View file

@ -1,4 +1,6 @@
import json
import logging
import typing
from urllib.parse import urljoin
from django.apps import apps
@ -9,13 +11,26 @@ from django.db.models.signals import post_save
from django.dispatch import receiver
from emoji import demojize
from apps.api.permissions import (
LegacyAccessControlCompatiblePermission,
LegacyAccessControlRole,
RBACPermission,
user_is_authorized,
)
from apps.schedules.tasks import drop_cached_ical_for_custom_events_for_organization
from common.constants.role import Role
from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length
logger = logging.getLogger(__name__)
class PermissionsRegexQuery(typing.TypedDict):
permissions__regex: str
class RoleInQuery(typing.TypedDict):
role__in: typing.List[int]
def generate_public_primary_key_for_user():
prefix = "U"
new_public_primary_key = generate_public_primary_key(prefix)
@ -60,8 +75,9 @@ class UserManager(models.Manager):
email=user["email"],
name=user["name"],
username=user["login"],
role=Role[user["role"].upper()],
role=LegacyAccessControlRole[user["role"].upper()],
avatar_url=user["avatarUrl"],
permissions=user["permissions"],
)
for user in grafana_users.values()
if user["userId"] not in existing_user_ids
@ -76,23 +92,31 @@ class UserManager(models.Manager):
users_to_update = []
for user in organization.users.filter(user_id__in=existing_user_ids):
grafana_user = grafana_users[user.user_id]
g_user_role = Role[grafana_user["role"].upper()]
g_user_role = LegacyAccessControlRole[grafana_user["role"].upper()]
if (
user.email != grafana_user["email"]
or user.name != grafana_user["name"]
or user.username != grafana_user["login"]
or user.role != g_user_role
or user.avatar_url != grafana_user["avatarUrl"]
# instead of looping through the array of permission objects, simply take the hash
# of the string representation of the data structures and compare.
# Need to first convert the lists of objects to strings because lists/dicts are not hashable
# (because lists and dicts are not hashable.. as they are mutable)
# https://stackoverflow.com/a/22003440
or hash(json.dumps(user.permissions)) != hash(json.dumps(grafana_user["permissions"]))
):
user.email = grafana_user["email"]
user.name = grafana_user["name"]
user.username = grafana_user["login"]
user.role = g_user_role
user.avatar_url = grafana_user["avatarUrl"]
user.permissions = grafana_user["permissions"]
users_to_update.append(user)
organization.users.bulk_update(
users_to_update, ["email", "name", "username", "role", "avatar_url"], batch_size=5000
users_to_update, ["email", "name", "username", "role", "avatar_url", "permissions"], batch_size=5000
)
@ -135,7 +159,7 @@ class User(models.Model):
email = models.EmailField()
name = models.CharField(max_length=300)
username = models.CharField(max_length=300)
role = models.PositiveSmallIntegerField(choices=Role.choices())
role = models.PositiveSmallIntegerField(choices=LegacyAccessControlRole.choices())
avatar_url = models.URLField()
# don't use "_timezone" directly, use the "timezone" property since it can be populated via slack user identity
@ -154,6 +178,7 @@ class User(models.Model):
# is_active = None is used to be able to have multiple deleted users with the same user_id
is_active = models.BooleanField(null=True, default=True)
permissions = models.JSONField(null=False, default=list)
def __str__(self):
return f"{self.pk}: {self.username}"
@ -187,13 +212,14 @@ class User(models.Model):
return hasattr(self, "telegram_connection")
def self_or_admin(self, user_to_check, organization) -> bool:
has_admin_permission = user_is_authorized(user_to_check, [RBACPermission.Permissions.USER_SETTINGS_ADMIN])
return user_to_check.pk == self.pk or (
user_to_check.role == Role.ADMIN and organization.pk == user_to_check.organization_id
has_admin_permission and organization.pk == user_to_check.organization_id
)
@property
def is_notification_allowed(self):
return self.role in (Role.ADMIN, Role.EDITOR)
return user_is_authorized(self, [RBACPermission.Permissions.NOTIFICATIONS_READ])
# using in-memory cache instead of redis to avoid pickling python objects
# @timed_lru_cache(timeout=100)
@ -249,6 +275,7 @@ class User(models.Model):
result = {
"username": self.username,
# LEGACY.. role should get removed eventually.. it's probably safe to remove it now?
"role": self.get_role_display(),
"notification_policies": notification_policies_verbal,
}
@ -262,6 +289,24 @@ class User(models.Model):
def insight_logs_metadata(self):
return {}
@staticmethod
def build_permissions_query(
permission: LegacyAccessControlCompatiblePermission, organization
) -> typing.Union[PermissionsRegexQuery, RoleInQuery]:
"""
This method returns a django query filter that is compatible with RBAC
as well as legacy "basic" role based authorization. If a permission is provided we simply do
a regex search where the permission column contains the permission value (need to use regex because
the JSON contains method is not supported by sqlite)
If RBAC is not supported for the org, we make the assumption that we are looking for any users with AT LEAST
the fallback role. Ex: if the fallback role were editor than we would get editors and admins.
"""
if organization.is_rbac_permissions_enabled:
# https://stackoverflow.com/a/50251879
return PermissionsRegexQuery(permissions__regex=r".*{0}.*".format(permission.value))
return RoleInQuery(role__lte=permission.fallback_role.value)
# TODO: check whether this signal can be moved to save method of the model
@receiver(post_save, sender=User)

View file

@ -14,9 +14,23 @@ logger.setLevel(logging.DEBUG)
def sync_organization(organization):
client = GrafanaAPIClient(api_url=organization.grafana_url, api_token=organization.api_token)
api_users, call_status = client.get_users()
rbac_is_enabled = client.is_rbac_enabled_for_organization()
organization.is_rbac_permissions_enabled = rbac_is_enabled
sync_instance_info(organization)
if organization.gcom_token:
gcom_client = GcomAPIClient(organization.gcom_token)
instance_info = gcom_client.get_instance_info(organization.stack_id)
if not instance_info or str(instance_info["orgId"]) != organization.org_id:
return
organization.stack_slug = instance_info["slug"]
organization.org_slug = instance_info["orgSlug"]
organization.org_title = instance_info["orgName"]
organization.region_slug = instance_info["regionSlug"]
organization.grafana_url = instance_info["url"]
organization.gcom_token_org_last_time_synced = timezone.now()
api_users = client.get_users(rbac_is_enabled)
if api_users:
organization.api_token_status = Organization.API_TOKEN_STATUS_OK
@ -34,25 +48,11 @@ def sync_organization(organization):
"last_time_synced",
"api_token_status",
"gcom_token_org_last_time_synced",
"is_rbac_permissions_enabled",
]
)
def sync_instance_info(organization):
if organization.gcom_token:
gcom_client = GcomAPIClient(organization.gcom_token)
instance_info, _ = gcom_client.get_instance_info(organization.stack_id)
if not instance_info or str(instance_info["orgId"]) != organization.org_id:
return
organization.stack_slug = instance_info["slug"]
organization.org_slug = instance_info["orgSlug"]
organization.org_title = instance_info["orgName"]
organization.region_slug = instance_info["regionSlug"]
organization.grafana_url = instance_info["url"]
organization.gcom_token_org_last_time_synced = timezone.now()
def sync_users_and_teams(client, api_users, organization):
# check if api_users are shaped correctly. e.g. for paused instance, the response is not a list.
if not api_users or not isinstance(api_users, (tuple, list)):

View file

@ -1,7 +1,7 @@
import pytest
from apps.api.permissions import LegacyAccessControlRole
from apps.twilioapp.constants import TwilioCallStatuses, TwilioMessageStatuses
from common.constants.role import Role
@pytest.mark.django_db
@ -13,8 +13,8 @@ def test_phone_calls_left(
make_alert_group,
):
organization = make_organization()
admin = make_user_for_organization(organization, role=Role.ADMIN)
user = make_user_for_organization(organization, role=Role.EDITOR)
admin = make_user_for_organization(organization)
user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_phone_call(receiver=admin, status=TwilioCallStatuses.COMPLETED, represents_alert_group=alert_group)
@ -28,8 +28,8 @@ def test_sms_left(
make_organization, make_user_for_organization, make_sms, make_alert_receive_channel, make_alert_group
):
organization = make_organization()
admin = make_user_for_organization(organization, role=Role.ADMIN)
user = make_user_for_organization(organization, role=Role.EDITOR)
admin = make_user_for_organization(organization)
user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_sms(receiver=admin, status=TwilioMessageStatuses.SENT, represents_alert_group=alert_group)
@ -48,8 +48,8 @@ def test_phone_calls_and_sms_counts_together(
make_alert_group,
):
organization = make_organization()
admin = make_user_for_organization(organization, role=Role.ADMIN)
user = make_user_for_organization(organization, role=Role.EDITOR)
admin = make_user_for_organization(organization)
user = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
alert_receive_channel = make_alert_receive_channel(organization)
alert_group = make_alert_group(alert_receive_channel)
make_phone_call(receiver=admin, status=TwilioCallStatuses.COMPLETED, represents_alert_group=alert_group)

View file

@ -6,6 +6,7 @@ from django.core.exceptions import ObjectDoesNotExist
from apps.grafana_plugin.helpers.client import GcomAPIClient, GrafanaAPIClient
from apps.user_management.models import Team, User
from apps.user_management.sync import cleanup_organization, sync_organization
from conftest import IS_RBAC_ENABLED
@pytest.mark.django_db
@ -21,6 +22,7 @@ def test_sync_users_for_organization(make_organization, make_user_for_organizati
"login": "test",
"role": "admin",
"avatarUrl": "/test/1234",
"permissions": [],
}
for user_id in (2, 3)
)
@ -97,11 +99,7 @@ def test_sync_users_for_team(make_organization, make_user_for_organization, make
@pytest.mark.django_db
def test_sync_organization(
make_organization,
make_team,
make_user_for_organization,
):
def test_sync_organization(make_organization, make_team, make_user_for_organization):
organization = make_organization()
api_users_response = (
@ -112,6 +110,7 @@ def test_sync_organization(
"login": "test",
"role": "admin",
"avatarUrl": "test.test/test",
"permissions": [],
},
)
@ -135,10 +134,11 @@ def test_sync_organization(
},
)
with patch.object(GrafanaAPIClient, "get_users", return_value=(api_users_response, {"status_code": 200})):
with patch.object(GrafanaAPIClient, "get_teams", return_value=(api_teams_response, None)):
with patch.object(GrafanaAPIClient, "get_team_members", return_value=(api_members_response, None)):
sync_organization(organization)
with patch.object(GrafanaAPIClient, "is_rbac_enabled_for_organization", return_value=IS_RBAC_ENABLED):
with patch.object(GrafanaAPIClient, "get_users", return_value=api_users_response):
with patch.object(GrafanaAPIClient, "get_teams", return_value=(api_teams_response, None)):
with patch.object(GrafanaAPIClient, "get_team_members", return_value=(api_members_response, None)):
sync_organization(organization)
# check that users are populated
assert organization.users.count() == 1
@ -154,6 +154,9 @@ def test_sync_organization(
assert team.users.count() == 1
assert team.users.get() == user
# check that the rbac flag is properly set on the org
assert organization.is_rbac_permissions_enabled == IS_RBAC_ENABLED
@pytest.mark.django_db
def test_duplicate_user_ids(make_organization, make_user_for_organization):
@ -178,6 +181,7 @@ def test_duplicate_user_ids(make_organization, make_user_for_organization):
"login": "test",
"role": "admin",
"avatarUrl": "test.test/test",
"permissions": [],
}
]
@ -192,7 +196,7 @@ def test_duplicate_user_ids(make_organization, make_user_for_organization):
def test_cleanup_organization_deleted(make_organization):
organization = make_organization(gcom_token="TEST_GCOM_TOKEN")
with patch.object(GcomAPIClient, "get_instance_info", return_value=({"status": "deleted"}, None)):
with patch.object(GcomAPIClient, "get_instance_info", return_value={"status": "deleted"}):
cleanup_organization(organization.id)
with pytest.raises(ObjectDoesNotExist):

View file

@ -1,20 +1,15 @@
# from unittest.mock import Mock, patch
import pytest
from apps.api.permissions import LegacyAccessControlRole
from apps.user_management.models import User
from common.constants.role import Role
@pytest.mark.django_db
def test_self_or_admin(
make_organization,
make_user_for_organization,
):
def test_self_or_admin(make_organization, make_user_for_organization):
organization = make_organization()
admin = make_user_for_organization(organization)
second_admin = make_user_for_organization(organization)
editor = make_user_for_organization(organization, role=Role.EDITOR)
editor = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
another_organization = make_organization()
admin_from_another_organization = make_user_for_organization(another_organization)
@ -26,10 +21,7 @@ def test_self_or_admin(
@pytest.mark.django_db
def test_lower_email_filter(
make_organization,
make_user_for_organization,
):
def test_lower_email_filter(make_organization, make_user_for_organization):
organization = make_organization()
user = make_user_for_organization(organization, email="TestingUser@test.com")
make_user_for_organization(organization, email="testing_user@test.com")

View file

@ -1,11 +0,0 @@
from enum import IntEnum
class Role(IntEnum):
ADMIN = 0
EDITOR = 1
VIEWER = 2
@classmethod
def choices(cls):
return tuple((option.value, option.name) for option in cls)

View file

@ -1,4 +1,5 @@
import json
import os
import sys
import typing
import uuid
@ -35,6 +36,13 @@ from apps.alerts.tests.factories import (
ResolutionNoteFactory,
ResolutionNoteSlackMessageFactory,
)
from apps.api.permissions import (
ACTION_PREFIX,
GrafanaAPIPermission,
LegacyAccessControlCompatiblePermission,
LegacyAccessControlRole,
RBACPermission,
)
from apps.auth_token.models import ApiAuthToken, PluginAuthToken
from apps.base.models.user_notification_policy_log_record import (
UserNotificationPolicyLogRecord,
@ -72,7 +80,6 @@ from apps.telegram.tests.factories import (
from apps.twilioapp.tests.factories import PhoneCallFactory, SMSFactory
from apps.user_management.models.user import User, listen_for_user_model_save
from apps.user_management.tests.factories import OrganizationFactory, RegionFactory, TeamFactory, UserFactory
from common.constants.role import Role
register(OrganizationFactory)
register(UserFactory)
@ -112,6 +119,8 @@ register(EmailMessageFactory)
register(IntegrationHeartBeatFactory)
register(LiveSettingFactory)
IS_RBAC_ENABLED = os.getenv("ONCALL_TESTING_RBAC_ENABLED", "True") == "True"
@pytest.fixture(autouse=True)
def mock_slack_api_call(monkeypatch):
@ -142,18 +151,16 @@ def mock_telegram_bot_username(monkeypatch):
@pytest.fixture
def make_organization():
def _make_organization(**kwargs):
organization = OrganizationFactory(**kwargs)
return organization
return OrganizationFactory(**kwargs, is_rbac_permissions_enabled=IS_RBAC_ENABLED)
return _make_organization
@pytest.fixture
def make_user_for_organization():
def _make_user_for_organization(organization, role=Role.ADMIN, **kwargs):
def make_user_for_organization(make_user):
def _make_user_for_organization(organization, role: typing.Optional[LegacyAccessControlRole] = None, **kwargs):
post_save.disconnect(listen_for_user_model_save, sender=User)
user = UserFactory(organization=organization, role=role, **kwargs)
user = make_user(organization=organization, role=role, **kwargs)
post_save.disconnect(listen_for_user_model_save, sender=User)
return user
@ -200,19 +207,84 @@ def make_user_auth_headers():
return _make_user_auth_headers
RoleMapping = typing.Dict[LegacyAccessControlRole, typing.List[LegacyAccessControlCompatiblePermission]]
def get_user_permission_role_mapping_from_frontend_plugin_json() -> RoleMapping:
"""
This is used to take the RBAC permission -> basic role grants on the frontend
and test that the RBAC grants work the same way against the backend in terms of authorization
"""
class PluginJSONRoleDefinition(typing.TypedDict):
permissions: typing.List[GrafanaAPIPermission]
class PluginJSONRole(typing.TypedDict):
role: PluginJSONRoleDefinition
grants: typing.List[str]
class PluginJSON(typing.TypedDict):
roles: typing.List[PluginJSONRole]
with open("../grafana-plugin/src/plugin.json") as fp:
plugin_json: PluginJSON = json.load(fp)
role_mapping: RoleMapping = {
LegacyAccessControlRole.VIEWER: [],
LegacyAccessControlRole.EDITOR: [],
LegacyAccessControlRole.ADMIN: [],
}
all_permission_classes: typing.Dict[str, LegacyAccessControlCompatiblePermission] = {
getattr(RBACPermission.Permissions, attr).value: getattr(RBACPermission.Permissions, attr)
for attr in dir(RBACPermission.Permissions)
if not attr.startswith("_")
}
# we just care about getting the basic role grants, everything else can be ignored
for role in plugin_json["roles"]:
if grants := role["grants"]:
for permission in role["role"]["permissions"]:
# only concerned with grafana-oncall-app specific grants
# ignore things like plugins.app:access actions
action = permission["action"]
permission_class = None
if action.startswith(ACTION_PREFIX):
permission_class = all_permission_classes[action]
if permission_class:
for grant in grants:
try:
role = LegacyAccessControlRole[grant.upper()]
if role not in role_mapping[role]:
role_mapping[role].append(permission_class)
except KeyError:
# may come across grants like "Grafana Admin"
# which we can ignore
continue
return role_mapping
ROLE_PERMISSION_MAPPING = get_user_permission_role_mapping_from_frontend_plugin_json()
@pytest.fixture
def make_user():
def _make_user(role=Role.ADMIN, **kwargs):
user = UserFactory(role=role, **kwargs)
return user
def _make_user(role: typing.Optional[LegacyAccessControlRole] = None, **kwargs):
role = LegacyAccessControlRole.ADMIN if role is None else role
permissions = ROLE_PERMISSION_MAPPING[role] if IS_RBAC_ENABLED else []
return UserFactory(
role=role, permissions=[GrafanaAPIPermission(action=perm.value) for perm in permissions], **kwargs
)
return _make_user
@pytest.fixture
def make_organization_and_user(make_organization, make_user_for_organization):
def _make_organization_and_user(role=Role.ADMIN):
def _make_organization_and_user(role: typing.Optional[LegacyAccessControlRole] = None):
organization = make_organization()
user = make_user_for_organization(organization=organization, role=role)
return organization, user
@ -224,33 +296,31 @@ def make_organization_and_user(make_organization, make_user_for_organization):
def make_organization_and_user_with_slack_identities(
make_organization_with_slack_team_identity, make_user_with_slack_user_identity
):
def _make_organization_and_user_with_slack_identities(role=Role.ADMIN):
def _make_organization_and_user_with_slack_identities(role: typing.Optional[LegacyAccessControlRole] = None):
organization, slack_team_identity = make_organization_with_slack_team_identity()
user, slack_user_identity = make_user_with_slack_user_identity(slack_team_identity, organization, role=role)
return organization, user, slack_team_identity, slack_user_identity
return _make_organization_and_user_with_slack_identities
@pytest.fixture
def make_user_with_slack_user_identity():
def _make_slack_user_identity_with_user(slack_team_identity, organization, role=Role.ADMIN, **kwargs):
slack_user_identity = SlackUserIdentityFactory(
slack_team_identity=slack_team_identity,
**kwargs,
)
user = UserFactory(slack_user_identity=slack_user_identity, organization=organization, role=role)
def make_user_with_slack_user_identity(make_user):
def _make_slack_user_identity_with_user(
slack_team_identity, organization, role: typing.Optional[LegacyAccessControlRole] = None, **kwargs
):
slack_user_identity = SlackUserIdentityFactory(slack_team_identity=slack_team_identity, **kwargs)
user = make_user(slack_user_identity=slack_user_identity, organization=organization, role=role)
return user, slack_user_identity
return _make_slack_user_identity_with_user
@pytest.fixture
def make_organization_with_slack_team_identity(make_slack_team_identity):
def make_organization_with_slack_team_identity(make_slack_team_identity, make_organization):
def _make_slack_team_identity_with_organization(**kwargs):
slack_team_identity = make_slack_team_identity(**kwargs)
organization = OrganizationFactory(slack_team_identity=slack_team_identity)
organization = make_organization(slack_team_identity=slack_team_identity)
return organization, slack_team_identity
return _make_slack_team_identity_with_organization
@ -565,10 +635,9 @@ def mock_start_disable_maintenance_task(monkeypatch):
@pytest.fixture()
def make_organization_and_user_with_plugin_token(make_organization_and_user, make_token_for_organization):
def _make_organization_and_user_with_plugin_token(role=Role.ADMIN):
organization, user = make_organization_and_user(role=role)
def _make_organization_and_user_with_plugin_token(role: typing.Optional[LegacyAccessControlRole] = None):
organization, user = make_organization_and_user(role)
_, token = make_token_for_organization(organization)
return organization, user, token
return _make_organization_and_user_with_plugin_token

View file

@ -9,6 +9,6 @@ banned-modules =
[pytest]
# https://pytest-django.readthedocs.io/en/latest/configuring_django.html#order-of-choosing-settings
# https://pytest-django.readthedocs.io/en/latest/database.html
addopts = --reuse-db --nomigrations --color=yes --showlocals
addopts = --color=yes --showlocals
# https://pytest-django.readthedocs.io/en/latest/faq.html#my-tests-are-not-being-found-why
python_files = tests.py test_*.py *_tests.py

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