# What this PR does **NOTE**: should be merged/released after https://github.com/grafana/irm/pull/183 has been rolled out to most stacks (as that frontend update is what will grant that new RBAC "action" to users whom already have the "OnCall Admin" RBAC role assigned) tldr; from the comment in the `RBACPermission.Permission.ADMIN` comment in `engine/apps/api/permissions.py`: > NOTE: this is a bit of a hack for now. See https://github.com/grafana/support-escalations/issues/12625 > Basically when it comes to filtering teams that are configured to share their resources with > "Team members and admins", we have no way of knowing, when a user is ACTUALLY an Admin when RBAC is involed. > > Example: Take a user with the basic role of None/Editor/Viewer but with the "OnCall Admin" role assigned. > Without this RBAC permission, we have no way of knowing that the user is ACTUALLY an "Admin". ## Which issue(s) this PR closes Closes https://github.com/grafana/support-escalations/issues/12625 ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes.
290 lines
11 KiB
Python
290 lines
11 KiB
Python
import datetime
|
|
|
|
import pytest
|
|
from django.utils import timezone
|
|
|
|
from apps.api.permissions import GrafanaAPIPermission, LegacyAccessControlRole, RBACPermission
|
|
from apps.google import constants as google_constants
|
|
from apps.google.models import GoogleOAuth2User
|
|
from apps.user_management.models import User
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_self_or_has_user_settings_admin_permission(make_organization, make_user_for_organization):
|
|
# RBAC not enabled for org
|
|
organization = make_organization(is_rbac_permissions_enabled=False)
|
|
admin = make_user_for_organization(organization)
|
|
second_admin = make_user_for_organization(organization)
|
|
editor = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
|
|
|
another_organization = make_organization(is_rbac_permissions_enabled=False)
|
|
admin_from_another_organization = make_user_for_organization(another_organization)
|
|
|
|
assert organization.is_rbac_permissions_enabled is False
|
|
assert another_organization.is_rbac_permissions_enabled is False
|
|
|
|
assert admin.self_or_has_user_settings_admin_permission(admin, organization) is True
|
|
assert admin.self_or_has_user_settings_admin_permission(editor, organization) is False
|
|
assert admin.self_or_has_user_settings_admin_permission(second_admin, organization) is True
|
|
|
|
assert admin.self_or_has_user_settings_admin_permission(admin_from_another_organization, organization) is False
|
|
|
|
assert editor.self_or_has_user_settings_admin_permission(editor, organization) is True
|
|
assert editor.self_or_has_user_settings_admin_permission(admin, organization) is True
|
|
|
|
# RBAC enabled org
|
|
organization_with_rbac = make_organization(is_rbac_permissions_enabled=True)
|
|
user_with_perms = make_user_for_organization(
|
|
organization_with_rbac,
|
|
role=LegacyAccessControlRole.NONE,
|
|
permissions=[GrafanaAPIPermission(action=RBACPermission.Permissions.USER_SETTINGS_ADMIN.value)],
|
|
)
|
|
user_without_perms = make_user_for_organization(
|
|
organization_with_rbac,
|
|
role=LegacyAccessControlRole.NONE,
|
|
permissions=[],
|
|
)
|
|
|
|
assert organization_with_rbac.is_rbac_permissions_enabled is True
|
|
|
|
# true because self
|
|
assert user_with_perms.self_or_has_user_settings_admin_permission(user_with_perms, organization_with_rbac) is True
|
|
assert (
|
|
user_without_perms.self_or_has_user_settings_admin_permission(user_without_perms, organization_with_rbac)
|
|
is True
|
|
)
|
|
|
|
# true because user_with_perms has proper admin RBAC permission
|
|
assert (
|
|
user_without_perms.self_or_has_user_settings_admin_permission(user_with_perms, organization_with_rbac) is True
|
|
)
|
|
|
|
# false because user_without_perms does not have proper admin RBAC permission
|
|
assert (
|
|
user_with_perms.self_or_has_user_settings_admin_permission(user_without_perms, organization_with_rbac) is False
|
|
)
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_is_admin(make_organization, make_user_for_organization):
|
|
# RBAC not enabled for org
|
|
organization = make_organization(is_rbac_permissions_enabled=False)
|
|
admin = make_user_for_organization(organization, role=LegacyAccessControlRole.ADMIN)
|
|
editor = make_user_for_organization(organization, role=LegacyAccessControlRole.EDITOR)
|
|
|
|
assert organization.is_rbac_permissions_enabled is False
|
|
|
|
assert admin.is_admin is True
|
|
assert editor.is_admin is False
|
|
|
|
# RBAC enabled org
|
|
organization_with_rbac = make_organization(is_rbac_permissions_enabled=True)
|
|
user_with_perms = make_user_for_organization(
|
|
organization_with_rbac,
|
|
role=LegacyAccessControlRole.NONE,
|
|
permissions=[GrafanaAPIPermission(action=RBACPermission.Permissions.ADMIN.value)],
|
|
)
|
|
user_without_perms = make_user_for_organization(
|
|
organization_with_rbac,
|
|
role=LegacyAccessControlRole.NONE,
|
|
permissions=[],
|
|
)
|
|
|
|
assert organization_with_rbac.is_rbac_permissions_enabled is True
|
|
|
|
assert user_with_perms.is_admin is True
|
|
assert user_without_perms.is_admin is False
|
|
|
|
|
|
@pytest.mark.django_db
|
|
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")
|
|
|
|
assert User.objects.get(email__lower="testinguser@test.com") == user
|
|
assert User.objects.filter(email__lower__in=["testinguser@test.com"]).get() == user
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_is_in_working_hours(make_organization, make_user_for_organization):
|
|
organization = make_organization()
|
|
user = make_user_for_organization(organization, _timezone="Europe/London")
|
|
|
|
_7_59_utc = timezone.datetime(2023, 8, 1, 7, 59, 59, tzinfo=datetime.timezone.utc)
|
|
_8_utc = timezone.datetime(2023, 8, 1, 8, 0, 0, tzinfo=datetime.timezone.utc)
|
|
_17_utc = timezone.datetime(2023, 8, 1, 16, 0, 0, tzinfo=datetime.timezone.utc)
|
|
_17_01_utc = timezone.datetime(2023, 8, 1, 16, 0, 1, tzinfo=datetime.timezone.utc)
|
|
|
|
assert user.is_in_working_hours(_7_59_utc) is False
|
|
assert user.is_in_working_hours(_8_utc) is True
|
|
assert user.is_in_working_hours(_17_utc) is True
|
|
assert user.is_in_working_hours(_17_01_utc) is False
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_is_in_working_hours_next_day(make_organization, make_user_for_organization):
|
|
organization = make_organization()
|
|
user = make_user_for_organization(
|
|
organization,
|
|
working_hours={
|
|
"tuesday": [{"start": "17:00:00", "end": "18:00:00"}],
|
|
"wednesday": [{"start": "01:00:00", "end": "02:00:00"}],
|
|
},
|
|
)
|
|
|
|
_8_59_utc = timezone.datetime(2023, 8, 1, 8, 59, 59, tzinfo=datetime.timezone.utc) # 4:59pm on Tuesday in Singapore
|
|
_9_utc = timezone.datetime(2023, 8, 1, 9, 0, 0, tzinfo=datetime.timezone.utc) # 5pm on Tuesday in Singapore
|
|
_10_utc = timezone.datetime(2023, 8, 1, 10, 0, 0, tzinfo=datetime.timezone.utc) # 6pm on Tuesday in Singapore
|
|
_10_01_utc = timezone.datetime(2023, 8, 1, 10, 0, 1, tzinfo=datetime.timezone.utc) # 6:01pm on Tuesday in Singapore
|
|
|
|
_16_59_utc = timezone.datetime(
|
|
2023, 8, 1, 16, 59, 0, tzinfo=datetime.timezone.utc
|
|
) # 00:59am on Wednesday in Singapore
|
|
_17_utc = timezone.datetime(2023, 8, 1, 17, 0, 0, tzinfo=datetime.timezone.utc) # 1am on Wednesday in Singapore
|
|
_18_utc = timezone.datetime(2023, 8, 1, 18, 0, 0, tzinfo=datetime.timezone.utc) # 2am on Wednesday in Singapore
|
|
_18_01_utc = timezone.datetime(
|
|
2023, 8, 1, 18, 0, 1, tzinfo=datetime.timezone.utc
|
|
) # 2:01am on Wednesday in Singapore
|
|
|
|
tz = "Asia/Singapore"
|
|
assert user.is_in_working_hours(_8_59_utc, tz=tz) is False
|
|
assert user.is_in_working_hours(_9_utc, tz=tz) is True
|
|
assert user.is_in_working_hours(_10_utc, tz=tz) is True
|
|
assert user.is_in_working_hours(_10_01_utc, tz=tz) is False
|
|
assert user.is_in_working_hours(_16_59_utc, tz=tz) is False
|
|
assert user.is_in_working_hours(_17_utc, tz=tz) is True
|
|
assert user.is_in_working_hours(_18_utc, tz=tz) is True
|
|
assert user.is_in_working_hours(_18_01_utc, tz=tz) is False
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_is_in_working_hours_no_timezone(make_organization, make_user_for_organization):
|
|
organization = make_organization()
|
|
user = make_user_for_organization(organization, _timezone=None)
|
|
|
|
assert user.is_in_working_hours(timezone.now()) is False
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_is_in_working_hours_weekend(make_organization, make_user_for_organization):
|
|
organization = make_organization()
|
|
user = make_user_for_organization(organization, working_hours={"saturday": []}, _timezone=None)
|
|
|
|
on_saturday = timezone.datetime(2023, 8, 5, 12, 0, 0, tzinfo=datetime.timezone.utc)
|
|
assert user.is_in_working_hours(on_saturday, "UTC") is False
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_is_telegram_connected(make_organization_and_user, make_telegram_user_connector):
|
|
_, user = make_organization_and_user()
|
|
assert user.is_telegram_connected is False
|
|
make_telegram_user_connector(user)
|
|
assert user.is_telegram_connected is True
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_has_google_oauth2_connected(make_organization_and_user, make_google_oauth2_user_for_user):
|
|
_, user = make_organization_and_user()
|
|
|
|
assert user.has_google_oauth2_connected is False
|
|
make_google_oauth2_user_for_user(user)
|
|
assert user.has_google_oauth2_connected is True
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_google_oauth2_token_is_missing_scopes(make_organization_and_user, make_google_oauth2_user_for_user):
|
|
initial_granted_scope = "foo bar baz"
|
|
initial_oauth_response = {
|
|
"access_token": "access",
|
|
"refresh_token": "refresh",
|
|
"sub": "google_user_id",
|
|
"scope": initial_granted_scope,
|
|
}
|
|
|
|
_, user = make_organization_and_user()
|
|
|
|
# false because the user hasn't yet connected their google account
|
|
assert user.google_oauth2_token_is_missing_scopes is False
|
|
|
|
user.save_google_oauth2_settings(initial_oauth_response)
|
|
user.refresh_from_db()
|
|
|
|
# true because we're missing a granted scope
|
|
assert user.google_oauth2_token_is_missing_scopes is True
|
|
|
|
user.save_google_oauth2_settings(
|
|
{
|
|
**initial_oauth_response,
|
|
"scope": f"{initial_granted_scope} {' '.join(google_constants.REQUIRED_OAUTH_SCOPES)}",
|
|
}
|
|
)
|
|
user.refresh_from_db()
|
|
|
|
# False because we now have all the required scopes
|
|
assert user.google_oauth2_token_is_missing_scopes is False
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_save_google_oauth2_settings(make_organization_and_user):
|
|
oauth_response = {
|
|
"access_token": "access",
|
|
"refresh_token": "refresh",
|
|
"sub": "google_user_id",
|
|
"scope": "scope",
|
|
}
|
|
|
|
_, user = make_organization_and_user()
|
|
|
|
assert GoogleOAuth2User.objects.filter(user=user).exists() is False
|
|
assert user.google_calendar_settings is None
|
|
|
|
user.save_google_oauth2_settings(oauth_response)
|
|
user.refresh_from_db()
|
|
|
|
google_oauth_user = user.google_oauth2_user
|
|
assert google_oauth_user.google_user_id == "google_user_id"
|
|
assert google_oauth_user.access_token == "access"
|
|
assert google_oauth_user.refresh_token == "refresh"
|
|
assert google_oauth_user.oauth_scope == "scope"
|
|
assert user.google_calendar_settings["oncall_schedules_to_consider_for_shift_swaps"] == []
|
|
|
|
oauth_response2 = {
|
|
"access_token": "access2",
|
|
"refresh_token": "refresh2",
|
|
"sub": "google_user_id2",
|
|
"scope": "scope2",
|
|
}
|
|
|
|
user.save_google_oauth2_settings(oauth_response2)
|
|
user.refresh_from_db()
|
|
|
|
google_oauth_user = user.google_oauth2_user
|
|
assert google_oauth_user.google_user_id == "google_user_id2"
|
|
assert google_oauth_user.access_token == "access2"
|
|
assert google_oauth_user.refresh_token == "refresh2"
|
|
assert google_oauth_user.oauth_scope == "scope2"
|
|
|
|
|
|
@pytest.mark.django_db
|
|
def test_reset_google_oauth2_settings(make_organization_and_user):
|
|
_, user = make_organization_and_user()
|
|
|
|
user.save_google_oauth2_settings(
|
|
{
|
|
"access_token": "access",
|
|
"refresh_token": "refresh",
|
|
"sub": "google_user_id",
|
|
"scope": "scope",
|
|
}
|
|
)
|
|
user.refresh_from_db()
|
|
|
|
assert user.google_oauth2_user is not None
|
|
assert user.google_calendar_settings is not None
|
|
|
|
user.reset_google_oauth2_settings()
|
|
user.refresh_from_db()
|
|
|
|
assert GoogleOAuth2User.objects.filter(user=user).exists() is False
|
|
assert user.google_calendar_settings is None
|