Skip to content

Commit

Permalink
Permissions per tasks and jobs (#185)
Browse files Browse the repository at this point in the history
  • Loading branch information
nmanovic committed Nov 7, 2018
1 parent d7e6998 commit 608253f
Show file tree
Hide file tree
Showing 30 changed files with 412 additions and 318 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Polyshape editing method has been improved. You can redraw part of shape instead of points cloning.
- Unified shortcut (Esc) for close any mode instead of different shortcuts (Alt+N, Alt+G, Alt+M etc.).
- Dump file contains information about data source (e.g. video name, archive name, ...)
- Update requests library due to https://nvd.nist.gov/vuln/detail/CVE-2018-18074
- Per task/job permissions to create/access/change/delete tasks and annotations

### Fixed
- Performance bottleneck has been fixed during you create new objects (draw, copy, merge etc).
- Label UI elements aren't updated after changelabel.
- Attribute annotation mode can use invalid shape position after resize or move shapes.


## [0.2.0] - 2018-09-28
### Added
- New annotation shapes: polygons, polylines, points
Expand Down
10 changes: 10 additions & 0 deletions cvat/apps/authentication/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@

default_app_config = 'cvat.apps.authentication.apps.AuthenticationConfig'

from enum import Enum

class AUTH_ROLE(Enum):
ADMIN = 'admin'
USER = 'user'
ANNOTATOR = 'annotator'
OBSERVER = 'observer'

def __str__(self):
return self.value
20 changes: 19 additions & 1 deletion cvat/apps/authentication/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,24 @@
# SPDX-License-Identifier: MIT

from django.contrib import admin
from django.contrib.auth.models import Group, User
from django.contrib.auth.admin import GroupAdmin, UserAdmin
from django.utils.translation import ugettext_lazy as _

# Register your models here.
class CustomUserAdmin(UserAdmin):
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('first_name', 'last_name', 'email')}),
(_('Permissions'), {'fields': ('is_active', 'is_staff', 'is_superuser',
'groups',)}),
(_('Important dates'), {'fields': ('last_login', 'date_joined')}),
)

class CustomGroupAdmin(GroupAdmin):
fieldsets = ((None, {'fields': ('name',)}),)


admin.site.unregister(User)
admin.site.unregister(Group)
admin.site.register(User, CustomUserAdmin)
admin.site.register(Group, CustomGroupAdmin)
13 changes: 2 additions & 11 deletions cvat/apps/authentication/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,11 @@
# SPDX-License-Identifier: MIT

from django.apps import AppConfig
from django.db.models.signals import post_migrate, post_save
from .settings.authentication import DJANGO_AUTH_TYPE

class AuthenticationConfig(AppConfig):
name = 'cvat.apps.authentication'

def ready(self):
from . import signals
from django.contrib.auth.models import User
from .auth import register_signals

post_migrate.connect(signals.create_groups)

if DJANGO_AUTH_TYPE == 'SIMPLE':
post_save.connect(signals.create_user, sender=User, dispatch_uid="create_user")

import django_auth_ldap.backend
django_auth_ldap.backend.populate_user.connect(signals.update_ldap_groups)
register_signals()
80 changes: 80 additions & 0 deletions cvat/apps/authentication/auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT

import os
from django.conf import settings
import rules
from . import AUTH_ROLE

def register_signals():
from django.db.models.signals import post_migrate, post_save
from django.contrib.auth.models import User, Group

def create_groups(sender, **kwargs):
for role in AUTH_ROLE:
db_group, _ = Group.objects.get_or_create(name=role)
db_group.save()

post_migrate.connect(create_groups, weak=False)

if settings.DJANGO_AUTH_TYPE == 'BASIC':
from .auth_basic import create_user

post_save.connect(create_user, sender=User)
elif settings.DJANGO_AUTH_TYPE == 'LDAP':
import django_auth_ldap.backend
from .auth_ldap import create_user

django_auth_ldap.backend.populate_user.connect(create_user)

# AUTH PREDICATES
has_admin_role = rules.is_group_member(str(AUTH_ROLE.ADMIN))
has_user_role = rules.is_group_member(str(AUTH_ROLE.USER))
has_annotator_role = rules.is_group_member(str(AUTH_ROLE.ANNOTATOR))
has_observer_role = rules.is_group_member(str(AUTH_ROLE.OBSERVER))

@rules.predicate
def is_task_owner(db_user, db_task):
# If owner is None (null) the task can be accessed/changed/deleted
# only by admin. At the moment each task has an owner.
return db_task.owner == db_user

@rules.predicate
def is_task_assignee(db_user, db_task):
return db_task.assignee == db_user

@rules.predicate
def is_task_annotator(db_user, db_task):
from functools import reduce

db_segments = list(db_task.segment_set.prefetch_related('job_set__assignee').all())
return any([is_job_annotator(db_user, db_job)
for db_segment in db_segments for db_job in db_segment.job_set.all()])

@rules.predicate
def is_job_owner(db_user, db_job):
return is_task_owner(db_user, db_job.segment.task)

@rules.predicate
def is_job_annotator(db_user, db_job):
db_task = db_job.segment.task
# A job can be annotated by any user if the task's assignee is None.
has_rights = db_task.assignee is None or is_task_assignee(db_user, db_task)
if db_job.assignee is not None:
has_rights |= (db_user == db_job.assignee)

return has_rights

# AUTH PERMISSIONS RULES
rules.add_perm('engine.task.create', has_admin_role | has_user_role)
rules.add_perm('engine.task.access', has_admin_role | has_observer_role |
is_task_owner | is_task_annotator)
rules.add_perm('engine.task.change', has_admin_role | is_task_owner |
is_task_assignee)
rules.add_perm('engine.task.delete', has_admin_role | is_task_owner)

rules.add_perm('engine.job.access', has_admin_role | has_observer_role |
is_job_owner | is_job_annotator)
rules.add_perm('engine.job.change', has_admin_role | is_job_owner |
is_job_annotator)
12 changes: 12 additions & 0 deletions cvat/apps/authentication/auth_basic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT
from . import AUTH_ROLE
from django.conf import settings

def create_user(sender, instance, created, **kwargs):
from django.contrib.auth.models import Group

if instance.is_superuser and instance.is_staff:
db_group = Group.objects.get(name=AUTH_ROLE.ADMIN)
instance.groups.add(db_group)
29 changes: 29 additions & 0 deletions cvat/apps/authentication/auth_ldap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

# Copyright (C) 2018 Intel Corporation
#
# SPDX-License-Identifier: MIT

from django.conf import settings
from . import AUTH_ROLE

AUTH_LDAP_GROUPS = {
AUTH_ROLE.ADMIN: settings.AUTH_LDAP_ADMIN_GROUPS,
AUTH_ROLE.ANNOTATOR: settings.AUTH_LDAP_ANNOTATOR_GROUPS,
AUTH_ROLE.USER: settings.AUTH_LDAP_USER_GROUPS,
AUTH_ROLE.OBSERVER: settings.AUTH_LDAP_OBSERVER_GROUPS
}

def create_user(sender, user=None, ldap_user=None, **kwargs):
from django.contrib.auth.models import Group
user_groups = []
for role in AUTH_ROLE:
db_group = Group.objects.get(name=role)

for ldap_group in AUTH_LDAP_GROUPS[role]:
if ldap_group.lower() in ldap_user.group_dns:
user_groups.append(db_group)
if role == AUTH_ROLE.ADMIN:
user.is_staff = user.is_superuser = True

user.groups.set(user_groups)
user.save()
8 changes: 4 additions & 4 deletions cvat/apps/authentication/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@
#
# SPDX-License-Identifier: MIT

from functools import wraps
from urllib.parse import urlparse
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.shortcuts import resolve_url, reverse
from django.http import JsonResponse
from urllib.parse import urlparse
from django.contrib.auth.views import redirect_to_login

from functools import wraps
from django.conf import settings

def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None, redirect_methods=['GET']):
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME,
login_url=None, redirect_methods=['GET']):
def decorator(view_func):
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
Expand Down
5 changes: 0 additions & 5 deletions cvat/apps/authentication/settings/__init__.py

This file was deleted.

56 changes: 0 additions & 56 deletions cvat/apps/authentication/settings/auth_ldap.py

This file was deleted.

8 changes: 0 additions & 8 deletions cvat/apps/authentication/settings/auth_simple.py

This file was deleted.

58 changes: 0 additions & 58 deletions cvat/apps/authentication/settings/authentication.py

This file was deleted.

Loading

0 comments on commit 608253f

Please sign in to comment.