Skip to content

Commit

Permalink
add groups to profile endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Leinfelder committed Jan 31, 2019
1 parent 219e47d commit 7d310e0
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 22 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,21 @@ custom profile endpoint, returns user profile information as json:
"email": "[email protected]",
"email_verified": "True",
"is_superuser": "False",
"can_authenticate": "True"
"can_authenticate": "True",
"groups": ["staff", "customer"]
}
````

## configuration
- navigate to `/admin/` to setup the OAuth2 uids and secrets.
- navigate to `/admin/` to setup the OAuth2 uids and secrets.

### setup a profile group and add permissions
setup your first group, eg. default and set the default flag.
all new user will be added to the default flag. for testing, you may need to add the default group for the first admin user by hand.

as a second step you need to authorize your app by mapping a "Can authenticate" flag between the application and the new default group.
see Group permissions for that.

you can also add a single permission for a user without the need of generating groups. see: Profile permissions

there is an option to provide Application groups for a application based on profiles or group permission these groups get returned to the application on the profile call.
18 changes: 18 additions & 0 deletions janus/migrations/0007_auto_20190131_2348.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 2.1.5 on 2019-01-31 23:48

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('janus', '0006_applicationextension_profile_replace_json'),
]

operations = [
migrations.AlterField(
model_name='applicationgroup',
name='description',
field=models.TextField(blank=True, default=''),
),
]
2 changes: 1 addition & 1 deletion janus/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ApplicationGroup(models.Model):
application = models.ForeignKey(Application, on_delete=models.CASCADE)
# the name will be returned as char to the application
name = models.CharField(max_length=255)
description = models.TextField(default="")
description = models.TextField(default="", blank=True)

def __str__(self):
return self.name
Expand Down
105 changes: 99 additions & 6 deletions janus/tests.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import json
from datetime import timedelta

from django.contrib.auth.models import User
from django.contrib.auth import get_user_model
from django.test import TestCase, Client
from django.urls import reverse
from django.utils.timezone import now
from oauth2_provider.models import Application, AccessToken, Grant

from janus.models import ProfileGroup, Profile, GroupPermission, ProfilePermission
from janus.models import ProfileGroup, Profile, GroupPermission, ProfilePermission, ApplicationGroup
from janus.views import ProfileView

User = get_user_model()


class ProfileGeneration(TestCase):
Expand Down Expand Up @@ -70,9 +73,9 @@ def setUp(self):

def test_not_authenticated(self):
c = Client()
response = c.get('/')
response = c.get(reverse('index'))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b'hello from janus')
self.assertIn('hello from janus'.lower(), response.content.decode('utf-8').lower())

def test_authentication(self):
response = self.client.login(username='bob', password='testjanus123')
Expand All @@ -81,9 +84,10 @@ def test_authentication(self):
def test_authentication_2(self):
c = Client()
response = c.post(reverse('login'), dict(username='bob', password='testjanus123', response_type='http'))
response = c.get('/')
response = c.get(reverse('index'))
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content, b'hello from janus bob')
self.assertIn('hello from janus'.lower(), response.content.decode('utf-8').lower())
self.assertIn('to bob'.lower(), response.content.decode('utf-8').lower())

def test_authenticate_bob(self):
# authenticate the user
Expand Down Expand Up @@ -167,3 +171,92 @@ def test_authenticate_eve(self):




class ProfileViewTests(TestCase):
def setUp(self):
# generate some user
self.user_admin = User.objects.create(username='admin')
self.user_staff = User.objects.create(username='help-desk-user')
self.user_customer = User.objects.create(username='customer')

# init some profile groups
self.group_default = ProfileGroup.objects.create(name='default', default=True)
self.group_superuser = ProfileGroup.objects.create(name='superuser_group')
self.group_staff = ProfileGroup.objects.create(name='staff_group')
self.group_customer = ProfileGroup.objects.create(name='customer_group')

# init user profiles
Profile.create_default_profile(self.user_admin)
Profile.create_default_profile(self.user_staff)
Profile.create_default_profile(self.user_customer)

# some apps
self.application_one = Application.objects.create(user=None,
redirect_uris='https://localhost:8000/accounts/janus/login/callback/',
client_type='confidential',
authorization_grant_type='authorization-code',
name='test', skip_authorization=True)
self.application_two = Application.objects.create(user=None,
redirect_uris='https://localhost:8000/accounts/janus/login/callback/',
client_type='confidential',
authorization_grant_type='authorization-code',
name='test_all_superuser', skip_authorization=True)

# generate some application groups (the groups will be returned to the app on profile call)
staff_app_group = ApplicationGroup.objects.create(application=self.application_one,
name="django_user_staff",
description="staff")
customer_app_group = ApplicationGroup.objects.create(application=self.application_one,
name="django_customer",
description="customer")

app2_group1 = ApplicationGroup.objects.create(application=self.application_two,
name="group1")
app2_group2 = ApplicationGroup.objects.create(application=self.application_two,
name="group2")
app2_group3 = ApplicationGroup.objects.create(application=self.application_one,
name="group3")

# add app_groups to profile_groups iva (profile)group permissions
gp1 = GroupPermission.objects.create(profile_group=self.group_staff, application=self.application_one)
gp1.groups.add(staff_app_group)

gp2 = GroupPermission.objects.create(profile_group=self.group_staff, application=self.application_one)
gp2.groups.add(customer_app_group)


gp2 = GroupPermission.objects.create(profile_group=self.group_superuser, application=self.application_two)
gp2.groups.add(app2_group1, app2_group2, app2_group3) # group3 is not supposed in here, simulate human error



def test_profile_view_groups(self):
# check ProfileView
pv = ProfileView()

# add users to groups
self.user_staff.profile.group.add(self.group_staff)
self.user_staff.profile.group.add(self.group_customer)

list_group_names = pv.get_profile_group_memberships(self.user_staff, self.application_one)
self.assertIn("django_user_staff", list_group_names)
self.assertIn("django_customer", list_group_names)


list_empty_groups = pv.get_profile_group_memberships(self.user_customer, self.application_one)
self.assertAlmostEqual(len(list_empty_groups), 0)


# test super user has on app2 -> group1, group2
self.user_admin.profile.group.add(self.group_superuser)
list_two_not_three = pv.get_profile_group_memberships(self.user_admin, self.application_two)
self.assertIn("group1", list_two_not_three)
self.assertIn("group2", list_two_not_three)
self.assertNotIn("group3", list_two_not_three)
# also group3 is only linked to the wrong app so its also not present in the desired app
list_empty_again = pv.get_profile_group_memberships(self.user_admin, self.application_one)
self.assertAlmostEqual(len(list_empty_again), 0)




2 changes: 1 addition & 1 deletion janus/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@
url(r'^o/restart_authorize/$', views.restart_authorize, name="restart_authorize"),

url('^accounts/', include('django.contrib.auth.urls')),
url(r'^', views.index, name='index'),
url(r'', views.index, name='index'),
]
86 changes: 74 additions & 12 deletions janus/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,18 @@

class ProfileView(ProtectedResourceView):

def get_group_permissions(self, user, token):

def get_profile_memberships(self, user):

all_profiles = Profile.objects.get(user=user).group.all()

# add the default groups by default
default_profile = ProfileGroup.objects.filter(default=True)
all_profiles = set(all_profiles | default_profile)

return list(all_profiles)

def get_group_permissions(self, user, application):
"""
Validates the group permissions for a user given a token
:param user:
Expand All @@ -25,14 +36,10 @@ def get_group_permissions(self, user, token):
if not user.is_authenticated:
return is_superuser, can_authenticate

all_groups = Profile.objects.get(user=user).group.all()

# add the default groups by default
default_groups = ProfileGroup.objects.filter(default=True)
all_groups = list(all_groups | default_groups)
all_groups = self.get_profile_memberships(user)

for g in all_groups:
gp = GroupPermission.objects.filter(profile_group=g, application=token.application)
gp = GroupPermission.objects.filter(profile_group=g, application=application)
if gp.count() == 0:
continue
elif gp.count() == 1:
Expand All @@ -45,7 +52,7 @@ def get_group_permissions(self, user, token):
print('We have a problem')
return is_superuser, can_authenticate

def get_personal_permissions(self, user, token):
def get_personal_permissions(self, user, application):
"""
Validates the personal permissions for a user given a token
:param user:
Expand All @@ -57,14 +64,64 @@ def get_personal_permissions(self, user, token):
if not user.is_authenticated:
return is_superuser, can_authenticate

pp = ProfilePermission.objects.filter(profile__user=user, application=token.application).first()
pp = ProfilePermission.objects.filter(profile__user=user, application=application).first()
if pp:
if pp.is_superuser:
is_superuser = True
if pp.can_authenticate:
can_authenticate = True
return is_superuser, can_authenticate


def get_profile_group_memberships(self, user, application):
"""
collect group names form user profile group memberships
:param user:
:param token:
:return:
"""

all_profiles = self.get_profile_memberships(user)

group_list = set()

for g in all_profiles:
# get profile-group-permission object
gp = GroupPermission.objects.filter(profile_group=g, application=application)
for elem in gp:
groups = elem.groups.all()

for g in groups:
# ensure only groups for this application can be returned
if g.application == application:
group_list.add(g.name)

return group_list


def get_profile_personal_memberships(self, user, application):
"""
collect group names form user profile permission
:param user:
:param token:
:return:
"""

profile_permissions = ProfilePermission.objects.filter(profile__user=user, application=application).first()

group_list = set()

if profile_permissions:
groups = profile_permissions.groups.all()

for g in groups:
# ensure only groups for this application can be returned
if g.application == application:
group_list.add(g.name)

return group_list


def get(self, request):
if request.resource_owner:
user = request.resource_owner
Expand All @@ -80,10 +137,10 @@ def get(self, request):
if not token:
return self.error_response(OAuthToolkitError("No access token"))

is_superuser, can_authenticate = self.get_group_permissions(token.user, token)
is_superuser, can_authenticate = self.get_group_permissions(token.user, token.application)

# if set the personal settings overwrite the user settings
pp_superuser, pp_authenticate = self.get_personal_permissions(token.user, token)
pp_superuser, pp_authenticate = self.get_personal_permissions(token.user, token.application)
if pp_superuser is not None:
if type(pp_superuser) is bool:
is_superuser = pp_superuser
Expand All @@ -92,6 +149,10 @@ def get(self, request):
if type(pp_authenticate) is bool:
can_authenticate = pp_authenticate

groups = set()
groups.union(self.get_profile_group_memberships(token.user, token.application))
groups.union(self.get_profile_personal_memberships(token.user, token.application))

json_data = (
{
'id': user.username,
Expand All @@ -102,7 +163,8 @@ def get(self, request):
# ToDo: check the emails
'email_verified': True,
'is_superuser': is_superuser,
'can_authenticate': can_authenticate
'can_authenticate': can_authenticate,
'groups': list(groups),
}
)
json_data = self._replace_json_ids(json_data, token)
Expand Down

0 comments on commit 7d310e0

Please sign in to comment.