Update Private user attributes

main
Malte Kerl 1 year ago
parent 92546024d8
commit ac52768ca7
Signed by: malte
GPG Key ID: EF4BC804CE1AB1D5

@ -1,4 +1,4 @@
# Generated by Django 4.2.5 on 2023-09-28 12:45 # Generated by Django 4.2.5 on 2023-09-28 16:35
from django.conf import settings from django.conf import settings
import django.contrib.auth.models import django.contrib.auth.models
@ -72,6 +72,9 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)), ('name', models.CharField(max_length=255)),
('read', models.BooleanField(default=False)),
('write', models.BooleanField(default=False)),
('delete', models.BooleanField(default=False)),
('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='abac.file')), ('file', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='abac.file')),
], ],
), ),
@ -80,7 +83,6 @@ class Migration(migrations.Migration):
fields=[ fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('value', models.IntegerField()), ('value', models.IntegerField()),
('last_modified', models.DateTimeField(auto_now=True)),
('attribute_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='abac.attributetype')), ('attribute_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='abac.attributetype')),
], ],
), ),
@ -88,6 +90,7 @@ class Migration(migrations.Migration):
name='UserAttribute', name='UserAttribute',
fields=[ fields=[
('attribute_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='abac.attribute')), ('attribute_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='abac.attribute')),
('last_modified', models.DateTimeField(auto_now=True)),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
], ],
bases=('abac.attribute',), bases=('abac.attribute',),
@ -96,6 +99,7 @@ class Migration(migrations.Migration):
name='RuleAttribute', name='RuleAttribute',
fields=[ fields=[
('attribute_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='abac.attribute')), ('attribute_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='abac.attribute')),
('operator', models.CharField(choices=[('EQ', 'Equals'), ('NEQ', "Doesn't Equal"), ('GT', 'Greater Than'), ('LT', 'Less Than')], default='EQ', max_length=3)),
('rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='abac.rule')), ('rule', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='abac.rule')),
], ],
bases=('abac.attribute',), bases=('abac.attribute',),

@ -1,42 +0,0 @@
# Generated by Django 4.2.5 on 2023-09-28 13:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('abac', '0001_initial'),
]
operations = [
migrations.RemoveField(
model_name='attribute',
name='last_modified',
),
migrations.AddField(
model_name='rule',
name='delete',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='rule',
name='read',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='rule',
name='write',
field=models.BooleanField(default=False),
),
migrations.AddField(
model_name='ruleattribute',
name='option',
field=models.CharField(choices=[('EQ', 'Equals'), ('NEQ', "Doesn't Equal"), ('GT', 'Greater Than'), ('LT', 'Less Than')], default='EQ', max_length=3),
),
migrations.AddField(
model_name='userattribute',
name='last_modified',
field=models.DateTimeField(auto_now=True),
),
]

@ -1,18 +0,0 @@
# Generated by Django 4.2.5 on 2023-09-28 14:49
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('abac', '0002_remove_attribute_last_modified_rule_delete_rule_read_and_more'),
]
operations = [
migrations.RenameField(
model_name='ruleattribute',
old_name='option',
new_name='operator',
),
]

@ -86,6 +86,34 @@ class Rule(models.Model):
write = models.BooleanField(default=False) write = models.BooleanField(default=False)
delete = models.BooleanField(default=False) delete = models.BooleanField(default=False)
def is_satisfied_by(self, user):
for rule_attribute in self.ruleattribute_set.all():
try:
user_attribute = UserAttribute.objects.get(
user=user,
attribute_type=rule_attribute.attribute_type,
)
except UserAttribute.DoesNotExist:
# The user does not have this attribute; the rule is not satisfied.
return False
if not self.attribute_satisfies_rule(user_attribute, rule_attribute):
return False
return True
def attribute_satisfies_rule(self, user_attribute, rule_attribute):
operator = rule_attribute.operator
if operator == 'EQ':
return user_attribute.value == rule_attribute.value
elif operator == 'NEQ':
return not (user_attribute.value == rule_attribute.value)
elif operator == 'GT':
return user_attribute.value > rule_attribute.value
elif operator == 'LT':
return user_attribute.value < rule_attribute.value
return False
class RuleAttribute(Attribute): class RuleAttribute(Attribute):
OPTION_EQUALS = 'EQ' OPTION_EQUALS = 'EQ'
OPTION_DOES_NOT_EQUAL = 'NEQ' OPTION_DOES_NOT_EQUAL = 'NEQ'

@ -29,8 +29,8 @@
{% if user.is_authenticated %} {% if user.is_authenticated %}
<nav class="navbar navbar-expand-lg navbar-light bg-light"> <nav class="navbar navbar-expand-lg navbar-light bg-light">
<button class="btn btn-primary" onclick="window.location.href='{% url 'abac:upload_file' %}'">Upload File</button> <button class="btn btn-primary" onclick="window.location.href='{% url 'abac:upload_file' %}'">Upload File</button>
{% if perms.abac.can_create_users %} {% if user.is_superuser %}
<button class="btn btn-primary ml-2">User Management</button> <a href="{% url 'abac:user_management' %}" class="btn btn-primary ml-2">User Management</a>
{% endif %} {% endif %}
<span class="ml-auto"> <span class="ml-auto">
<button class="btn btn-primary" onclick="window.location.href='{% url 'abac:upload_certificate' %}'">Upload Certificate</button> <button class="btn btn-primary" onclick="window.location.href='{% url 'abac:upload_certificate' %}'">Upload Certificate</button>

@ -17,12 +17,12 @@
</thead> </thead>
<tbody> <tbody>
{% for file in files %} {% for file in files %}
<tr data-url="{% url 'abac:file_detail' file_id=file.id %}"> <!-- Add data-url attribute here --> <tr data-url="{% url 'abac:file_detail' file_id=file.id %}">
<td>{{ file.name }}</td> <td>{{ file.name }}</td>
<td>{{ file.owner.username }}</td> <td>{{ file.owner.username }}</td>
<td>{{ file.created_at|date:"F d, Y H:i" }}</td> <td>{{ file.created_at|date:"F d, Y H:i" }}</td>
<td> <td>
<a href="{{ file.file.url }}" download="{{ file.name }}" class="btn btn-outline-primary btn-sm"> <a href="{% url 'abac:download_file' file.id %}" class="btn btn-outline-primary btn-sm">
<i class="bi bi-download"></i> Download <i class="bi bi-download"></i> Download
</a> </a>
</td> </td>

@ -0,0 +1,45 @@
{% extends 'base.html' %}
{% block title %}User Management{% endblock %}
{% block content %}
<form method="post" class="mb-3">
{% csrf_token %}
<div class="form-row align-items-center">
<div class="col-auto">
<label class="sr-only" for="username">Username</label>
<input type="text" class="form-control mb-2" id="username" name="username" placeholder="Username" required>
</div>
<div class="col-auto">
<label class="sr-only" for="password">Password</label>
<input type="password" class="form-control mb-2" id="password" name="password" placeholder="Password" required>
</div>
<div class="col-auto">
<div class="form-check mb-2">
<input class="form-check-input" type="checkbox" id="is_superuser" name="is_superuser">
<label class="form-check-label" for="is_superuser">Superuser</label>
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-2">Create User</button>
</div>
</div>
</form>
<table class="table table-striped">
<thead>
<tr>
<th>Username</th>
<th>Is Superuser</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td><a href="{% url 'abac:user_details' username=user.username %}">{{ user.username }}</a></td>
<td>{{ user.is_superuser|yesno:"Yes,No" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}

@ -5,7 +5,6 @@ from django.urls import path
from .views import ( from .views import (
landing_page, landing_page,
login_view,
logout_view, logout_view,
upload_file_view, upload_file_view,
create_user_view, create_user_view,
@ -15,6 +14,8 @@ from .views import (
rule_detail, rule_detail,
user_detail_view, user_detail_view,
delete_rule_attribute, delete_rule_attribute,
download_file,
user_management
) )
app_name = 'abac' app_name = 'abac'
@ -31,4 +32,6 @@ urlpatterns = [
path('rules/<int:file_id>/<int:rule_id>/',rule_detail, name='rule_detail'), path('rules/<int:file_id>/<int:rule_id>/',rule_detail, name='rule_detail'),
path('user/<str:username>/', user_detail_view, name='user_details'), path('user/<str:username>/', user_detail_view, name='user_details'),
path('rules/<int:file_id>/<int:rule_id>/delete/<int:rule_attribute_id>/', delete_rule_attribute, name='delete_rule_attribute'), path('rules/<int:file_id>/<int:rule_id>/delete/<int:rule_attribute_id>/', delete_rule_attribute, name='delete_rule_attribute'),
path('downloader/<int:file_id>/', download_file, name='download_file'),
path('user_management/', user_management, name='user_management'),
] ]

@ -1,11 +1,9 @@
from django.shortcuts import render, redirect, get_object_or_404 from django.shortcuts import render, redirect, get_object_or_404
from django.http.response import HttpResponseNotAllowed from django.http.response import HttpResponseNotAllowed
from django.contrib.auth.decorators import permission_required, login_required from django.contrib.auth.decorators import permission_required, login_required, user_passes_test
from django.http import HttpResponse, JsonResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponseNotAllowed from django.http import HttpResponse, FileResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponseNotAllowed
from django.contrib.auth import logout from django.contrib.auth import logout
from django.urls import reverse from django.urls import reverse
from django.views.generic.detail import SingleObjectMixin, DetailView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib import messages from django.contrib import messages
from django.core.exceptions import ValidationError from django.core.exceptions import ValidationError
from django.utils import timezone from django.utils import timezone
@ -15,27 +13,12 @@ import json
from .forms import FileUploadForm, UploadCertificateForm, RuleAttributeForm from .forms import FileUploadForm, UploadCertificateForm, RuleAttributeForm
from .models import File, Rule, UserAttribute, RuleAttribute, AttributeType, User from .models import File, Rule, UserAttribute, RuleAttribute, AttributeType, User
def create_user(request):
special_user = request.user
if special_user.has_perm('abac.can_create_users'):
pass #TODO: Create new User
else:
# Return a response indicating insufficient permissions
return HttpResponseNotAllowed(request)
@permission_required('abac.can_create_users', raise_exception=True)
def create_user_view(request):
# Your view logic here
return HttpResponse('New user created')
@login_required @login_required
def landing_page(request): def landing_page(request):
files = File.objects.all() files = File.objects.all()
return render(request, 'landing_page.html', {'files': files}) return render(request, 'landing_page.html', {'files': files})
def login_view(request):
return HttpResponse('Login View')
def logout_view(request): def logout_view(request):
logout(request) logout(request)
return redirect('abac:login') return redirect('abac:login')
@ -46,9 +29,9 @@ def upload_file_view(request):
form = FileUploadForm(request.POST, request.FILES) form = FileUploadForm(request.POST, request.FILES)
if form.is_valid(): if form.is_valid():
file_instance = form.save(commit=False) file_instance = form.save(commit=False)
file_instance.owner = request.user # Assign the logged-in user as the owner of the uploaded file. file_instance.owner = request.user
file_instance.save() file_instance.save()
return redirect('abac:home') # Redirect to the landing page after a successful file upload. return redirect('abac:home')
else: else:
form = FileUploadForm() form = FileUploadForm()
return render(request, 'file_upload.html', {'form': form}) return render(request, 'file_upload.html', {'form': form})
@ -69,8 +52,6 @@ def upload_certificate_view(request):
for attribute_data in certificate_data: for attribute_data in certificate_data:
name = attribute_data.get('name') name = attribute_data.get('name')
value = attribute_data.get('value') value = attribute_data.get('value')
# Assuming you have a method to get or create AttributeType
attribute_type = get_or_create_attribute_type(name, value, private=True) attribute_type = get_or_create_attribute_type(name, value, private=True)
attribute, created = UserAttribute.objects.update_or_create( attribute, created = UserAttribute.objects.update_or_create(
@ -167,7 +148,8 @@ def get_or_create_attribute_type(name, value, private=False):
# Try to get the existing AttributeType object with matching name and datatype. # Try to get the existing AttributeType object with matching name and datatype.
attribute_type, created = AttributeType.objects.get_or_create( attribute_type, created = AttributeType.objects.get_or_create(
name=name, name=name,
datatype=datatype datatype=datatype,
defaults={'is_private': private}
) )
if not created and attribute_type.datatype != datatype: if not created and attribute_type.datatype != datatype:
@ -175,3 +157,53 @@ def get_or_create_attribute_type(name, value, private=False):
attribute_type = AttributeType.objects.create(name=name, datatype=datatype, is_private=private) attribute_type = AttributeType.objects.create(name=name, datatype=datatype, is_private=private)
return attribute_type return attribute_type
@login_required
def download_file(request, file_id):
file = get_object_or_404(File, id=file_id)
user = request.user
# a) Check if the user is the owner of the file.
if user == file.owner:
print("User is Owner")
return serve_file(file)
# b) Check if the user is a superuser.
if user.is_superuser:
print("User is superuser")
return serve_file(file)
# c) Check the user's attributes against the file's rules.
for rule in file.rule_set.all():
if rule.is_satisfied_by(user):
print(f"user satisfies rule {rule.name}")
return serve_file(file)
return HttpResponseForbidden('You do not have permission to download this file.')
def serve_file(file):
# Serve the file using FileResponse.
response = FileResponse(file.file.open('rb'))
response['Content-Disposition'] = f'attachment; filename="{file.name}"'
return response
@login_required
@user_passes_test(lambda u: u.is_superuser)
def user_management(request):
if request.method == "POST":
username = request.POST.get('username')
password = request.POST.get('password')
is_superuser = 'is_superuser' in request.POST # The checkbox for superuser will be in request.POST if checked.
if not username or not password:
messages.error(request, 'Username and password are required.')
else:
User.objects.create_user(username=username, password=password, is_superuser=is_superuser)
messages.success(request, f'User {username} created successfully.')
return redirect('abac:user_management')
users = User.objects.all().order_by('-is_superuser', 'username')
return render(request, 'user_management.html', {'users': users})

Loading…
Cancel
Save