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

@ -0,0 +1,21 @@
import json
from channels.generic.websocket import AsyncWebsocketConsumer
class EncryptedValueConsumer(AsyncWebsocketConsumer):
async def connect(self):
await self.accept()
async def receive(self, text_data):
data = json.loads(text_data)
decrypted_value = data['decrypted_value']
status = self.decryption_equals_X(decrypted_value)
await self.send(text_data=json.dumps({
'status': status
}))
def decryption_equals_X(self, x_decrypted):
return True

@ -1,6 +1,10 @@
from django import forms
from django.core.exceptions import ValidationError
from .models import File, RuleAttribute, AttributeType
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
from .models import File, RuleAttribute, AttributeType, TrustedAuthority
class FileUploadForm(forms.ModelForm):
class Meta:
@ -9,6 +13,7 @@ class FileUploadForm(forms.ModelForm):
class UploadCertificateForm(forms.Form):
signature = forms.FileField(label='Signature File')
certificate = forms.FileField()
class RuleAttributeForm(forms.ModelForm):
@ -17,3 +22,19 @@ class RuleAttributeForm(forms.ModelForm):
fields = ['attribute_type', 'operator', 'value']
attribute_type = forms.ModelChoiceField(queryset=AttributeType.objects.all())
class TrustedAuthorityForm(forms.ModelForm):
class Meta:
model = TrustedAuthority
fields = ['name', 'public_key']
def clean_public_key(self):
data = self.cleaned_data['public_key']
try:
# Try to load the RSA public key. If it's invalid, it'll raise an error
serialization.load_pem_public_key(data.encode(), backend=default_backend())
except ValueError:
raise ValidationError("Invalid RSA public key.")
return data

@ -0,0 +1,22 @@
# Generated by Django 4.2.5 on 2023-10-05 11:09
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('abac', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='user',
options={'verbose_name': 'user', 'verbose_name_plural': 'users'},
),
migrations.AlterField(
model_name='attribute',
name='value',
field=models.CharField(max_length=2048),
),
]

@ -0,0 +1,21 @@
# Generated by Django 4.2.5 on 2023-10-13 09:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('abac', '0002_alter_user_options_alter_attribute_value'),
]
operations = [
migrations.CreateModel(
name='TrustedAuthority',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255)),
('public_key', models.TextField()),
],
),
]

@ -25,11 +25,6 @@ class User(AbstractUser):
def deserialized_private_key(self):
return pickle.loads(b64decode(self.phe_private_key))
class Meta:
permissions = [
("can_create_users", "Can create new users"),
]
class AttributeType(models.Model):
DATATYPE_CHOICES = [
@ -59,7 +54,7 @@ class AttributeType(models.Model):
class Attribute(models.Model):
attribute_type = models.ForeignKey(AttributeType, on_delete=models.CASCADE)
value = models.IntegerField()
value = models.CharField(max_length=2048)
class UserAttribute(Attribute):
@ -129,3 +124,7 @@ class RuleAttribute(Attribute):
rule = models.ForeignKey(Rule, on_delete=models.CASCADE)
operator = models.CharField(max_length=3, choices=OPTION_CHOICES, default=OPTION_EQUALS)
class TrustedAuthority(models.Model):
name = models.CharField(max_length=255)
public_key = models.TextField()

@ -31,6 +31,8 @@
<button class="btn btn-primary" onclick="window.location.href='{% url 'abac:upload_file' %}'">Upload File</button>
{% if user.is_superuser %}
<a href="{% url 'abac:user_management' %}" class="btn btn-primary ml-2">User Management</a>
<a href="{% url 'abac:hierarchy_vis' %}" class="btn btn-primary ml-2">Rule Overview</a>
<a href="{% url 'abac:trusted_authorities' %}" class="btn btn-primary ml-2">Authorities</a>
{% endif %}
<span class="ml-auto">
<button class="btn btn-primary" onclick="window.location.href='{% url 'abac:upload_certificate' %}'">Upload Certificate</button>
@ -40,6 +42,18 @@
</header>
<main class="flex-shrink-0">
{% if messages %}
<div class="container mt-3">
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endfor %}
</div>
{% endif %}
<div class="container mt-4">
{% block content %}{% endblock %}
</div>

@ -0,0 +1,38 @@
{% extends "base.html" %}
{% load static %}
{% block content %}
<div class="container">
<h2>Trusted Authorities</h2>
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Public Key</th>
<th>Action</th>
</tr>
</thead>
<tbody>
{% for authority, hash in authorities_with_hashes %}
<tr>
<td>{{ authority.name }}</td>
<td>{{ hash }}</td>
<td>
<form action="{% url 'abac:delete_authority' authority.id %}" method="post">
{% csrf_token %}
<button type="submit" class="btn btn-danger">Delete</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<h3>Add New Authority</h3>
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit" class="btn btn-primary">Add</button>
</form>
</div>
{% endblock %}

@ -0,0 +1,18 @@
{% extends 'base.html' %}
{% block content %}
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<h2>Upload Public Key</h2>
<form method="post" class="mt-3">
{% csrf_token %}
<div class="form-group">
<label for="public_key">Paste your public key here:</label>
<textarea id="public_key" name="public_key" class="form-control" rows="6" required></textarea>
</div>
<button type="submit" class="btn btn-primary">Upload Public Key</button>
</form>
</div>
</div>
</div>
{% endblock %}

@ -2,6 +2,16 @@
{% block content %}
<h2>{{ user.username }}'s Attributes</h2>
{% if user.public_key %}
<h3 class="mt-4">Public Key</h3>
<pre class="bg-light p-3">{{ user.public_key }}</pre>
{% else %}
<div class="alert alert-warning mt-3">
<strong>Note:</strong> No public key uploaded.
<a href="{% url 'abac:upload_public_key' username=user.username %}" class="alert-link">Click here to upload.</a>
</div>
{% endif %}
<table class="table">
<thead>
<tr>
@ -30,4 +40,22 @@
{% endfor %}
</tbody>
</table>
<h3 class="mt-4">Upload Precertified Definitions</h3>
<form method="post" enctype="multipart/form-data" class="mb-3">
{% csrf_token %}
<div class="custom-file mb-3">
<input type="file" class="custom-file-input" id="precertifiedFile" name="precertified_file">
<label class="custom-file-label" for="precertifiedFile">Choose file</label>
</div>
<button type="submit" class="btn btn-primary">Upload</button>
</form>
<script>
// This script ensures that the file name is displayed in the custom file input after selection
document.querySelector('.custom-file-input').addEventListener('change', function (e) {
var fileName = document.getElementById("precertifiedFile").files[0].name;
var nextSibling = e.target.nextElementSibling;
nextSibling.innerText = fileName;
});
</script>
{% endblock %}

@ -0,0 +1,152 @@
{% extends "base.html" %}
{% block content %}
<div class="container">
<div class="tree-container col-md10"></div>
</div>
<!-- Include D3.js -->
<script src="https://d3js.org/d3.v6.min.js"></script>
<script>
data = {{data|safe}};
const width = 928;
const marginTop = 10;
const marginRight = 10;
const marginBottom = 10;
const marginLeft = 40;
// Rows are separated by dx pixels, columns by dy pixels. These names can be counter-intuitive
// (dx is a height, and dy a width). This because the tree must be viewed with the root at the
// “bottom”, in the data domain. The width of a column is based on the trees height.
const root = d3.hierarchy(data);
const dx = 20;
const dy = (width - marginRight - marginLeft) / (1 + root.height);
// Define the tree layout and the shape for links.
const tree = d3.tree().nodeSize([dx, dy]);
const diagonal = d3.linkHorizontal().x(d => d.y).y(d => d.x);
// Create the SVG container, a layer for the links and a layer for the nodes.
const svg = d3.select(".tree-container").append("svg")
.attr("width", width)
.attr("height", dx)
.attr("viewBox", [-marginLeft, -marginTop, width, dx])
.attr("style", "max-width: 100%; height: auto; font: 14px sans-serif; user-select: none;");
const gLink = svg.append("g")
.attr("fill", "none")
.attr("stroke", "#555")
.attr("stroke-opacity", 0.4)
.attr("stroke-width", 1.5);
const gNode = svg.append("g")
.attr("cursor", "pointer")
.attr("pointer-events", "all");
function update(event, source) {
const duration = event?.altKey ? 2500 : 250; // hold the alt key to slow down the transition
const nodes = root.descendants().reverse();
const links = root.links();
// Compute the new tree layout.
tree(root);
let left = root;
let right = root;
root.eachBefore(node => {
if (node.x < left.x) left = node;
if (node.x > right.x) right = node;
});
const height = right.x - left.x + marginTop + marginBottom;
const transition = svg.transition()
.duration(duration)
.attr("height", height)
.attr("viewBox", [-marginLeft, left.x - marginTop, width, height])
.tween("resize", window.ResizeObserver ? null : () => () => svg.dispatch("toggle"));
// Update the nodes…
const node = gNode.selectAll("g")
.data(nodes, d => d.id);
// Enter any new nodes at the parent's previous position.
const nodeEnter = node.enter().append("g")
.attr("transform", d => `translate(${source.y0},${source.x0})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0)
.on("click", (event, d) => {
d.children = d.children ? null : d._children;
update(event, d);
});
nodeEnter.append("circle")
.attr("r", 2.5)
.attr("fill", d => d._children ? "#555" : "#999")
.attr("stroke-width", 10);
nodeEnter.append("text")
.attr("dy", "0.31em")
.attr("x", d => d._children ? -6 : 6)
.attr("text-anchor", d => d._children ? "end" : "start")
.text(d => d.data.name)
.clone(true).lower()
.attr("stroke-linejoin", "round")
.attr("stroke-width", 3)
.attr("stroke", "white");
// Transition nodes to their new position.
const nodeUpdate = node.merge(nodeEnter).transition(transition)
.attr("transform", d => `translate(${d.y},${d.x})`)
.attr("fill-opacity", 1)
.attr("stroke-opacity", 1);
// Transition exiting nodes to the parent's new position.
const nodeExit = node.exit().transition(transition).remove()
.attr("transform", d => `translate(${source.y},${source.x})`)
.attr("fill-opacity", 0)
.attr("stroke-opacity", 0);
// Update the links…
const link = gLink.selectAll("path")
.data(links, d => d.target.id);
// Enter any new links at the parent's previous position.
const linkEnter = link.enter().append("path")
.attr("d", d => {
const o = {x: source.x0, y: source.y0};
return diagonal({source: o, target: o});
});
// Transition links to their new position.
link.merge(linkEnter).transition(transition)
.attr("d", diagonal);
// Transition exiting nodes to the parent's new position.
link.exit().transition(transition).remove()
.attr("d", d => {
const o = {x: source.x, y: source.y};
return diagonal({source: o, target: o});
});
// Stash the old positions for transition.
root.eachBefore(d => {
d.x0 = d.x;
d.y0 = d.y;
});
}
// Do the first update to the initial configuration of the tree — where a number of nodes
// are open (arbitrarily selected as the root, plus nodes with 7 letters).
root.x0 = dy / 2;
root.y0 = 0;
root.descendants().forEach((d, i) => {
d.id = i;
d._children = d.children;
if (d.depth && d.data.name.length !== 7) d.children = null;
});
update(null, root);
</script>
{% endblock %}

@ -15,7 +15,11 @@ from .views import (
user_detail_view,
delete_rule_attribute,
download_file,
user_management
user_management,
upload_public_key,
hierarchy_view,
trusted_authorities,
delete_authority
)
app_name = 'abac'
@ -34,4 +38,8 @@ urlpatterns = [
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'),
path('user/<str:username>/upload-public-key', upload_public_key, name='upload_public_key'),
path('visualization/hierarchical', hierarchy_view, name='hierarchy_vis'),
path('trusted-authorities', trusted_authorities, name="trusted_authorities"),
path('trusted-authorities/delete/<int:authority_id>/', delete_authority, name='delete_authority'),
]

@ -1,17 +1,23 @@
from django.shortcuts import render, redirect, get_object_or_404
from django.http.response import HttpResponseNotAllowed
from django.contrib.auth.decorators import permission_required, login_required, user_passes_test
from django.http import HttpResponse, FileResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponseNotAllowed
from django.http import HttpResponse, FileResponse, HttpResponseForbidden, HttpResponseBadRequest, HttpResponseNotAllowed, JsonResponse
from django.contrib.auth import logout
from django.urls import reverse
from django.contrib import messages
from django.core.exceptions import ValidationError
from django.utils import timezone
import hashlib
import json
from phe import paillier
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.backends import default_backend
from .forms import FileUploadForm, UploadCertificateForm, RuleAttributeForm
from .models import File, Rule, UserAttribute, RuleAttribute, AttributeType, User
from .forms import FileUploadForm, UploadCertificateForm, RuleAttributeForm, TrustedAuthorityForm
from .models import File, Rule, UserAttribute, RuleAttribute, AttributeType, User, TrustedAuthority
@login_required
@ -42,26 +48,62 @@ def create_user_view(request):
@login_required
def upload_certificate_view(request):
if request.method == 'POST':
uploaded_file = request.FILES.get('certificate')
form = UploadCertificateForm(request.POST, request.FILES)
if form.is_valid():
certificate = form.cleaned_data['certificate']
signature = form.cleaned_data['signature'].read()
is_verified = False
for authority in TrustedAuthority.objects.all():
pub_key = serialization.load_pem_public_key(
authority.public_key.encode(),
backend=default_backend()
)
try:
pub_key.verify(
signature,
certificate.read(),
padding.PSS(
mgf=padding.MGF1(hashes.SHA256()),
salt_length=padding.PSS.MAX_LENGTH
),
hashes.SHA256()
)
is_verified = True
break
except:
continue
if is_verified:
certificate_data = json.load(certificate)
if not uploaded_file:
return HttpResponseBadRequest('No file uploaded.')
if not request.user.public_key:
messages.warning(request, 'Please upload your public key first.')
return redirect(reverse('abac:upload_public_key', args=[request.user.username]))
public_key_native = paillier.PaillierPublicKey(n=int(request.user.public_key))
certificate_data = json.load(uploaded_file)
for attribute_data in certificate_data:
name = attribute_data.get('name')
value = attribute_data.get('value')
attribute_type = get_or_create_attribute_type(name, value, private=True)
encrypted_value = str(public_key_native.encrypt(value).ciphertext())
attribute, created = UserAttribute.objects.update_or_create(
user=request.user,
attribute_type=attribute_type,
defaults={'value': value, 'last_modified': timezone.now()}
defaults={'value': encrypted_value, 'last_modified': timezone.now()}
)
messages.success(request, 'Certificate uploaded successfully.')
return redirect('abac:user_details', username=request.user.username)
else:
messages.error(request, 'Certificate verification failed.')
else:
form = UploadCertificateForm()
@ -69,9 +111,14 @@ def upload_certificate_view(request):
@login_required
def file_detail(request, file_id):
user = request.user
file = get_object_or_404(File, id=file_id)
if check_permission(user, file):
rules = Rule.objects.filter(file=file)
return render(request, 'file_detail.html', {'file': file, 'rules': rules})
else:
return HttpResponseForbidden("You don't have the permission to access this file")
@login_required
def create_rule(request, file_id):
@ -123,7 +170,7 @@ def user_detail_view(request, username):
user = get_object_or_404(User, username=username)
# Check if the requested user is the same as the logged-in user or the logged-in user has the required permission
if user != request.user and not request.user.has_perm('abac.can_create_users'):
if user != request.user and not request.user.is_superuser:
return HttpResponseForbidden('You do not have permission to view this page.')
attributes = UserAttribute.objects.filter(user=user)
@ -165,23 +212,28 @@ def download_file(request, file_id):
user = request.user
for rule in file.rule_set.all():
if rule.is_satisfied_by(user):
return serve_file(file)
return HttpResponseForbidden('You do not have permission to download this file.')
def check_permission(user, file, mode = None):
if mode:
pass
else:
# a) Check if the user is the owner of the file.
if user == file.owner:
print("User is Owner")
return serve_file(file)
return True
# b) Check if the user is a superuser.
if user.is_superuser:
print("User is superuser")
return serve_file(file)
return True
# 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.')
return True
def serve_file(file):
# Serve the file using FileResponse.
@ -189,6 +241,7 @@ def serve_file(file):
response['Content-Disposition'] = f'attachment; filename="{file.name}"'
return response
@login_required
@user_passes_test(lambda u: u.is_superuser)
def user_management(request):
@ -207,3 +260,97 @@ def user_management(request):
users = User.objects.all().order_by('-is_superuser', 'username')
return render(request, 'user_management.html', {'users': users})
@login_required
def upload_public_key(request, username):
if request.method == 'POST':
public_key = request.POST.get('public_key')
request.user.public_key = public_key
request.user.save()
messages.success(request, 'Public key uploaded successfully.')
return redirect(reverse('abac:user_details', args=[username]))
return render(request, 'upload_public_key.html')
def get_hierarchy_data():
users = User.objects.all()
data = {
"name": "Users",
"children": []
}
for user in users:
user_data = {
'name': user.username,
'children': []
}
for file in user.file_set.all():
file_data = {
'name': file.name,
'children': []
}
for rule in file.rule_set.all():
rule_data = {
'name': rule.name,
'children': []
}
for rule_attribute in rule.ruleattribute_set.all():
rule_data['children'].append({
'name': f"{rule_attribute.attribute_type.name}; {rule_attribute.operator}; {rule_attribute.value}",
'children': []
})
file_data['children'].append(rule_data)
user_data['children'].append(file_data)
data['children'].append(user_data)
return json.dumps(data)
@login_required
@user_passes_test(lambda u: u.is_superuser)
def hierarchy_view(request):
context = {'data': get_hierarchy_data()}
return render(request, 'vis_tree.html', context=context)
@login_required
@user_passes_test(lambda u: u.is_superuser)
def trusted_authorities(request):
if request.method == "POST":
form = TrustedAuthorityForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, 'Authority successfully added!')
return redirect(reverse('abac:trusted_authorities'))
else:
messages.warning(request, 'Invalid RSA Key')
return redirect(reverse('abac:trusted_authorities'))
authorities = TrustedAuthority.objects.all()
authorities_with_hashes = [(authority, hashlib.sha256(authority.public_key.encode()).hexdigest()) for authority in authorities]
form = TrustedAuthorityForm()
context = {
'authorities_with_hashes': authorities_with_hashes,
'form': form
}
return render(request, 'trusted_authorities.html', context)
@login_required
@user_passes_test(lambda u: u.is_superuser)
def delete_authority(request, authority_id):
authority = get_object_or_404(TrustedAuthority, id=authority_id)
if request.method == 'POST':
authority.delete()
messages.success(request, 'Authority successfully deleted!')
return redirect(reverse('abac:trusted_authorities'))
else:
return redirect(reverse('abac:trusted_authorities'))

269670
hashlib

File diff suppressed because it is too large Load Diff

@ -0,0 +1,11 @@
from channels.routing import ProtocolTypeRouter, URLRouter
from django.urls import path
from abac import consumers
websocket_urlpatterns = [
path('ws/encrypted_value/', consumers.EncryptedValueConsumer.as_asgi()),
]
application = ProtocolTypeRouter({
'websocket': URLRouter(websocket_urlpatterns),
})

@ -32,6 +32,7 @@ ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'channels',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@ -71,6 +72,15 @@ TEMPLATES = [
WSGI_APPLICATION = 'mabac.wsgi.application'
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
ASGI_APPLICATION = 'mabac.routing.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases

Loading…
Cancel
Save