Skip to content

Commit

Permalink
Merge 24d79de into 75cfd31
Browse files Browse the repository at this point in the history
  • Loading branch information
nuwang committed Jul 16, 2020
2 parents 75cfd31 + 24d79de commit 8f3c1b7
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 17 deletions.
1 change: 1 addition & 0 deletions cloudman/cloudman/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
if OIDC_ENABLED:
LOGIN_URL = "/openid/openid/KeyCloak"
LOGOUT_URL = "/openid/logout"
LOAD_USER_ROLES = 'projman.rules.assign_oidc_roles'
configure_oidc(auth_uri, client_id, public_uri) # NOTE: scope is optional and can be left out
else:
OIDC_PROVIDERS = {}
Expand Down
2 changes: 1 addition & 1 deletion cloudman/helmsman/rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
rules.add_perm('helmsman.change_chart', rules.is_staff)
rules.add_perm('helmsman.delete_chart', rules.is_staff)

rules.add_perm('helmsman.view_install_template', rules.is_staff)
rules.add_perm('helmsman.view_install_template', rules.is_authenticated)
rules.add_perm('helmsman.add_install_template', rules.is_staff)
rules.add_perm('helmsman.change_install_template', rules.is_staff)
rules.add_perm('helmsman.delete_install_template', rules.is_staff)
6 changes: 3 additions & 3 deletions cloudman/projman/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,11 @@ def find(self, name):
def list(self):
return [self._to_proj_chart(chart) for chart
in self._get_helmsman_api().charts.list(self.project.namespace)
if self.has_permissions('projman.view_chart', chart)]
if self.has_permissions('projman.view_chart', self._to_proj_chart(chart))]

def get(self, chart_id):
chart = self._get_helmsman_api().charts.get(chart_id)
self.check_permissions('projman.view_chart', chart)
self.check_permissions('projman.view_chart', self._to_proj_chart(chart))
return (self._to_proj_chart(chart)
if chart and chart.namespace == self.project.namespace else None)

Expand All @@ -200,7 +200,7 @@ def _get_project_oidc_secret(self):

def create(self, template_name, release_name=None,
values=None, context=None):
self.check_permissions('projman.add_chart')
self.check_permissions('projman.add_chart', obj=self.project)
template = self._get_helmsman_api().templates.get(template_name)
if not context:
context = {}
Expand Down
51 changes: 45 additions & 6 deletions cloudman/projman/rules.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
from django.contrib.auth.models import Group

import rules


# Called by boss-oidc to process JWT user roles
# This function should be in a separate module, but leaving it here for now
def assign_oidc_roles(user, roles):
"""Default implementation of the LOAD_USER_ROLES callback
Args:
user (UserModel): Django user object for the user logging in
roles (list[str]): List of Keycloak roles assigned to the user
Note: Contains both realm roles and client roles
"""
for role in roles:
group, _ = Group.objects.get_or_create(name=f"{role}")
user.groups.add(group)
user.save()

# Delegate to keycloak in future iteration

# Predicates
@rules.predicate
def can_view_project(user, project):
if not project:
return False
return rules.is_group_member(f'projman-{project.namespace}')(user)


@rules.predicate
def is_project_admin(user, project):
if not project:
return False
return rules.is_group_member(f'projman-{project.namespace}-admin')(user)


@rules.predicate
def is_project_owner(user, project):
if not project:
Expand All @@ -18,13 +49,21 @@ def is_chart_owner(user, proj_chart):
return user.has_perm('projman.change_project', proj_chart.project)


@rules.predicate
def can_view_chart(user, proj_chart):
# Should have view rights on the parent project
if not proj_chart:
return False
return user.has_perm('projman.view_project', proj_chart.project)


# Permissions
rules.add_perm('projman.view_project', rules.is_authenticated)
rules.add_perm('projman.add_project', is_project_owner | rules.is_staff)
rules.add_perm('projman.change_project', is_project_owner | rules.is_staff)
rules.add_perm('projman.delete_project', is_project_owner | rules.is_staff)
rules.add_perm('projman.view_project', is_project_owner | is_project_admin | rules.is_staff | can_view_project)
rules.add_perm('projman.add_project', rules.is_staff)
rules.add_perm('projman.change_project', is_project_owner | is_project_admin | rules.is_staff)
rules.add_perm('projman.delete_project', is_project_owner | is_project_admin | rules.is_staff)

rules.add_perm('projman.view_chart', rules.is_authenticated)
rules.add_perm('projman.add_chart', is_chart_owner | rules.is_staff)
rules.add_perm('projman.view_chart', is_chart_owner | rules.is_staff | can_view_chart)
rules.add_perm('projman.add_chart', is_project_owner | is_project_admin | rules.is_staff)
rules.add_perm('projman.change_chart', is_chart_owner | rules.is_staff)
rules.add_perm('projman.delete_chart', is_chart_owner | rules.is_staff)
1 change: 1 addition & 0 deletions cloudman/projman/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def get_permissions(self, project):
"""
user = self.context['view'].request.user
return {
'add_project': user.has_perm('projman.add_project', project),
'change_project': user.has_perm('projman.change_project', project),
'delete_project': user.has_perm('projman.delete_project', project)
}
Expand Down
19 changes: 15 additions & 4 deletions cloudman/projman/tests/test_project_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
from helmsman.tests import HelmsManServiceTestBase
from helmsman import helpers as hm_helpers

from projman import rules


# Create your tests here.
class ProjManManServiceTestBase(HelmsManServiceTestBase):
Expand Down Expand Up @@ -117,10 +119,11 @@ def test_delete_unauthorized(self):
self._check_no_projects_exist()

def test_can_view_shared_project(self):
self._create_project()
response = self._create_project()
project_id_then = self._list_project()
self.client.force_login(
User.objects.get_or_create(username='notaprojadmin', is_staff=False)[0])
non_admin = User.objects.get_or_create(username='notaprojadmin', is_staff=False)[0]
self.client.force_login(non_admin)
rules.assign_oidc_roles(non_admin, ["projman-" + response.data['namespace']])
project_id_now = self._list_project()
assert project_id_now # should be visible
assert project_id_then == project_id_now # should be the same project
Expand Down Expand Up @@ -255,7 +258,7 @@ def _check_no_project_charts_exist(self, project_id):
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_200_OK)
# There's always the default projman chart, so ignore that
self.assertEqual(len(response.data['results']), 1)
self.assertLessEqual(len(response.data['results']), 1)

def _update_project_chart(self, project_id, chart_id):
url = reverse('projman:chart-detail', args=[project_id, chart_id])
Expand Down Expand Up @@ -374,6 +377,14 @@ def test_can_view_shared_chart(self):
response = self._delete_project(project_id)
self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT, response.data)

def test_chart_create_assign_project_admin(self):
project_id = self._create_project()
non_admin = User.objects.get_or_create(username='projadminassigned', is_staff=False)[0]
self.client.force_login(non_admin)
rules.assign_oidc_roles(non_admin, ["projman-gvl-admin"])
response = self._create_project_chart(project_id)
self.assertEqual(response.status_code, status.HTTP_201_CREATED, response.data)

def test_chart_rollback(self):
project_id = self._create_project()
self._create_project_chart(project_id)
Expand Down
10 changes: 7 additions & 3 deletions cloudman/projman/views.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""ProjMan Create views."""
from rest_framework.views import APIView
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
from rest_framework.permissions import IsAuthenticated, IsAdminUser
from rest_framework.permissions import IsAuthenticated

from djcloudbridge import drf_helpers
from . import serializers
Expand Down Expand Up @@ -43,8 +44,11 @@ class ProjectChartViewSet(drf_helpers.CustomModelViewSet):
serializer_class = serializers.PMProjectChartSerializer

def list_objects(self):
project = ProjManAPI.from_request(self.request).projects.get(
self.kwargs["project_pk"])
try:
project = ProjManAPI.from_request(self.request).projects.get(
self.kwargs["project_pk"])
except PermissionDenied:
project = None
if project:
return project.charts.list()
else:
Expand Down

0 comments on commit 8f3c1b7

Please sign in to comment.