Permissions per tasks and jobs (#185)
parent
d7e69982fd
commit
608253f1cc
@ -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)
|
||||
@ -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)
|
||||
@ -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()
|
||||
@ -1,5 +0,0 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
@ -1,56 +0,0 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.conf import settings
|
||||
import ldap
|
||||
from django_auth_ldap.config import LDAPSearch, NestedActiveDirectoryGroupType
|
||||
|
||||
# Baseline configuration.
|
||||
settings.AUTH_LDAP_SERVER_URI = ""
|
||||
|
||||
# Credentials for LDAP server
|
||||
settings.AUTH_LDAP_BIND_DN = ""
|
||||
settings.AUTH_LDAP_BIND_PASSWORD = ""
|
||||
|
||||
# Set up basic user search
|
||||
settings.AUTH_LDAP_USER_SEARCH = LDAPSearch("dc=example,dc=com",
|
||||
ldap.SCOPE_SUBTREE, "(sAMAccountName=%(user)s)")
|
||||
|
||||
# Set up the basic group parameters.
|
||||
settings.AUTH_LDAP_GROUP_SEARCH = LDAPSearch("dc=example,dc=com",
|
||||
ldap.SCOPE_SUBTREE, "(objectClass=group)")
|
||||
settings.AUTH_LDAP_GROUP_TYPE = NestedActiveDirectoryGroupType()
|
||||
|
||||
# # Simple group restrictions
|
||||
settings.AUTH_LDAP_REQUIRE_GROUP = "cn=cvat,ou=Groups,dc=example,dc=com"
|
||||
|
||||
# Populate the Django user from the LDAP directory.
|
||||
settings.AUTH_LDAP_USER_ATTR_MAP = {
|
||||
"first_name": "givenName",
|
||||
"last_name": "sn",
|
||||
"email": "mail",
|
||||
}
|
||||
|
||||
settings.AUTH_LDAP_ALWAYS_UPDATE_USER = True
|
||||
|
||||
# Cache group memberships for an hour to minimize LDAP traffic
|
||||
settings.AUTH_LDAP_CACHE_GROUPS = True
|
||||
settings.AUTH_LDAP_GROUP_CACHE_TIMEOUT = 3600
|
||||
settings.AUTH_LDAP_AUTHORIZE_ALL_USERS = True
|
||||
|
||||
# Keep ModelBackend around for per-user permissions and maybe a local
|
||||
# superuser.
|
||||
settings.AUTHENTICATION_BACKENDS.append('django_auth_ldap.backend.LDAPBackend')
|
||||
|
||||
AUTH_LDAP_ADMIN_GROUPS = [
|
||||
"cn=cvat_admins,ou=Groups,dc=example,dc=com"
|
||||
]
|
||||
|
||||
AUTH_LDAP_DATA_ANNOTATORS_GROUPS = [
|
||||
]
|
||||
|
||||
AUTH_LDAP_DEVELOPER_GROUPS = [
|
||||
"cn=cvat_users,ou=Groups,dc=example,dc=com"
|
||||
]
|
||||
@ -1,8 +0,0 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
# Specify groups that new users will have
|
||||
AUTH_SIMPLE_DEFAULT_GROUPS = []
|
||||
|
||||
@ -1,58 +0,0 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.conf import settings
|
||||
import os
|
||||
|
||||
settings.LOGIN_URL = 'login'
|
||||
settings.LOGIN_REDIRECT_URL = '/'
|
||||
|
||||
settings.AUTHENTICATION_BACKENDS = [
|
||||
'django.contrib.auth.backends.ModelBackend',
|
||||
]
|
||||
|
||||
AUTH_LDAP_DEVELOPER_GROUPS = []
|
||||
AUTH_LDAP_DATA_ANNOTATORS_GROUPS = []
|
||||
AUTH_LDAP_ADMIN_GROUPS = []
|
||||
|
||||
DJANGO_AUTH_TYPE = 'LDAP' if os.environ.get('DJANGO_AUTH_TYPE', '') == 'LDAP' else 'SIMPLE'
|
||||
|
||||
if DJANGO_AUTH_TYPE == 'LDAP':
|
||||
from .auth_ldap import *
|
||||
else:
|
||||
from .auth_simple import *
|
||||
|
||||
# Definition of CVAT groups with permissions for task and annotation objects
|
||||
# Annotator - can modify annotation for task, but cannot add, change and delete tasks
|
||||
# Developer - can create tasks and modify (delete) owned tasks and any actions with annotation
|
||||
# Admin - can any actions for task and annotation, can login to admin area and manage groups and users
|
||||
cvat_groups_definition = {
|
||||
'user': {
|
||||
'description': '',
|
||||
'permissions': {
|
||||
'task': ['view', 'add', 'change', 'delete'],
|
||||
'annotation': ['view', 'change'],
|
||||
},
|
||||
'ldap_groups': AUTH_LDAP_DEVELOPER_GROUPS,
|
||||
},
|
||||
|
||||
'annotator': {
|
||||
'description': '',
|
||||
'permissions': {
|
||||
'task': ['view'],
|
||||
'annotation': ['view', 'change'],
|
||||
},
|
||||
'ldap_groups': AUTH_LDAP_DATA_ANNOTATORS_GROUPS,
|
||||
},
|
||||
|
||||
'admin': {
|
||||
'description': '',
|
||||
'permissions': {
|
||||
'task': ['view', 'add', 'change', 'delete'],
|
||||
'annotation': ['view', 'change'],
|
||||
},
|
||||
'ldap_groups': AUTH_LDAP_ADMIN_GROUPS,
|
||||
},
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
|
||||
# Copyright (C) 2018 Intel Corporation
|
||||
#
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
from django.db import models
|
||||
|
||||
from django.conf import settings
|
||||
from .settings import authentication
|
||||
from django.contrib.auth.models import User, Group
|
||||
|
||||
def setup_group_permissions(group):
|
||||
from cvat.apps.engine.models import Task
|
||||
from django.contrib.auth.models import Permission
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
|
||||
def append_permissions_for_model(model):
|
||||
content_type = ContentType.objects.get_for_model(model)
|
||||
for perm_target, actions in authentication.cvat_groups_definition[group.name]['permissions'].items():
|
||||
for action in actions:
|
||||
codename = '{}_{}'.format(action, perm_target)
|
||||
try:
|
||||
perm = Permission.objects.get(codename=codename, content_type=content_type)
|
||||
group_permissions.append(perm)
|
||||
except:
|
||||
pass
|
||||
group_permissions = []
|
||||
append_permissions_for_model(Task)
|
||||
|
||||
group.permissions.set(group_permissions)
|
||||
group.save()
|
||||
|
||||
def create_groups(sender, **kwargs):
|
||||
for cvat_role, _ in authentication.cvat_groups_definition.items():
|
||||
Group.objects.get_or_create(name=cvat_role)
|
||||
|
||||
def update_ldap_groups(sender, user=None, ldap_user=None, **kwargs):
|
||||
user_groups = []
|
||||
for cvat_role, role_settings in authentication.cvat_groups_definition.items():
|
||||
group_instance, _ = Group.objects.get_or_create(name=cvat_role)
|
||||
setup_group_permissions(group_instance)
|
||||
|
||||
for ldap_group in role_settings['ldap_groups']:
|
||||
if ldap_group.lower() in ldap_user.group_dns:
|
||||
user_groups.append(group_instance)
|
||||
|
||||
user.save()
|
||||
user.groups.set(user_groups)
|
||||
user.is_staff = user.is_superuser = user.groups.filter(name='admin').exists()
|
||||
|
||||
def create_user(sender, instance, created, **kwargs):
|
||||
if instance.is_superuser and instance.is_staff:
|
||||
admin_group, _ = Group.objects.get_or_create(name='admin')
|
||||
admin_group.user_set.add(instance)
|
||||
|
||||
if created:
|
||||
for cvat_role, _ in authentication.cvat_groups_definition.items():
|
||||
group_instance, _ = Group.objects.get_or_create(name=cvat_role)
|
||||
setup_group_permissions(group_instance)
|
||||
|
||||
if cvat_role in authentication.AUTH_SIMPLE_DEFAULT_GROUPS:
|
||||
instance.groups.add(group_instance)
|
||||
@ -1,27 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2018 Intel Corporation
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
{% extends "auth_base.html" %}
|
||||
|
||||
{% block title %}Login{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Login</h1>
|
||||
{% if form.errors %}
|
||||
<small>Your username and password didn't match. Please try again.</small>
|
||||
{% endif %}
|
||||
<form method="post" action="{% url 'login' %}">
|
||||
{% csrf_token %}
|
||||
{% for field in form %}
|
||||
{{ field }}
|
||||
{% endfor %}
|
||||
<input type="hidden" name="next" value="{{ next }}" />
|
||||
<button type="submit" class="btn btn-primary btn-block btn-large">Login</button>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
{% block note %}
|
||||
{% include "note.html" %}
|
||||
{% endblock %}
|
||||
@ -1,7 +0,0 @@
|
||||
<!--
|
||||
Copyright (C) 2018 Intel Corporation
|
||||
|
||||
SPDX-License-Identifier: MIT
|
||||
-->
|
||||
<p>
|
||||
</p>
|
||||
@ -0,0 +1,118 @@
|
||||
# Generated by Django 2.0.9 on 2018-11-07 12:25
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('engine', '0012_auto_20181025_1618'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name='attributespec',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='job',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='label',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='labeledboxattributeval',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='labeledpointsattributeval',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='labeledpolygonattributeval',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='labeledpolylineattributeval',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='objectpathattributeval',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='segment',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='task',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='trackedbox',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='trackedboxattributeval',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='trackedpoints',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='trackedpointsattributeval',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='trackedpolygon',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='trackedpolygonattributeval',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='trackedpolyline',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.AlterModelOptions(
|
||||
name='trackedpolylineattributeval',
|
||||
options={'default_permissions': ()},
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='job',
|
||||
old_name='annotator',
|
||||
new_name='assignee',
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='task',
|
||||
name='assignee',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='assignees', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='owner',
|
||||
field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owners', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='job',
|
||||
name='assignee',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='bug_tracker',
|
||||
field=models.CharField(blank=True, default='', max_length=2000),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='task',
|
||||
name='owner',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='owners', to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
Loading…
Reference in New Issue