Skip to content

Commit

Permalink
SSO-39 Casbin. Демонстрация
Browse files Browse the repository at this point in the history
  • Loading branch information
nosanity committed May 8, 2019
1 parent baf5829 commit 6e8d6b5
Show file tree
Hide file tree
Showing 20 changed files with 326 additions and 106 deletions.
3 changes: 3 additions & 0 deletions isle/api.py
Expand Up @@ -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/')
36 changes: 36 additions & 0 deletions 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
2 changes: 1 addition & 1 deletion isle/context_processors.py
Expand Up @@ -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,
Expand Down
3 changes: 2 additions & 1 deletion isle/forms.py
Expand Up @@ -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


Expand Down
21 changes: 21 additions & 0 deletions isle/kafka.py
Expand Up @@ -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(
Expand Down Expand Up @@ -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())
52 changes: 52 additions & 0 deletions 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)
]
31 changes: 27 additions & 4 deletions isle/models.py
Expand Up @@ -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()
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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'Материал')
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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'Материал команды')
Expand Down Expand Up @@ -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()
15 changes: 15 additions & 0 deletions isle/static/js/pages/index.js
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions isle/templates/activities.html
Expand Up @@ -18,7 +18,7 @@ <h2>Активности</h2>
</div>
{% endcomment %}
</div>
{% if request.user.is_assistant %}
{% if is_assistant %}
<a class="pull-right export_event_csv" href="{% url 'get_activities_csv' %}">
<span class="glyphicon glyphicon-download-alt"></span>&nbsp;Экспортировать все
</a>
Expand Down Expand Up @@ -71,7 +71,7 @@ <h2>Активности</h2>
<!-- event handlers of buttons are binded in js file below -->
<script type="text/javascript" src="{% static 'js/pages/activities.js' %}"></script>
<script type="text/javascript" src="{% static 'js/pages/index.js' %}"></script>
{% if request.user.is_assistant %}
{% if is_assistant %}
<script type="text/javascript" src="{% static 'js/pages/index_assistant.js' %}"></script>
{% endif %}
{% endblock %}
2 changes: 1 addition & 1 deletion isle/templates/base.html
Expand Up @@ -72,7 +72,7 @@
{% block js %}
<script type="text/javascript" src="{% static 'js/libs/jquery.min.js' %}"></script>
<script type="text/javascript" src="{% static 'js/libs/bootstrap.min.js' %}"></script>
{% if request.user.is_assistant %}
{% if request.user.has_assistant_role %}
<script type="text/javascript" src="{% static 'js/pages/base.js' %}"></script>
{% endif %}
{% endblock %}
Expand Down
10 changes: 5 additions & 5 deletions isle/templates/base_results_upload.html
Expand Up @@ -133,7 +133,7 @@ <h4>{% blocktrans with n1=block.order n2=result.order %}{{ n1 }}.{{ n2 }}. {% en
{% endif %}
<span class="glyphicon glyphicon-pencil result-action-buttons pull-right edit-result-comment"></span>
<span data-url="{{ result_item.get_page_url }}" class="glyphicon glyphicon-eye-open result-action-buttons pull-right view-result-page"></span>
{% if request.user.is_assistant %}
{% if is_assistant %}
<span class="glyphicon glyphicon-move result-action-buttons pull-right move-deleted-result"></span>
{% endif %}
</div>
Expand Down Expand Up @@ -178,7 +178,7 @@ <h5 class="result-entry" id="result-entry-{{ forloop.counter }}">Результ
<div data-result="{{ result.result.id }}">
<ul id="trace-ul" class="list-group list-group-flush">
{% for link in result.links %}
<li class="list-group-item {% if link.confirmed and team_upload and not link.initiator_user.is_assistant %}confirmed-team-link{% elif team_upload and link.initiator_user.is_assistant %}assistant-team-link{% endif %}">
<li class="list-group-item {% if link.confirmed and team_upload and not link.loaded_by_assistant %}confirmed-team-link{% elif team_upload and link.initiator_user.is_assistant %}assistant-team-link{% endif %}">
<a class="link_preview" href="{{ link.get_url }}" {{ link.render_metadata|safe }}>{{ link.get_name }}</a>&nbsp;
{% if can_upload %}
<button name="material_id" value="{{ link.id }}" class="btn btn-warning btn-sm pull-right delete-material-btn">
Expand All @@ -199,7 +199,7 @@ <h4 class="mt-60">Отдельные файлы</h4>

<ul class="list-group list-group-flush">
{% for link in links %}
<li class="list-group-item {% if link.confirmed and team_upload and not link.initiator_user.is_assistant %}confirmed-team-link{% elif team_upload and link.initiator_user.is_assistant %}assistant-team-link{% endif %}">
<li class="list-group-item {% if link.confirmed and team_upload and not link.loaded_by_assistant %}confirmed-team-link{% elif team_upload and link.initiator_user.is_assistant %}assistant-team-link{% endif %}">
<a href="{{ link.get_url }}" {{ link.render_metadata|safe }}>{{ link.get_name }}</a>&nbsp;
{% if can_upload %}
<button name="material_id" value="{{ link.id }}" class="btn btn-warning btn-sm pull-right delete-material-btn">
Expand All @@ -217,7 +217,7 @@ <h4 class="mt-60">Отдельные файлы</h4>
{% blocktrans %}Удалить{% endblocktrans %}
</button>
{% endif %}
{% if request.user.is_assistant %}
{% if is_assistant %}
<span class="glyphicon glyphicon-move result-action-buttons pull-right move-unattached-file" data-file-id="{{ link.id }}"></span>
{% endif %}
<div><span>{{ link.comment }}</span></div>
Expand All @@ -231,7 +231,7 @@ <h4 class="mt-60">Отдельные файлы</h4>
{{ block.super }}
<script type="text/javascript">
const pageType = "loadMaterials_v2";
const isAssistant = eval("{{ request.user.is_assistant|lower }}");
const isAssistant = eval("{{ is_assistant|lower }}");
const eventUpload = eval("{{ event_upload|lower }}");
const teamUpload = eval("{{ team_upload|lower }}");
const fromUser = eval("{{ user_upload|lower }}");
Expand Down
2 changes: 1 addition & 1 deletion isle/templates/create_blocks.html
Expand Up @@ -93,7 +93,7 @@ <h5>Файл разметки модуля</h5>
<button name="material_id" value="{{ link.id }}" class="btn btn-warning btn-sm pull-right delete-material-btn">
{% blocktrans %}Удалить{% endblocktrans %}
</button>
{% if not request.user.is_assistant %}<div><span>{{ link.comment }}</span></div>{% endif %}
{% if not is_assistant %}<div><span>{{ link.comment }}</span></div>{% endif %}
</li>
{% endfor %}
<li class="list-group-item form-inline list-group-border">
Expand Down

0 comments on commit 6e8d6b5

Please sign in to comment.