Update Private user attributes
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
45
abac/templates/user_management.html
Normal file
45
abac/templates/user_management.html
Normal file
@@ -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})
|
||||||
|
|||||||
Reference in New Issue
Block a user