From 18971f43e057740d4758e4356ddd0c9dbafc06cc Mon Sep 17 00:00:00 2001 From: Gary <26419401+Gary-H9@users.noreply.github.com> Date: Mon, 1 Jul 2024 17:20:11 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=9B=82=20=20Add=20EntraID=20Authenticatio?= =?UTF-8?q?n=20(#4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * :rocket: Initialise project * :art: Add logout button * :wrench: Authentication via AzureAD * :wrench: -> --- .env.example | 7 +++ ollamate/settings.py | 51 ++++++++++++++++++- ollamate/urls.py | 5 +- .../templates/streamingapp/input_form.html | 20 +++++--- streamingapp/views.py | 20 ++++++-- 5 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3bb634c --- /dev/null +++ b/.env.example @@ -0,0 +1,7 @@ +CLIENT_ID= +CLIENT_SECRET= +AZURE_TENANT_ID= +REDIRECT_URI= + +#Β when running locally, you can use +REDIRECT_URI="https://127.0.0.1:8000/azure_auth/callback" \ No newline at end of file diff --git a/ollamate/settings.py b/ollamate/settings.py index fbf6a87..c02d8e7 100644 --- a/ollamate/settings.py +++ b/ollamate/settings.py @@ -12,10 +12,30 @@ from pathlib import Path +import os +import environ + +# Initialize environment variables +env = environ.Env() + +# Set the project base directory +BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + +# Read the .env file +env_file = os.path.join(BASE_DIR, '.env') +if os.path.isfile(env_file): + print(f"Loading environment variables from {env_file}") + environ.Env.read_env(env_file) +else: + print(f"{env_file} not found") + print("CLIENT_ID:", env("CLIENT_ID")) + print("CLIENT_SECRET:", env("CLIENT_SECRET")) + print("TENANT_ID:", env("AZURE_TENANT_ID")) + print("REDIRECT_URI:", env("REDIRECT_URI")) + # Build paths inside the project like this: BASE_DIR / 'subdir'. BASE_DIR = Path(__file__).resolve().parent.parent - # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/ @@ -37,7 +57,8 @@ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'streamingapp' + 'streamingapp', + 'azure_auth' ] MIDDLEWARE = [ @@ -122,3 +143,29 @@ # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' + +# Azure authentication settings + +AZURE_AUTH = { + "CLIENT_ID": env("CLIENT_ID"), + "CLIENT_SECRET": env("CLIENT_SECRET"), + "REDIRECT_URI": env("REDIRECT_URI"), + "SCOPES": ["User.Read"], + "AUTHORITY": "https://login.microsoftonline.com/{}".format(env("AZURE_TENANT_ID")), # Or https://login.microsoftonline.com/common if multi-tenant +# # "LOGOUT_URI": "https:///logout", # Optional +# # "PUBLIC_URLS": ["",], # Optional, public views accessible by non-authenticated users +# # "PUBLIC_PATHS": ['/go/',], # Optional, public paths accessible by non-authenticated users +# # "ROLES": { +# # "95170e67-2bbf-4e3e-a4d7-e7e5829fe7a7": "GroupName1", +# # "3dc6539e-0589-4663-b782-fef100d839aa": "GroupName2" +# # }, # Optional, will add user to django group if user is in EntraID group + "USERNAME_ATTRIBUTE": "mail", # The AAD attribute or ID token claim you want to use as the value for the user model `USERNAME_FIELD` +# # "EXTRA_FIELDS": [], # Optional, extra AAD user profile attributes you want to make available in the user mapping function +# "USER_MAPPING_FN": "azure_auth.tests.misc.user_mapping_fn", # Optional, path to the function used to map the AAD to Django attributes +} +LOGIN_URL = "/azure_auth/login" +LOGIN_REDIRECT_URL = "/" +LOGOUT_REDIRECT_URL = '/' + +AUTHENTICATION_BACKENDS = ("azure_auth.backends.AzureBackend",) + diff --git a/ollamate/urls.py b/ollamate/urls.py index e78bb25..b37c2b0 100644 --- a/ollamate/urls.py +++ b/ollamate/urls.py @@ -16,8 +16,11 @@ """ from django.contrib import admin from django.urls import path, include +from streamingapp.views import redirect_to_ollama urlpatterns = [ path('admin/', admin.site.urls), - path('stream/', include('streamingapp.urls')) + path("azure_auth/", include("azure_auth.urls"),), + path('stream/', include('streamingapp.urls')), + path('', redirect_to_ollama) ] diff --git a/streamingapp/templates/streamingapp/input_form.html b/streamingapp/templates/streamingapp/input_form.html index 841b007..76409ca 100644 --- a/streamingapp/templates/streamingapp/input_form.html +++ b/streamingapp/templates/streamingapp/input_form.html @@ -1,4 +1,3 @@ - @@ -43,25 +42,30 @@

A Proof of Concept by the Analytical Platform

- -

Ollamate πŸ¦™πŸ΅

A Simple Conversational AI.

-

Enter text below to interact with Ollamate:

- @@ -129,4 +133,4 @@

A Simple Conversational AI.

}); - + \ No newline at end of file diff --git a/streamingapp/views.py b/streamingapp/views.py index 2ebde88..9d3a2be 100644 --- a/streamingapp/views.py +++ b/streamingapp/views.py @@ -1,5 +1,6 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect from django.http import JsonResponse +from azure_auth.decorators import azure_auth_required import requests import logging import json @@ -7,10 +8,16 @@ # Configure logging logging.basicConfig(level=logging.DEBUG) +@azure_auth_required def call_ollama(request): if request.method == 'POST': user_input = request.POST.get('userInput', '') - conversation_history = json.loads(user_input) + try: + conversation_history = json.loads(user_input) + except json.JSONDecodeError as e: + logging.error("JSONDecodeError: %s", e) + return JsonResponse({"error": "Invalid input format"}, status=400) + url = 'http://localhost:11434/api/chat' headers = {'Content-Type': 'application/json'} data = { @@ -18,19 +25,26 @@ def call_ollama(request): "messages": conversation_history, "stream": False } + logging.debug("Sending data to Ollama API: %s", json.dumps(data, indent=2)) + try: response = requests.post(url, json=data, headers=headers) response.raise_for_status() response_data = response.json() logging.debug("Response data: %s", json.dumps(response_data, indent=2)) - # Extract the actual response message + ollama_response = response_data.get("message", {}).get("content", "") if not ollama_response: logging.error("Empty response from Ollama API") + return JsonResponse({"error": "Empty response from Ollama API"}, status=500) + return JsonResponse({"response": ollama_response}) except requests.RequestException as e: logging.error("RequestException: %s", e) return JsonResponse({"error": str(e)}, status=500) else: return render(request, 'streamingapp/input_form.html') + +def redirect_to_ollama(request): + return redirect('/stream/call-ollama') \ No newline at end of file