Harmony/main/views.py

262 lines
14 KiB
Python
Raw Normal View History

2024-03-19 11:07:56 +00:00
import requests
2024-03-20 12:25:07 +00:00
from django.shortcuts import get_object_or_404
2024-03-19 11:07:56 +00:00
import logging
from djangoProject1 import settings
2024-02-19 05:43:38 +00:00
from .models import Category, Product
from cart.forms import CartAddProductForm
2024-03-19 11:07:56 +00:00
from jose import jwt
from django.contrib import messages
2024-03-20 12:25:07 +00:00
import jwt
from django.http import JsonResponse
2024-02-19 05:43:38 +00:00
2024-03-19 11:07:56 +00:00
2024-02-19 05:43:38 +00:00
def product_detail(request, id, slug):
product = get_object_or_404(Product,
id=id,
slug=slug,
available=True)
cart_product_form = CartAddProductForm()
return render(request,
'main/product/detail.html',
{'product': product,
2024-03-05 11:23:18 +00:00
'cart_product_form': cart_product_form})
# Функция для страницы "О нас"
def about(request):
2024-03-14 10:25:32 +00:00
return render(request, 'main/about.html')
2024-03-19 11:07:56 +00:00
2024-03-20 12:25:07 +00:00
#Получение access token пользователя
2024-03-19 11:07:56 +00:00
def exchange_code_for_token(authorization_code, redirect_uri):
token_endpoint = 'https://auth.myterior.kz/realms/Harmony/protocol/openid-connect/token'
client_id = 'admin-cli'
client_secret = 'wOVphEiLVBS1AlNKRpaQpD4yQh5Wm3TJ'
payload = {
'grant_type': 'authorization_code',
'code': authorization_code,
'client_id': client_id,
'client_secret': client_secret,
'redirect_uri': redirect_uri,
}
response = requests.post(token_endpoint, data=payload)
print(response.status_code)
if response.status_code == 200:
tokens = response.json()
access_token = tokens.get('access_token')
return access_token
else:
print("Failed to exchange code: HTTP status", response.status_code)
print("Response body:", response.text)
raise Exception('Failed to exchange authorization code for tokens')
2024-03-20 12:25:07 +00:00
#Через access_token получать данные о пользователе декодировав его в json
2024-03-19 11:07:56 +00:00
def get_user_info(access_token):
userinfo_endpoint = 'https://auth.myterior.kz/realms/Harmony/protocol/openid-connect/userinfo'
headers = {'Authorization': f'Bearer {access_token}'}
response = requests.get(userinfo_endpoint, headers=headers)
if response.status_code == 200:
return response.json()
else:
print("Failed to fetch user info: HTTP status", response.status_code)
return None
2024-03-20 12:25:07 +00:00
2024-03-19 11:07:56 +00:00
def decode_access_token(access_token):
payload = jwt.decode(access_token, options={"verify_signature": False})
return payload
2024-03-20 12:25:07 +00:00
def callback(request, category_slug=None):
2024-03-19 11:07:56 +00:00
context = {}
2024-03-20 12:25:07 +00:00
# Получение кода авторизации из запроса.
# где клиент получает код после аутентификации пользователя на сервере.
2024-03-19 11:07:56 +00:00
authorization_code = request.GET.get('code')
if authorization_code:
try:
2024-03-20 12:25:07 +00:00
# Обмен кода на access_token с помощью функции exchange_code_for_token.
# Этот токен необходим для доступа к защищенным ресурсам (например, к информации о пользователе)
# на сервере аутентификации.
2024-03-20 10:19:34 +00:00
access_token = exchange_code_for_token(authorization_code, settings.KEYCLOAK_CONFIG['CALLBACK_URL'])
2024-03-20 12:25:07 +00:00
# Декодирование access_token для получения информации о пользователе.
# Это может включать имя пользователя, email и другие данные, в зависимости от настроек сервера.
2024-03-19 11:07:56 +00:00
user_info = decode_access_token(access_token)
2024-03-20 12:25:07 +00:00
# Сохранение полученной информации о пользователе в сессии.
# Это позволяет использовать данные пользователя в дальнейшем, не обращаясь к серверу аутентификации.
2024-03-19 11:07:56 +00:00
request.session['user_info'] = user_info
2024-03-20 12:25:07 +00:00
# Добавление информации о пользователе в контекст для отображения на странице.
2024-03-19 11:07:56 +00:00
context['userinfo'] = request.session.get('user_info', {})
except Exception as e:
2024-03-20 12:25:07 +00:00
# В случае ошибки (например, проблемы с обменом кода или декодированием токена)
# добавляем информацию об ошибке в контекст.
2024-03-19 11:07:56 +00:00
context['error'] = str(e)
else:
2024-03-20 12:25:07 +00:00
# Если код авторизации не найден в запросе, сообщаем об этом через контекст.
2024-03-19 11:07:56 +00:00
context['error'] = "Authorization code not found."
2024-03-20 12:25:07 +00:00
# Загрузка всех категорий и продуктов для отображения на странице.
# Если указан category_slug, фильтруем продукты по данной категории.
2024-03-19 11:07:56 +00:00
category = None
categories = Category.objects.all()
products = Product.objects.filter(available=True)
if category_slug:
2024-03-20 12:25:07 +00:00
category = get_object_or_404(Category, slug=category_slug)
2024-03-19 11:07:56 +00:00
products = products.filter(category=category)
2024-03-20 12:25:07 +00:00
# Рендеринг страницы с продуктами, передавая информацию о категориях, продуктах и контекст с данными пользователя или ошибками.
return render(request, 'main/product/list.html',
{'category': category, 'categories': categories, 'products': products})
2024-03-19 11:07:56 +00:00
from django.shortcuts import render, redirect
def profile(request):
2024-03-20 12:25:07 +00:00
# Проверка метода запроса: если POST, значит пользователь отправил форму для обновления своих данных.
2024-03-19 11:07:56 +00:00
if request.method == 'POST':
2024-03-20 12:25:07 +00:00
# Получение текущей информации о пользователе из сессии.
2024-03-19 11:07:56 +00:00
user_info = request.session.get('user_info', {})
2024-03-20 12:25:07 +00:00
# Обновление информации о пользователе данными из формы.
# Если какое-то поле не было предоставлено, используется текущее значение из сессии.
2024-03-19 11:07:56 +00:00
user_info['given_name'] = request.POST.get('first_name', user_info.get('given_name', ''))
user_info['family_name'] = request.POST.get('last_name', user_info.get('family_name', ''))
user_info['preferred_username'] = request.POST.get('username', user_info.get('preferred_username', ''))
user_info['email'] = request.POST.get('email', user_info.get('email', ''))
2024-03-20 12:25:07 +00:00
# Сохранение обновленной информации о пользователе обратно в сессию.
2024-03-19 11:07:56 +00:00
request.session['user_info'] = user_info
2024-03-20 12:25:07 +00:00
# После обновления информации редиректим пользователя на страницу callback,
# которая, скорее всего, отображает информацию о профиле или подтверждение об обновлении данных.
2024-03-19 11:07:56 +00:00
return render(request, 'main/callback.html')
else:
2024-03-20 12:25:07 +00:00
# Если метод запроса не POST, пользователь просто посещает страницу профиля,
# мы загружаем текущую информацию о пользователе из сессии и передаем ее в контекст шаблона.
2024-03-19 11:07:56 +00:00
context = {'userinfo': request.session.get('user_info', {})}
2024-03-20 12:25:07 +00:00
# Отображаем страницу профиля пользователя, передавая информацию о пользователе в контекст.
2024-03-19 11:07:56 +00:00
return render(request, 'main/callback.html', context)
2024-03-20 12:25:07 +00:00
# логирование
2024-03-19 11:07:56 +00:00
logger = logging.getLogger(__name__)
def get_keycloak_admin_token():
2024-03-20 12:25:07 +00:00
# Формируем данные для запроса токена
2024-03-19 11:07:56 +00:00
data = {
2024-03-20 12:25:07 +00:00
'client_id': settings.KEYCLOAK_CONFIG['CLIENT_ID'], # ID клиента
'client_secret': settings.KEYCLOAK_CONFIG['CLIENT_SECRET'], # Секрет клиента
'grant_type': 'client_credentials', # Тип авторизации
2024-03-19 11:07:56 +00:00
}
2024-03-20 12:25:07 +00:00
# Отправка запроса для получения токена администратора Keycloak
2024-03-19 11:07:56 +00:00
response = requests.post(
f"{settings.KEYCLOAK_CONFIG['SERVER_URL']}/realms/{settings.KEYCLOAK_CONFIG['REALM']}/protocol/openid-connect/token",
data=data
)
2024-03-20 12:25:07 +00:00
# Возвращаем токен доступа из ответа
return response.json()['access_token']
2024-03-19 11:07:56 +00:00
def get_user_sessions(admin_token, user_id):
2024-03-20 12:25:07 +00:00
# Установка заголовков с токеном администратора для авторизации запроса
2024-03-19 11:07:56 +00:00
headers = {'Authorization': f'Bearer {admin_token}'}
2024-03-20 12:25:07 +00:00
# Запрос на получение списка сессий пользователя в Keycloak
2024-03-19 11:07:56 +00:00
response = requests.get(
f"{settings.KEYCLOAK_CONFIG['SERVER_URL']}/admin/realms/{settings.KEYCLOAK_CONFIG['REALM']}/users/{user_id}/sessions",
headers=headers
)
2024-03-20 12:25:07 +00:00
# Проверка успешности запроса и возвращение результата
2024-03-19 11:07:56 +00:00
if response.status_code == 200:
return response.json()
else:
logger.error(f"Error fetching user sessions: {response.status_code}")
return []
2024-03-20 12:25:07 +00:00
def logout_user(admin_token, session_id):
# Установка заголовков с токеном администратора для авторизации запроса
headers = {'Authorization': f'Bearer {admin_token}'}
# Отправка запроса на удаление сессии пользователя по ID сессии
2024-03-19 11:07:56 +00:00
response = requests.delete(
f"{settings.KEYCLOAK_CONFIG['SERVER_URL']}/admin/realms/{settings.KEYCLOAK_CONFIG['REALM']}/sessions/{session_id}",
headers=headers
)
2024-03-20 12:25:07 +00:00
# Проверка успешности запроса на удаление
2024-03-19 11:07:56 +00:00
return response.status_code == 204
2024-03-20 12:25:07 +00:00
2024-03-19 11:07:56 +00:00
def get_user_id_from_session(request):
2024-03-20 12:25:07 +00:00
# Получение информации о пользователе из сессии
2024-03-19 11:07:56 +00:00
user_info = request.session.get('user_info', {})
2024-03-20 12:25:07 +00:00
user_id = user_info.get('sub') # 'sub' обычно используется как идентификатор пользователя
# Проверка валидности формата ID пользователя
2024-03-19 11:07:56 +00:00
if isinstance(user_id, str):
return user_id
else:
logger.error(f"Invalid user ID format in session: {user_id}")
return None
2024-03-20 12:25:07 +00:00
def admin_logout_user(request, category_slug=None):
# Получение ID пользователя из сессии текущего запроса.
user_id = get_user_id_from_session(request)
2024-03-19 11:07:56 +00:00
2024-03-20 12:25:07 +00:00
# Получение административного токена для обращения к Keycloak API.
2024-03-19 11:07:56 +00:00
admin_token = get_keycloak_admin_token()
if not admin_token:
2024-03-20 12:25:07 +00:00
# Логгирование ошибки, если не удалось получить токен администратора.
2024-03-19 11:07:56 +00:00
logger.error("Can't obtain admin token.")
2024-03-20 12:25:07 +00:00
# Отображение сообщения об ошибке пользователю.
2024-03-19 11:07:56 +00:00
messages.error(request, "Can't obtain admin token for logout.")
2024-03-20 12:25:07 +00:00
# Перенаправление на страницу выхода/авторизации в случае ошибки.
2024-03-19 11:07:56 +00:00
return redirect(settings.KEYCLOAK_CONFIG['LOGOUT_REDIRECT_URL'])
2024-03-20 12:25:07 +00:00
# Получение списка активных сессий пользователя через Keycloak API.
2024-03-19 11:07:56 +00:00
sessions = get_user_sessions(admin_token, user_id)
if sessions is None or not sessions:
2024-03-20 12:25:07 +00:00
# Логгирование, если сессии не найдены.
2024-03-19 11:07:56 +00:00
logger.error("Can't find user sessions.")
2024-03-20 12:25:07 +00:00
# Уведомление пользователя о невозможности найти активные сессии.
2024-03-19 11:07:56 +00:00
messages.error(request, "No active sessions found for user.")
2024-03-20 12:25:07 +00:00
# Перенаправление на callback URL.
return redirect(settings.KEYCLOAK_CONFIG['CALLBACK_URL'])
2024-03-19 11:07:56 +00:00
2024-03-20 12:25:07 +00:00
# Флаг, указывающий на успешный выход из всех сессий.
2024-03-19 11:07:56 +00:00
all_sessions_logged_out = True
for session in sessions:
2024-03-20 12:25:07 +00:00
# Попытка выхода из каждой сессии через Keycloak API.
2024-03-19 11:07:56 +00:00
if not logout_user(admin_token, session['id']):
2024-03-20 12:25:07 +00:00
# Если выход из какой-либо сессии не удался, флаг устанавливается в False.
2024-03-19 11:07:56 +00:00
all_sessions_logged_out = False
logger.error(f"Failed to log out session {session['id']}")
if all_sessions_logged_out:
2024-03-20 12:25:07 +00:00
# Логгирование успешного выхода из всех сессий.
2024-03-19 11:07:56 +00:00
logger.info("User successfully logged out from all Keycloak sessions.")
2024-03-20 12:25:07 +00:00
# Уведомление пользователя о успешном выходе.
2024-03-19 11:07:56 +00:00
messages.success(request, "Successfully logged out.")
else:
2024-03-20 12:25:07 +00:00
# Уведомление пользователя, если выход был выполнен не из всех сессий.
2024-03-19 11:07:56 +00:00
messages.warning(request, "Partial logout. Some sessions may still be active.")
2024-03-20 12:25:07 +00:00
# Очистка сессии Django после выхода пользователя.
2024-03-20 10:19:34 +00:00
request.session.flush()
2024-03-20 12:25:07 +00:00
# Перенаправление пользователя на callback URL после выхода.
return redirect(settings.KEYCLOAK_CONFIG['CALLBACK_URL'])
2024-03-20 10:19:34 +00:00
def check_user_authenticated(request):
user_id = get_user_id_from_session(request)
is_authenticated = user_id is not None
return JsonResponse({'isAuthenticated': is_authenticated})