diff --git a/isle/api.py b/isle/api.py index 2be367d..d780a41 100644 --- a/isle/api.py +++ b/isle/api.py @@ -256,3 +256,6 @@ class SSOApi(BaseApi): def push_user_to_uploads(self, user_id): return self.make_request_no_pagination('/api/push-user-to-uploads/', method='POST', json={'unti_id': user_id}) + + def get_casbin_data(self): + return self.make_request_no_pagination('/api/casbin/') diff --git a/isle/casbin.py b/isle/casbin.py new file mode 100644 index 0000000..123cb95 --- /dev/null +++ b/isle/casbin.py @@ -0,0 +1,36 @@ +import logging +from casbin import persist +from casbin.enforcer import Enforcer +from casbin.model import Model +from casbin.persist.adapter import Adapter +from .models import CasbinData + + +class TextAdapter(Adapter): + def __init__(self, policy): + self.policy = policy + + def load_policy(self, model): + for line in self.policy.splitlines(): + if not line.strip(): + continue + persist.load_policy_line(line.strip(), model) + + +def get_enforcer(): + data = CasbinData.objects.first() + if data: + m = Model() + m.load_model_from_text(data.model) + return Enforcer(m, TextAdapter(data.policy)) + + +def enforce(sub, ctx, obj_type, action): + try: + enforcer = get_enforcer() + if not enforcer: + return False + return enforcer.enforce(sub, ctx, obj_type, action) + except Exception: + logging.exception('Enforcer failed') + return False diff --git a/isle/context_processors.py b/isle/context_processors.py index 63d3fae..3b80322 100644 --- a/isle/context_processors.py +++ b/isle/context_processors.py @@ -3,7 +3,7 @@ def context(request): contexts = [] - if request.user.is_authenticated and request.user.is_assistant: + if request.user.is_authenticated and request.user.has_assistant_role(): contexts = ((i[0], i[1] or i[2] or i[3]) for i in Context.objects.values_list('id', 'title', 'guid', 'uuid')) return { 'AVAILABLE_CONTEXTS': contexts, diff --git a/isle/forms.py b/isle/forms.py index a64de7e..bd1af03 100644 --- a/isle/forms.py +++ b/isle/forms.py @@ -33,7 +33,8 @@ def get_instance(self): instance = super().get_instance() instance.event = self.event instance.creator = self.creator - instance.confirmed = self.creator.is_assistant + instance.confirmed = self.creator.is_assistant_for_context(self.event.context) + instance.created_by_assistant = instance.confirmed return instance diff --git a/isle/kafka.py b/isle/kafka.py index 6dda2ad..1e7781e 100644 --- a/isle/kafka.py +++ b/isle/kafka.py @@ -7,6 +7,7 @@ from django_carrier_client.helpers import MessageManagerHelper from isle.api import SSOApi, ApiError from isle.models import LabsUserResult, LabsTeamResult +from isle.utils import update_casbin_data message_manager = MessageManager( @@ -103,4 +104,24 @@ def _handle_for_id(self, obj_id, action): logging.error('Got wrong object id from kafka: %s' % obj_id) +class CasbinPolicyListener(KafkaBaseListener): + topic = settings.KAFKA_TOPIC_SSO + actions = (KafkaActions.CREATE, KafkaActions.DELETE) + msg_type = 'casbin_policy' + + def _handle_for_id(self, obj_id, action): + update_casbin_data() + + +class CasbinModelListener(KafkaBaseListener): + topic = settings.KAFKA_TOPIC_SSO + actions = (KafkaActions.CREATE, KafkaActions.UPDATE) + msg_type = 'casbin_model' + + def _handle_for_id(self, obj_id, action): + update_casbin_data() + + MessageManagerHelper.set_manager_to_listen(SSOUserChangeListener()) +MessageManagerHelper.set_manager_to_listen(CasbinPolicyListener()) +MessageManagerHelper.set_manager_to_listen(CasbinModelListener()) diff --git a/isle/migrations/0045_auto_20190508_1930.py b/isle/migrations/0045_auto_20190508_1930.py new file mode 100644 index 0000000..e360934 --- /dev/null +++ b/isle/migrations/0045_auto_20190508_1930.py @@ -0,0 +1,52 @@ +# Generated by Django 2.0.7 on 2019-05-08 09:30 + +from django.db import migrations, models + + +def set_assistant_flag(apps, schema_editor): + """ + проставление флажков для существующих файлов и команд о том, что они были созданы ассистентами + """ + EventMaterial = apps.get_model('isle', 'EventMaterial') + EventTeamMaterial = apps.get_model('isle', 'EventTeamMaterial') + Team = apps.get_model('isle', 'Team') + User = apps.get_model('isle', 'User') + assistants = list(User.objects.filter(unti_id__isnull=False, is_assistant=True).values_list('unti_id', flat=True)) + EventMaterial.objects.filter(initiator__in=assistants).update(loaded_by_assistant=True) + EventTeamMaterial.objects.filter(initiator__in=assistants).update(loaded_by_assistant=True) + assistants = list(User.objects.filter(is_assistant=True).values_list('id', flat=True)) + Team.objects.filter(creator_id__in=assistants).update(created_by_assistant=True) + + +class Migration(migrations.Migration): + + dependencies = [ + ('isle', '0044_auto_20190328_0215'), + ] + + operations = [ + migrations.CreateModel( + name='CasbinData', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('model', models.TextField()), + ('policy', models.TextField()), + ], + ), + migrations.AddField( + model_name='eventmaterial', + name='loaded_by_assistant', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='eventteammaterial', + name='loaded_by_assistant', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='team', + name='created_by_assistant', + field=models.BooleanField(default=False), + ), + migrations.RunPython(set_assistant_flag, reverse_code=migrations.RunPython.noop) + ] diff --git a/isle/models.py b/isle/models.py index 3aa9d81..db248fa 100644 --- a/isle/models.py +++ b/isle/models.py @@ -18,6 +18,13 @@ from jsonfield import JSONField +def check_permission(user, context, obj_type='file', action='upload'): + from .casbin import enforce + if not user.unti_id or not context: + return False + return enforce(str(user.unti_id), context, obj_type, action) + + class User(AbstractUser): second_name = models.CharField(max_length=50) icon = JSONField() @@ -40,6 +47,16 @@ def fio(self): def get_full_name(self): return ' '.join(filter(None, [self.last_name, self.first_name])) + def is_assistant_for_context(self, context): + return check_permission(self, context and context.uuid) + + def has_assistant_role(self): + return any(check_permission(self, c) for c in Context.objects.values_list('uuid', flat=True)) + + @cached_property + def available_context_uuids(self): + return [c for c in Context.objects.values_list('uuid', flat=True) if check_permission(self, c)] + class EventType(models.Model): ext_id = models.PositiveIntegerField(verbose_name='Внешний id', null=True) @@ -137,9 +154,6 @@ def _get_dt(self, dt_field_name): tz = default return val.astimezone(tz) - def is_author(self, user): - return user.is_assistant - def get_traces(self): traces = self.trace_set.order_by('name') if not traces and self.event_type is not None: @@ -523,6 +537,7 @@ class EventMaterial(AbstractMaterial): comment = models.CharField(default='', max_length=255) result = models.ForeignKey(UserResult, on_delete=models.CASCADE, null=True, default=None) result_v2 = models.ForeignKey('LabsUserResult', on_delete=models.SET_NULL, null=True, default=None) + loaded_by_assistant = models.BooleanField(default=False) class Meta: verbose_name = _(u'Материал') @@ -563,6 +578,7 @@ class Team(models.Model): name = models.CharField(max_length=500, verbose_name='Название команды') creator = models.ForeignKey(User, on_delete=models.CASCADE, null=True, default=None, related_name='team_creator') confirmed = models.BooleanField(default=True) + created_by_assistant = models.BooleanField(default=False) @property def team_name(self): @@ -580,7 +596,8 @@ def __str__(self): return self.name def user_can_edit_team(self, user): - return user.is_assistant or user.id == self.creator_id or user in self.users.all() + return user.is_assistant_for_context(self.event.context) or user.id == self.creator_id or \ + user in self.users.all() class EventTeamMaterial(AbstractMaterial): @@ -590,6 +607,7 @@ class EventTeamMaterial(AbstractMaterial): owners = models.ManyToManyField(User) result = models.ForeignKey(TeamResult, on_delete=models.CASCADE, null=True, default=None) result_v2 = models.ForeignKey('LabsTeamResult', on_delete=models.CASCADE, null=True, default=None) + loaded_by_assistant = models.BooleanField(default=False) class Meta: verbose_name = _(u'Материал команды') @@ -886,3 +904,8 @@ def current_generations_for_user(cls, user): def get_download_link(self): return reverse('load_csv_dump', kwargs={'dump_id': self.id}) + + +class CasbinData(models.Model): + model = models.TextField() + policy = models.TextField() diff --git a/isle/static/js/pages/index.js b/isle/static/js/pages/index.js index 84d1266..70a71e4 100644 --- a/isle/static/js/pages/index.js +++ b/isle/static/js/pages/index.js @@ -42,6 +42,21 @@ $('span.sort-col').on('click', (e) => { window.location.replace(queryStringUrlReplacement(window.location.href, 'sort', isAsc ? 'asc': 'desc')); }); +$('#select-view-mode').on('change', (e) => { + setCookie('index-view-mode', $(e.target).val(), 1); + window.location.reload(); +}); + +function setCookie(name,value,days) { + var expires = ""; + if (days) { + var date = new Date(); + date.setTime(date.getTime() + (days*24*60*60*1000)); + expires = "; expires=" + date.toUTCString(); + } + document.cookie = name + "=" + (value || "") + expires + "; path=/"; +} + function queryStringUrlReplacement(url, param, value) { const re = new RegExp(`[\\?&]${param}=([^&#]*)`, "i"); const match = re.exec(url); diff --git a/isle/templates/activities.html b/isle/templates/activities.html index 8e04fbf..0f742b2 100644 --- a/isle/templates/activities.html +++ b/isle/templates/activities.html @@ -18,7 +18,7 @@

Активности

{% endcomment %} - {% if request.user.is_assistant %} + {% if is_assistant %}  Экспортировать все @@ -71,7 +71,7 @@

Активности

- {% if request.user.is_assistant %} + {% if is_assistant %} {% endif %} {% endblock %} \ No newline at end of file diff --git a/isle/templates/base.html b/isle/templates/base.html index 8faf13e..e3b04e5 100644 --- a/isle/templates/base.html +++ b/isle/templates/base.html @@ -72,7 +72,7 @@ {% block js %} - {% if request.user.is_assistant %} + {% if request.user.has_assistant_role %} {% endif %} {% endblock %} diff --git a/isle/templates/base_results_upload.html b/isle/templates/base_results_upload.html index 8c7cbc8..1418299 100644 --- a/isle/templates/base_results_upload.html +++ b/isle/templates/base_results_upload.html @@ -133,7 +133,7 @@

{% blocktrans with n1=block.order n2=result.order %}{{ n1 }}.{{ n2 }}. {% en {% endif %} - {% if request.user.is_assistant %} + {% if is_assistant %} {% endif %} @@ -178,7 +178,7 @@

Результ
+{% endif %} diff --git a/isle/templates/load_materials.html b/isle/templates/load_materials.html index 255210c..0b4af71 100644 --- a/isle/templates/load_materials.html +++ b/isle/templates/load_materials.html @@ -61,7 +61,7 @@

{% trans "Добавление материалов" %}

- {% if request.user.is_assistant and event_upload %} + {% if is_assistant and event_upload %}