diff --git a/.env b/.env index 35b39b6..2e51029 100644 --- a/.env +++ b/.env @@ -13,6 +13,7 @@ MQTT_USER=alex MQTT_PASSWORD=batt1l0 MQTT_DS_TOPIC="$CONTROL/dynamic-security/v1" MQTT_DS_RESP_TOPIC="$CONTROL/dynamic-security/v1/response" +MQTT_PWDX=AsE -GMAIL_USER=alessandro.battilani@gmail.com -GMAIL_PASSWORD=batmanu#171017 \ No newline at end of file +GMAIL_USER= +GMAIL_PASSWORD= \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json index 1561c79..aa86391 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,18 @@ { "taipyStudio.config.mainPythonFile": "manage.py", - "taipyStudio.gUI.elementsFilePaths": [] + "taipyStudio.gUI.elementsFilePaths": [], + "rest-client.environmentVariables": { + "$shared": { + "version": "v1", + "prodToken": "foo", + "nonProdToken": "bar" + }, + "test": { + "version": "v2", + "host": "localhost" + }, + "production": { + "host": "aaaaa", + } + } } \ No newline at end of file diff --git a/Test_API_wallet.http b/Test_API_wallet.http new file mode 100644 index 0000000..6d7e090 --- /dev/null +++ b/Test_API_wallet.http @@ -0,0 +1,16 @@ +POST http://{{host}}:8000/get/ HTTP/1.1 +Content-Type: application/json + +{ + "master_password": "pipperepettenuse", + "site": "enertec_ase_site51" +} + +#### + +POST http://{{host}}:8000/list/ HTTP/1.1 +Content-Type: application/json + +{ + "master_password": "pipperepettenuse" +} \ No newline at end of file diff --git a/aseusers/templates/aseusers/profile_edit.html b/aseusers/templates/aseusers/profile_edit.html index cffc157..ae6854c 100644 --- a/aseusers/templates/aseusers/profile_edit.html +++ b/aseusers/templates/aseusers/profile_edit.html @@ -1,54 +1,86 @@ -{% extends 'layouts/box.html' %} {% block content %} {% if onboarding %} -

Complete your Profile

-{% else %} -

Edit your Profile

-{% endif %} +{% extends 'layouts/box.html' %} {% block content %} -
- -
-

{{ user.profile.displayname|default:"" }}

-
@{{ user.username }}
+
+
+
+
+
+ {% if onboarding %} +

Complete your Profile

+ {% else %} +

Edit your Profile

+ {% endif %} + +
+ Avatar +
+

+ {{ user.profile.displayname|default:"" }} +

+
@{{ user.username }}
+
+
+ +
+ {% csrf_token %} {% for field in form %} +
+ + {{ field }} {% if field.errors %} +
+ {% for error in field.errors %} {{ error }} {% endfor %} +
+ {% endif %} +
+ {% endfor %} +
+ + {% if onboarding %} + Skip + {% else %} + Cancel + {% endif %} +
+
+
+
+
-
- {% csrf_token %} {{ form.as_p }} - - {% if onboarding %} - Skip - {% else %} - Cancel - {% endif %} -
- diff --git a/static/images/android-chrome-192x192.png b/static/images/android-chrome-192x192.png new file mode 100644 index 0000000..2fa5927 Binary files /dev/null and b/static/images/android-chrome-192x192.png differ diff --git a/static/images/android-chrome-512x512.png b/static/images/android-chrome-512x512.png new file mode 100644 index 0000000..2fa5927 Binary files /dev/null and b/static/images/android-chrome-512x512.png differ diff --git a/static/images/apple-touch-icon.png b/static/images/apple-touch-icon.png new file mode 100644 index 0000000..2fa5927 Binary files /dev/null and b/static/images/apple-touch-icon.png differ diff --git a/static/images/favicon-16x16.png b/static/images/favicon-16x16.png new file mode 100644 index 0000000..ad825f0 Binary files /dev/null and b/static/images/favicon-16x16.png differ diff --git a/static/images/favicon-32x32.png b/static/images/favicon-32x32.png new file mode 100644 index 0000000..2fa5927 Binary files /dev/null and b/static/images/favicon-32x32.png differ diff --git a/static/images/favicon.ico b/static/images/favicon.ico new file mode 100644 index 0000000..5351937 Binary files /dev/null and b/static/images/favicon.ico differ diff --git a/static/images/site.webmanifest b/static/images/site.webmanifest new file mode 100644 index 0000000..8ea790c --- /dev/null +++ b/static/images/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "MQTT Ase User Management", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index d9351dc..39d539a 100644 --- a/templates/base.html +++ b/templates/base.html @@ -1,17 +1,45 @@ -{% load static %} -{% load django_htmx %} +{% load static %} {% load django_htmx %} - - - - - Project Title - + + + + + MQTT Ase User Management + + + + + + - - + + @@ -19,18 +47,13 @@ - - - - {% include 'includes/header.html' %} - - {% include 'includes/messages.html' %} - - {% block layout %} - {% endblock %} - - {% block javascript %}{% endblock %} - - \ No newline at end of file + + + {% include 'includes/header.html' %} {% include 'includes/messages.html' %} + {% block layout %} {% endblock %} {% block javascript %}{% endblock %} + + diff --git a/templates/layouts/box.html b/templates/layouts/box.html index bd2c4db..3fd12ae 100644 --- a/templates/layouts/box.html +++ b/templates/layouts/box.html @@ -1,9 +1,9 @@ {% extends 'base.html' %} {% block layout %} -
+
-
-
- {% block content %} {% endblock %} +
+
+
{% block content %} {% endblock %}
diff --git a/templates/wallet_api/create_user.html b/templates/wallet_api/create_user.html index 626cd50..6299a6a 100644 --- a/templates/wallet_api/create_user.html +++ b/templates/wallet_api/create_user.html @@ -1,49 +1,49 @@ {% extends 'layouts/blank.html' %} {% block content %} -

Add User

-
- {% csrf_token %} -
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- -
+

Add User

+
+ {% csrf_token %} +
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
- -
+ +
{% endblock content%} diff --git a/templates/wallet_api/edit_user.html b/templates/wallet_api/edit_user.html index af44514..cfe1664 100644 --- a/templates/wallet_api/edit_user.html +++ b/templates/wallet_api/edit_user.html @@ -48,5 +48,7 @@ />
+ + {% endblock content %} diff --git a/templates/wallet_api/list_users.html b/templates/wallet_api/list_users.html index 64e56e4..9cf9e29 100644 --- a/templates/wallet_api/list_users.html +++ b/templates/wallet_api/list_users.html @@ -14,7 +14,10 @@ Username Client ID Topic - Action + {% if ase_adm_group %} + Actions + + {% endif %} @@ -28,31 +31,22 @@ {{user.client_id}} {{user.topic}} - + {% if ase_adm_group %} + {% if user.status == "enabled" %} - - - + Disable + {% else %} - - - + Enable + {% endif %} + + + + {% endif %} {% empty %} diff --git a/wallet/settings.py b/wallet/settings.py index 6181e75..db94342 100644 --- a/wallet/settings.py +++ b/wallet/settings.py @@ -26,9 +26,9 @@ SECRET_KEY = 'django-insecure-=-1c582(r8ly33q+-ljg!xb2)#$+a0ykedc4zcj-dqh&i$4$l3 # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -ALLOWED_HOSTS = ['localhost', '127.0.0.1', '*'] +ALLOWED_HOSTS = [ 'localhost', '127.0.0.1'] -CSRF_TRUSTED_ORIGINS = [ 'https://*' ] +CSRF_TRUSTED_ORIGINS = [ 'https://*', 'http://*' ] # Application definition @@ -44,6 +44,7 @@ INSTALLED_APPS = [ # 'django.contrib.sites', 'allauth', 'allauth.account', + 'allauth.socialaccount', 'django_htmx', # My apps diff --git a/wallet_api/urls.py b/wallet_api/urls.py index 801dfad..7155aaa 100644 --- a/wallet_api/urls.py +++ b/wallet_api/urls.py @@ -9,6 +9,7 @@ urlpatterns = [ path('user/list/', views.list_users, name='list_users'), path('user/create_user/', views.create_user, name='create_user'), path('user/edit/', views.edit_user, name='edit_user'), + path('user/delete/', views.delete_user, name='delete_user'), path('user/disable/', views.disable_user, name='disable_user'), path('user/enable/', views.enable_user, name='enable_user'), path('user/role/', views.view_role, name='view_role'), diff --git a/wallet_api/utils_new.py b/wallet_api/utils_new.py deleted file mode 100644 index f1a1684..0000000 --- a/wallet_api/utils_new.py +++ /dev/null @@ -1,76 +0,0 @@ -import bcrypt -import base64 -import hashlib -import json -from cryptography.fernet import Fernet -from .models import MasterHash -from django.http import JsonResponse - -# 1. Salva l'hash della master password e la chiave principale cifrata -def save_master_data(hashed_password, encrypted_data_key): - entry, created = MasterHash.objects.get_or_create(id="1") - entry.hash = hashed_password - entry.encrypted_data_key = encrypted_data_key - entry.save() - -# 2. Carica i dati della master password -def load_master_data(): - try: - entry = MasterHash.objects.get(id="1") - return entry.hash, entry.encrypted_data_key - except Exception: - return None, None - -# 3. Autenticazione della master password -def authenticate(master_password): - stored_hash, encrypted_data_key = load_master_data() - if stored_hash is None: - hashed_password = bcrypt.hashpw(master_password.encode(), bcrypt.gensalt()) - key = Fernet.generate_key() # Genera una chiave principale - derived_key = derive_key(master_password) # Deriva la chiave dalla master password - encrypted_data_key = encrypt_password(key.decode(), derived_key) # Cifra la chiave principale - save_master_data(hashed_password, encrypted_data_key) - return True, key - - # Controlla se la password inserita è corretta - if bcrypt.checkpw(master_password.encode(), stored_hash.tobytes()): - derived_key = derive_key(master_password) - decrypted_data_key = decrypt_password(encrypted_data_key, derived_key) # Decifra la chiave principale - return True, decrypted_data_key.encode() - - return False, None - -# 4. Funzione per cambiare la master password -def change_master_password(old_password, new_password): - authenticated, data_key = authenticate(old_password) - if not authenticated: - return False # Fallisce se la vecchia password non è corretta - - # Deriviamo la nuova chiave dalla nuova master password - new_derived_key = derive_key(new_password) - - # Cifriamo la chiave principale con la nuova chiave derivata - new_encrypted_data_key = encrypt_password(data_key.decode(), new_derived_key) - - # Creiamo il nuovo hash della nuova master password - new_hashed_password = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()) - - # Aggiorniamo il database con i nuovi dati - save_master_data(new_hashed_password, new_encrypted_data_key) - - return True # Cambio password riuscito - -# 5. Deriva una chiave da una password -def derive_key(master_password): - hash = hashlib.sha256(master_password.encode()).digest() - return base64.urlsafe_b64encode(hash) - -# 6. Cifra una password con una chiave -def encrypt_password(password, key): - cipher = Fernet(key) - return cipher.encrypt(password.encode()).decode() - -# 7. Decifra una password con una chiave -def decrypt_password(encrypted_password, key): - cipher = Fernet(key) - return cipher.decrypt(encrypted_password.encode()).decode() diff --git a/wallet_api/views.py b/wallet_api/views.py index 2e24440..dbe6638 100644 --- a/wallet_api/views.py +++ b/wallet_api/views.py @@ -1,8 +1,8 @@ from django.shortcuts import render, redirect, get_object_or_404 from django.contrib import messages from django.http import JsonResponse, HttpResponse -from django.views.decorators.csrf import csrf_exempt -from django.contrib.auth.decorators import login_required, permission_required +from django.views.decorators.csrf import csrf_exempt, csrf_protect +from django.contrib.auth.decorators import login_required import json import paho.mqtt.client as mqtt @@ -16,13 +16,51 @@ from .mqtt_service import MosquittoDynamicSecurity config = dotenv_values(".env") -# authenticate(config['MASTER_PASSWORD']) +@csrf_exempt +def list_sites_api(request): + if request.method == 'POST': + data = json.loads(request.body) + master_password = data.get('master_password') + + if not authenticate(master_password): + return JsonResponse({"error": "Master password errata"}, status=403) + + sites = PasswordEntry.objects.values_list('site', flat=True) + return JsonResponse({"sites": list(sites)}) + return JsonResponse({"error": "Richiesta non valida"}, status=400) + +@csrf_exempt +def get_password_api(request): + if request.method == 'POST': + data = json.loads(request.body) + master_password = data.get('master_password') + site = data.get('site') + + if not authenticate(master_password): + return JsonResponse({"error": "Master password errata"}, status=403) + + key = derive_key(master_password) + try: + entry = PasswordEntry.objects.get(site=site) + decrypted_password = decrypt_password(entry.password, key) + return JsonResponse({ + "site": entry.site, + "username": entry.username, + "password": decrypted_password, + "client_id": entry.client_id, + "topic": entry.topic + }) + except PasswordEntry.DoesNotExist: + return JsonResponse({"error": "Sito non trovato"}, status=404) + return JsonResponse({"error": "Richiesta non valida"}, status=400) + +@login_required def home_view(request): return redirect('list_users') # Reindirizza alla lista degli utenti #return render(request, 'home.html') -@csrf_exempt +@login_required def publish_message(request): request_data = json.loads(request.body) publish.single(topic=request_data['topic'], @@ -58,6 +96,57 @@ def create_user(request): "username": username, "password": password } + ], + "commands": [ + { + "command": "createRole", + "rolename": f'{username}_role', + "textname": "", + "textdescription": "", + "acls": [ + { "acltype": "publishClientSend", "topic": topic, "priority": 0, "allow": True } + ] + }, + { + "command": "createRole", + "rolename": f'{username}_ase_role', + "textname": "", + "textdescription": "", + "acls": [ + { "acltype": "publishClientSend", "topic": topic, "priority": 0, "allow": True }, + { "acltype": "publishClientReceive", "topic": topic, "priority": 0, "allow": True }, + { "acltype": "subscribeLiteral", "topic": topic, "priority": 0, "allow": True }, + { "acltype": "subscribePattern", "topic": topic, "priority": 0, "allow": True }, + { "acltype": "unsubscribeLiteral", "topic": topic, "priority": 0, "allow": True }, + { "acltype": "unsubscribePattern", "topic": topic, "priority": 0, "allow": True } + ] + }, + { + "command": "createClient", + "username": username, + "password": password, + "clientid": client_id, + "textname": f'{username} subscriber', + "textdescription": f'{username} subscriber', + "groups": [ + ], + "roles": [ + { "rolename": f'{username}_role', "priority": 0 } + ] + }, + { + "command": "createClient", + "username": f'{username}_ase', + "password": f'{password}{config["MQTT_PWDX"]}', + "clientid": f'{client_id}_ase', + "textname": f'{username} ASE subscriber', + "textdescription": f'{username} ASE subscriber', + "groups": [ + ], + "roles": [ + { "rolename": f'{username}_ase_role', "priority": 0 } + ] + } ] } @@ -68,6 +157,7 @@ def create_user(request): key = derive_key(config['MASTER_PASSWORD']) encrypted_password = encrypt_password(password, key) + encrypted_password_ase = encrypt_password(f'{password}_ase', key) PasswordEntry.objects.create( site=site, @@ -78,6 +168,15 @@ def create_user(request): status='enabled' ) + PasswordEntry.objects.create( + site=f'{site}_ase', + username=f'{username}_ase', + password=encrypted_password_ase, + client_id=f'{client_id}_ase', + topic=topic, + status='enabled' + ) + messages.success(request, 'Utente creato con successo!') # Messaggio di successo return redirect('list_users') # Reindirizza alla lista degli utenti else: @@ -96,9 +195,39 @@ def edit_user(request, slug): user = PasswordEntry.objects.filter(slug=slug).first() return render(request, 'wallet_api/edit_user.html', {'user': user}) else: - messages.success(request, 'Non hai i permessi per creare utenti MQTT!') # Messaggio di successo + messages.success(request, 'Non hai i permessi per cancellare utenti MQTT!') # Messaggio di successo return redirect('list_users') # Reindirizza alla lista degli utenti +@login_required +def delete_user(request, slug): + if request.user.groups.filter(name='ase_admin').exists(): + user = PasswordEntry.objects.filter(slug=slug).first() + command = { + "commands": + [ + { + "command": "deleteClient", + "username": user.username + }, + { + "command": "deleteRole", + "rolename": f'{user.username}_role' + } + ] + } + + # Invia il comando a Mosquitto + mqtt_service = MosquittoDynamicSecurity() + response = mqtt_service.send_command(command) + if "error" not in response["responses"][0]: + result = PasswordEntry.objects.filter(id=user.id).delete() + print(result) + messages.success(request, f'Utente {user.username} eliminato!') # Messaggio di successo + return redirect('list_users') # Reindirizza alla lista degli utenti + else: + messages.success(request, 'Non hai i permessi per cancellare utenti MQTT!') # Messaggio di successo + return redirect('list_users') # Reindirizza alla lista degli utenti + @login_required def disable_user(request, slug): if request.user.groups.filter(name='ase_admin').exists(): @@ -164,43 +293,9 @@ def enable_user(request, slug): messages.success(request, 'Non hai i permessi per disabilitare utenti MQTT!') # Messaggio di successo return redirect('list_users') # Reindirizza alla lista degli utenti +@login_required def view_role(request, role): return render(request, 'wallet_api/role_info.html', {'role': role }) -@csrf_exempt -def get_password_api(request): - if request.method == 'POST': - data = json.loads(request.body) - master_password = data.get('master_password') - site = data.get('site') - if not authenticate(master_password): - return JsonResponse({"error": "Master password errata"}, status=403) - key = derive_key(master_password) - try: - entry = PasswordEntry.objects.get(site=site) - decrypted_password = decrypt_password(entry.password, key) - return JsonResponse({ - "site": entry.site, - "username": entry.username, - "password": decrypted_password, - "client_id": entry.client_id, - "topic": entry.topic - }) - except PasswordEntry.DoesNotExist: - return JsonResponse({"error": "Sito non trovato"}, status=404) - return JsonResponse({"error": "Richiesta non valida"}, status=400) - -@csrf_exempt -def list_sites_api(request): - if request.method == 'POST': - data = json.loads(request.body) - master_password = data.get('master_password') - - if not authenticate(master_password): - return JsonResponse({"error": "Master password errata"}, status=403) - - sites = PasswordEntry.objects.values_list('site', flat=True) - return JsonResponse({"sites": list(sites)}) - return JsonResponse({"error": "Richiesta non valida"}, status=400)