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

Add users to API #265

Merged
merged 2 commits into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions rdmo/accounts/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
class AccountsConfig(AppConfig):
name = 'rdmo.accounts'
verbose_name = _('Accounts')

def ready(self):
from . import rules
16 changes: 16 additions & 0 deletions rdmo/accounts/migrations/0019_delete_proxyuser.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Django 2.2.16 on 2020-10-19 15:37

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('accounts', '0018_blank_fields'),
]

operations = [
migrations.DeleteModel(
name='ProxyUser',
),
]
13 changes: 2 additions & 11 deletions rdmo/accounts/models.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django.utils.translation import ugettext_lazy as _

from rdmo.core.models import TranslationMixin


class ProxyUser(User):

class Meta:
proxy = True
default_permissions = ()
permissions = (('view_user', 'Can view user'),)


class AdditionalField(models.Model, TranslationMixin):

TYPE_CHOICES = (
Expand Down
16 changes: 16 additions & 0 deletions rdmo/accounts/rules.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import rules
from django.contrib.sites.models import Site

from .utils import is_site_manager as is_site_manager_util


@rules.predicate
def is_site_manager(manager, user):
if is_site_manager_util(manager):
current_site = Site.objects.get_current()
return current_site in user.role.member.all()
else:
return False


rules.add_perm('auth.view_user_object', is_site_manager)
80 changes: 72 additions & 8 deletions rdmo/accounts/serializers/v1.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,80 @@
from django.contrib.auth.models import User

from django.conf import settings
from django.contrib.auth.models import Group, User
from django.contrib.sites.models import Site
from rdmo.projects.models import Membership
from rest_framework import serializers

from ..models import Role

class UserSerializer(serializers.ModelSerializer):

class SiteSerializer(serializers.ModelSerializer):

class Meta:
model = User
model = Site
fields = (
'id',
'name',
'domain'
)


class GroupSerializer(serializers.ModelSerializer):

class Meta:
model = Group
fields = (
'id',
'username',
'first_name',
'last_name',
'email'
'name'
)


class RoleSerializer(serializers.ModelSerializer):

member = SiteSerializer(many=True)
manager = SiteSerializer(many=True)

class Meta:
model = Role
fields = (
'id',
'member',
'manager'
)


class MembershipSerializer(serializers.ModelSerializer):

class Meta:
model = Membership
fields = (
'id',
'project',
'role'
)


class UserSerializer(serializers.ModelSerializer):

groups = GroupSerializer(many=True)
role = RoleSerializer()
memberships = serializers.SerializerMethodField()

class Meta:
model = User
fields = [
'id',
'groups',
'role',
'memberships'
]
if settings.USER_API:
fields += [
'username',
'first_name',
'last_name',
'email'
]

def get_memberships(self, obj):
memberships = Membership.objects.filter(user=obj)
return MembershipSerializer(memberships, many=True).data
1 change: 1 addition & 0 deletions rdmo/accounts/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
('auth', 'group', 'view_group'),
)),
('api', (
('auth', 'user', 'view_user'),
('domain', 'attribute', 'add_attribute'),
('domain', 'attribute', 'change_attribute'),
('domain', 'attribute', 'delete_attribute'),
Expand Down
96 changes: 96 additions & 0 deletions rdmo/accounts/tests/test_viewsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import pytest
from django.contrib.auth.models import User
from django.urls import reverse

users = (
('site', 'site'),
('api', 'api'),
('user', 'user'),
('anonymous', None),
)

status_map = {
'list': {
'site': 200, 'api': 200, 'user': 200, 'anonymous': 401
},
'detail': {
'site': 200, 'api': 200, 'user': 404, 'anonymous': 401
},
'create': {
'site': 405, 'api': 405, 'user': 405, 'anonymous': 401
},
'update': {
'site': 405, 'api': 405, 'user': 405, 'anonymous': 401
},
'delete': {
'site': 405, 'api': 405, 'user': 405, 'anonymous': 401
}
}

urlnames = {
'list': 'v1-accounts:user-list',
'detail': 'v1-accounts:user-detail'
}


@pytest.mark.parametrize('username,password', users)
def test_list(db, client, username, password):
client.login(username=username, password=password)

url = reverse(urlnames['list'])
response = client.get(url)
assert response.status_code == status_map['list'][username], response.json()
if response.status_code == 200:
if username == 'api':
assert len(response.json()) == 11
elif username == 'site':
# the site admin must not see the user 'other'
assert len(response.json()) == 10
else:
assert len(response.json()) == 0


@pytest.mark.parametrize('username,password', users)
def test_detail(db, client, username, password):
client.login(username=username, password=password)
instances = User.objects.all()

for instance in instances:
url = reverse(urlnames['detail'], args=[instance.pk])
response = client.get(url)
if username == 'site' and instance.username == 'other':
# the site admin must not see the user 'other'
assert response.status_code == 404, response.json()
else:
assert response.status_code == status_map['detail'][username], response.json()


@pytest.mark.parametrize('username,password', users)
def test_create(db, client, username, password):
client.login(username=username, password=password)

url = reverse(urlnames['list'])
response = client.post(url, {})
assert response.status_code == status_map['create'][username], response.json()


@pytest.mark.parametrize('username,password', users)
def test_update(db, client, username, password):
client.login(username=username, password=password)
instances = User.objects.all()

for instance in instances:
url = reverse(urlnames['detail'], args=[instance.pk])
response = client.put(url, {}, content_type='application/json')
assert response.status_code == status_map['update'][username], response.json()


@pytest.mark.parametrize('username,password', users)
def test_delete(db, client, username, password):
client.login(username=username, password=password)
instances = User.objects.all()

for instance in instances:
url = reverse(urlnames['detail'], args=[instance.pk])
response = client.delete(url)
assert response.status_code == status_map['delete'][username], response.json()
13 changes: 13 additions & 0 deletions rdmo/accounts/urls/v1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from django.urls import include, path
from rest_framework import routers

from ..viewsets import UserViewSet

app_name = 'v1-accounts'

router = routers.DefaultRouter()
router.register(r'users', UserViewSet, basename='user')

urlpatterns = [
path('', include(router.urls)),
]
37 changes: 37 additions & 0 deletions rdmo/accounts/viewsets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from django.contrib.auth.models import User
from django.contrib.sites.models import Site
from django_filters.rest_framework import DjangoFilterBackend
from rdmo.core.permissions import HasModelPermission, HasObjectPermission
from rest_framework.viewsets import ReadOnlyModelViewSet

from .serializers.v1 import UserSerializer
from .utils import is_site_manager


class UserViewSetMixin(object):

def get_users_for_user(self, user):
if user.is_authenticated:
if user.has_perm('auth.view_user'):
return User.objects.all()
elif is_site_manager(user):
current_site = Site.objects.get_current()
return User.objects.filter(role__member=current_site)
return User.objects.none()


class UserViewSet(UserViewSetMixin, ReadOnlyModelViewSet):
permission_classes = (HasModelPermission | HasObjectPermission, )
serializer_class = UserSerializer

filter_backends = (DjangoFilterBackend,)
filterset_fields = (
'username',
'first_name',
'last_name',
'email',
'groups',
)

def get_queryset(self):
return self.get_users_for_user(self.request.user)
2 changes: 2 additions & 0 deletions rdmo/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@
EMAIL_RECIPIENTS_CHOICES = []
EMAIL_RECIPIENTS_INPUT = False

USER_API = True

EXPORT_FORMATS = (
('pdf', _('PDF')),
('rtf', _('Rich Text Format')),
Expand Down
1 change: 1 addition & 0 deletions rdmo/core/urls/v1.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
router.register(r'groups', GroupViewSet, basename='group')

urlpatterns = [
path('accounts/', include('rdmo.accounts.urls.v1')),
path('conditions/', include('rdmo.conditions.urls.v1')),
path('domain/', include('rdmo.domain.urls.v1')),
path('options/', include('rdmo.options.urls.v1')),
Expand Down
3 changes: 1 addition & 2 deletions rdmo/projects/serializers/v1/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from rest_framework import serializers

from rdmo.accounts.serializers.v1 import UserSerializer
from rdmo.services.validators import ProviderValidator
from rest_framework import serializers

from ...models import (Integration, IntegrationOption, Issue, IssueResource,
Membership, Project, Snapshot, Value)
Expand Down
14 changes: 13 additions & 1 deletion testing/fixtures/accounts.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@
"manager": []
}
},
{
{
"model": "accounts.role",
"pk": 8,
"fields": {
Expand Down Expand Up @@ -190,5 +190,17 @@
1
]
}
},
{
"model": "accounts.role",
"pk": 11,
"fields": {
"user": 11,
"member": [
2,
3
],
"manager": []
}
}
]