Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add permission for manage segments overrides #2919

Merged
merged 23 commits into from
Nov 14, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
47952b7
Add django extensions to local developement
zachaysan Nov 2, 2023
dedbb09
Add MANAGE_SEGMENT_OVERRIDES permission for feature segments
zachaysan Nov 2, 2023
bc8a549
Update to include manage segment overrides
zachaysan Nov 2, 2023
68ddeb4
Move constant to old migrations to declutter application code
zachaysan Nov 2, 2023
2d92db7
Create migraiton to add manage segments overrides and apply to existi…
zachaysan Nov 2, 2023
557f335
Add test for forbidden route
zachaysan Nov 3, 2023
6f632ef
Fix permission enforcement
zachaysan Nov 3, 2023
3a824be
Add staff user and client
zachaysan Nov 3, 2023
50035c4
Add staff tests
zachaysan Nov 3, 2023
8539bee
Format
zachaysan Nov 3, 2023
211fa75
Add staff tests
zachaysan Nov 3, 2023
7501b13
Remove unnecessary arg
zachaysan Nov 6, 2023
5c3d589
Fix conflicts and merge branch 'main' into feat/add_permission_for_ma…
zachaysan Nov 7, 2023
49ca2b0
Use fixture to create staff environment permissions
zachaysan Nov 7, 2023
4a15a52
Merge branch 'main' into feat/add_permission_for_manage_segments_over…
zachaysan Nov 7, 2023
9374d51
Create with_project_permissions fixture
zachaysan Nov 8, 2023
783fa2c
Use new fixture
zachaysan Nov 8, 2023
141333a
Use staff_user fixture instead
zachaysan Nov 8, 2023
dd5fa32
Merge branch 'main' into feat/add_permission_for_manage_segments_over…
zachaysan Nov 8, 2023
c5fcd9b
Fix word error
zachaysan Nov 8, 2023
c5fb073
Add typing to tests
zachaysan Nov 8, 2023
f49a46d
Fix conflicts and merge branch 'main' into feat/add_permission_for_ma…
zachaysan Nov 13, 2023
f725248
Merge branch 'main' into feat/add_permission_for_manage_segments_over…
zachaysan Nov 14, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/app/settings/local.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

ALLOWED_HOSTS.extend([".ngrok.io", "127.0.0.1", "localhost"])

INSTALLED_APPS.extend(["debug_toolbar"])
INSTALLED_APPS.extend(["debug_toolbar", "django_extensions"])

MIDDLEWARE.extend(["debug_toolbar.middleware.DebugToolbarMiddleware"])

Expand Down
17 changes: 14 additions & 3 deletions api/environments/migrations/0010_auto_20200219_2343.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
# Generated by Django 2.2.10 on 2020-02-19 23:43

from django.db import migrations

from environments.permissions.constants import ENVIRONMENT_PERMISSIONS

from environments.permissions.constants import VIEW_ENVIRONMENT, UPDATE_FEATURE_STATE, MANAGE_IDENTITIES, CREATE_CHANGE_REQUEST, APPROVE_CHANGE_REQUEST
ENVIRONMENT_PERMISSIONS = [
(VIEW_ENVIRONMENT, "View permission for the given environment."),
(UPDATE_FEATURE_STATE, "Update the state or value for a given feature state."),
(MANAGE_IDENTITIES, "Manage identities in the given environment."),
(
CREATE_CHANGE_REQUEST,
"Permission to create change requests in the given environment.",
),
(
APPROVE_CHANGE_REQUEST,
"Permission to approve change requests in the given environment.",
),
]

def create_default_permissions(apps, schema_editor):
EnvironmentPermission = apps.get_model('environments', 'EnvironmentPermission')
Expand Down
15 changes: 1 addition & 14 deletions api/environments/permissions/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,6 @@
VIEW_IDENTITIES = "VIEW_IDENTITIES"
CREATE_CHANGE_REQUEST = "CREATE_CHANGE_REQUEST"
APPROVE_CHANGE_REQUEST = "APPROVE_CHANGE_REQUEST"

ENVIRONMENT_PERMISSIONS = [
(VIEW_ENVIRONMENT, "View permission for the given environment."),
(UPDATE_FEATURE_STATE, "Update the state or value for a given feature state."),
(MANAGE_IDENTITIES, "Manage identities in the given environment."),
(
CREATE_CHANGE_REQUEST,
"Permission to create change requests in the given environment.",
),
(
APPROVE_CHANGE_REQUEST,
"Permission to approve change requests in the given environment.",
),
]
MANAGE_SEGMENT_OVERRIDES = "MANAGE_SEGMENT_OVERRIDES"

TAG_SUPPORTED_PERMISSIONS = [UPDATE_FEATURE_STATE]
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Generated by Django 3.2.20 on 2023-11-01 19:54

from django.db import migrations
from environments.permissions.constants import MANAGE_SEGMENT_OVERRIDES, UPDATE_FEATURE_STATE
from core.migration_helpers import create_new_environment_permissions

from permissions.models import ENVIRONMENT_PERMISSION_TYPE

def add_manage_segment_overrides_permission(apps, schema_editor):
PermissionModel = apps.get_model("permissions", "PermissionModel")
UserEnvironmentPermission = apps.get_model(
"environment_permissions",
"UserEnvironmentPermission",
)
UserPermissionGroupEnvironmentPermission = apps.get_model(
"environment_permissions",
"UserPermissionGroupEnvironmentPermission",
)
manage_segment_overrides_permission, _ = PermissionModel.objects.get_or_create(
key=MANAGE_SEGMENT_OVERRIDES,
description="Permission to manage segment overrides in the given environment",
type=ENVIRONMENT_PERMISSION_TYPE,
)

# Add MANAGE_SEGMENT_OVERRIDES to existing UPDATE_FEATURE_STATE holders
create_new_environment_permissions(
UPDATE_FEATURE_STATE,
UserEnvironmentPermission,
"userenvironmentpermission",
[manage_segment_overrides_permission],
)
create_new_environment_permissions(
UPDATE_FEATURE_STATE,
UserPermissionGroupEnvironmentPermission,
"userpermissiongroupenvironmentpermission",
[manage_segment_overrides_permission],
)


def remove_manage_segment_overrides_permission(apps, schema_editor):
PermissionModel = apps.get_model("permissions", "PermissionModel")
PermissionModel.objects.filter(key=MANAGE_SEGMENT_OVERRIDES).delete()

class Migration(migrations.Migration):

dependencies = [
('environment_permissions', '0007_add_unique_permission_constraint'),
]

operations = [
migrations.RunPython(
add_manage_segment_overrides_permission,
reverse_code=remove_manage_segment_overrides_permission,
)
]
4 changes: 1 addition & 3 deletions api/environments/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -780,9 +780,7 @@ def test_user_can_list_environment_permission(client, environment):

# Then
assert response.status_code == status.HTTP_200_OK
assert (
len(response.json()) == 6
) # hard code how many permissions we expect there to be
assert len(response.json()) == 7


def test_environment_my_permissions_reruns_400_for_master_api_key(
Expand Down
6 changes: 3 additions & 3 deletions api/features/feature_segments/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from rest_framework.permissions import IsAuthenticated

from environments.models import Environment
from environments.permissions.constants import UPDATE_FEATURE_STATE
from environments.permissions.constants import MANAGE_SEGMENT_OVERRIDES


class FeatureSegmentPermissions(IsAuthenticated):
Expand All @@ -25,12 +25,12 @@ def has_permission(self, request, view):
environment = Environment.objects.get(id=int(environment))

return request.user.has_environment_permission(
UPDATE_FEATURE_STATE, environment
MANAGE_SEGMENT_OVERRIDES, environment
)

return False

def has_object_permission(self, request, view, obj):
return request.user.has_environment_permission(
UPDATE_FEATURE_STATE, environment=obj.environment
MANAGE_SEGMENT_OVERRIDES, environment=obj.environment
)
4 changes: 2 additions & 2 deletions api/features/feature_segments/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from rest_framework import serializers
from rest_framework.exceptions import PermissionDenied

from environments.permissions.constants import UPDATE_FEATURE_STATE
from environments.permissions.constants import MANAGE_SEGMENT_OVERRIDES
from features.models import FeatureSegment


Expand Down Expand Up @@ -115,7 +115,7 @@ def validate(self, attrs):
environment = environments.pop()

if not self.context["request"].user.has_environment_permission(
UPDATE_FEATURE_STATE, environment
MANAGE_SEGMENT_OVERRIDES, environment
):
raise PermissionDenied("You do not have permission to perform this action.")

Expand Down
4 changes: 2 additions & 2 deletions api/features/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from rest_framework.permissions import IsAuthenticated

from environments.models import Environment
from environments.permissions.constants import MANAGE_SEGMENT_OVERRIDES
from environments.permissions.constants import (
TAG_SUPPORTED_PERMISSIONS as TAG_SUPPORTED_ENVIRONMENT_PERMISSIONS,
)
Expand Down Expand Up @@ -164,8 +165,7 @@ def has_permission(self, request, view):
Environment, api_key=view.kwargs["environment_api_key"]
)

# TODO: create dedicated permission for creating segment overrides
return request.user.has_environment_permission(
permission=UPDATE_FEATURE_STATE,
permission=MANAGE_SEGMENT_OVERRIDES,
environment=environment,
)
29 changes: 29 additions & 0 deletions api/features/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,35 @@ def test_get_feature_evaluation_data(project, feature, environment, mocker, clie
)


def test_create_segment_override_forbidden(feature, segment, environment, api_client):
# Given
url = reverse(
"api-v1:environments:create-segment-override",
args=[environment.api_key, feature.id],
)
user = FFAdminUser.objects.create(email="[email protected]")
api_client.force_authenticate(user)

# When
enabled = True
string_value = "foo"
data = {
"feature_state_value": {"string_value": string_value},
"enabled": enabled,
"feature_segment": {"segment": segment.id},
}

response = api_client.post(
url, data=json.dumps(data), content_type="application/json"
)

# Then
assert response.status_code == 403
assert response.data == {
"detail": "You do not have permission to perform this action."
}


def test_create_segment_override(admin_client, feature, segment, environment):
# Given
url = reverse(
Expand Down
2 changes: 1 addition & 1 deletion api/features/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -690,8 +690,8 @@ def organisation_has_got_feature(request, organisation):
request_body=CreateSegmentOverrideFeatureStateSerializer(),
responses={200: CreateSegmentOverrideFeatureStateSerializer()},
)
@permission_classes([CreateSegmentOverridePermissions()])
@api_view(["POST"])
@permission_classes([CreateSegmentOverridePermissions])
def create_segment_override(
request: Request, environment_api_key: str, feature_pk: int
):
Expand Down
15 changes: 14 additions & 1 deletion api/permissions/migrations/0001_initial.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,22 @@

from django.db import migrations, models

from environments.permissions.constants import ENVIRONMENT_PERMISSIONS
from permissions.models import PROJECT_PERMISSION_TYPE, ENVIRONMENT_PERMISSION_TYPE
from projects.permissions import PROJECT_PERMISSIONS
from environments.permissions.constants import VIEW_ENVIRONMENT, UPDATE_FEATURE_STATE, MANAGE_IDENTITIES, CREATE_CHANGE_REQUEST, APPROVE_CHANGE_REQUEST
ENVIRONMENT_PERMISSIONS = [
(VIEW_ENVIRONMENT, "View permission for the given environment."),
(UPDATE_FEATURE_STATE, "Update the state or value for a given feature state."),
(MANAGE_IDENTITIES, "Manage identities in the given environment."),
(
CREATE_CHANGE_REQUEST,
"Permission to create change requests in the given environment.",
),
(
APPROVE_CHANGE_REQUEST,
"Permission to approve change requests in the given environment.",
),
]


def insert_default_project_permissions(apps, schema_model):
Expand Down
24 changes: 19 additions & 5 deletions api/poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ pip-tools = "~6.13.0"
pytest-cov = "~4.1.0"
datamodel-code-generator = "~0.22"
requests-mock = "^1.11.0"
django-extensions = "^3.2.3"

[build-system]
requires = ["poetry-core>=1.5.0"]
Expand Down