KEYCLOAK BABE
parent
fd83b11c4a
commit
f928306d15
BIN
db.sqlite3
BIN
db.sqlite3
Binary file not shown.
Binary file not shown.
|
@ -51,7 +51,7 @@ INSTALLED_APPS = [
|
||||||
'mozilla_django_oidc',
|
'mozilla_django_oidc',
|
||||||
|
|
||||||
]
|
]
|
||||||
SESSION_COOKIE_AGE = 86400 # 24 часа в секундах
|
SESSION_COOKIE_AGE = 86400
|
||||||
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
|
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
|
@ -165,8 +165,8 @@ KEYCLOAK_CONFIG = {
|
||||||
'REALM': 'Harmony',
|
'REALM': 'Harmony',
|
||||||
'CLIENT_ID': 'admin-cli',
|
'CLIENT_ID': 'admin-cli',
|
||||||
'CLIENT_SECRET': 'wOVphEiLVBS1AlNKRpaQpD4yQh5Wm3TJ',
|
'CLIENT_SECRET': 'wOVphEiLVBS1AlNKRpaQpD4yQh5Wm3TJ',
|
||||||
'CALLBACK_URL': 'http://127.0.0.1:8000/products/',
|
'CALLBACK_URL': 'http://127.0.0.1:8000/callback/',
|
||||||
'POST_LOGOUT_REDIRECT_URI': 'http://127.0.0.1:8000/',
|
'LOGOUT_REDIRECT_URL': 'http://127.0.0.1:8000/admin_logout_user/',
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,19 +179,24 @@ LOGGING = {
|
||||||
'class': 'logging.FileHandler',
|
'class': 'logging.FileHandler',
|
||||||
'filename': 'debug.log',
|
'filename': 'debug.log',
|
||||||
},
|
},
|
||||||
|
'console': {
|
||||||
|
'level': 'INFO',
|
||||||
|
'class': 'logging.StreamHandler',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
'loggers': {
|
'loggers': {
|
||||||
'django': {
|
'django': {
|
||||||
'handlers': ['file'],
|
'handlers': ['file', 'console'],
|
||||||
'level': 'DEBUG',
|
'level': 'DEBUG',
|
||||||
'propagate': True,
|
'propagate': True,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
AUTHENTICATION_BACKENDS = [
|
AUTHENTICATION_BACKENDS = [
|
||||||
# ...
|
# ...
|
||||||
|
'django.contrib.auth.backends.ModelBackend', # Стандартный бэкенд для работы с моделью User
|
||||||
|
'users.keycloak_backend.KeycloakBackend',
|
||||||
'allauth.account.auth_backends.AuthenticationBackend',
|
'allauth.account.auth_backends.AuthenticationBackend',
|
||||||
# ...
|
# ...
|
||||||
]
|
]
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.9 MiB After Width: | Height: | Size: 1.9 MiB |
Binary file not shown.
Binary file not shown.
|
@ -70,7 +70,7 @@
|
||||||
color: #bb7bff;
|
color: #bb7bff;
|
||||||
}
|
}
|
||||||
.cart-container{
|
.cart-container{
|
||||||
background-color: rgba(203, 92, 255, 0.62); /* Прозрачный серый фон */
|
background-color: rgba(222, 155, 255, 0.62); /* Прозрачный серый фон */
|
||||||
padding: 30px; /* Отступ для создания пространства вокруг .cart-page */
|
padding: 30px; /* Отступ для создания пространства вокруг .cart-page */
|
||||||
border-radius: 15px; /* Скруглённые углы для согласованности с .cart-page */
|
border-radius: 15px; /* Скруглённые углы для согласованности с .cart-page */
|
||||||
box-shadow: 0 6px 12px rgb(136, 5, 255); /* Небольшое тень для дополнительной глубины */
|
box-shadow: 0 6px 12px rgb(136, 5, 255); /* Небольшое тень для дополнительной глубины */
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
|
|
||||||
/* Дополнительный стиль для общей цены, чтобы выделить её ещё сильнее */
|
/* Дополнительный стиль для общей цены, чтобы выделить её ещё сильнее */
|
||||||
.total .num {
|
.total .num {
|
||||||
background-color: rgb(203, 92, 255); /* Слегка прозрачный золотой фон */
|
background-color: rgb(192, 69, 255); /* Слегка прозрачный золотой фон */
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
|
|
||||||
/* Стиль для кнопок действий с использованием эзотерических цветов и эффектов */
|
/* Стиль для кнопок действий с использованием эзотерических цветов и эффектов */
|
||||||
.button.light, .button.checkout {
|
.button.light, .button.checkout {
|
||||||
background: linear-gradient(145deg, #8a00a9, #71008f);
|
background: linear-gradient(145deg, #efb4ff, #f0c1ff);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
border-radius: 30px;
|
border-radius: 30px;
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.3 MiB |
Binary file not shown.
After Width: | Height: | Size: 2.2 MiB |
|
@ -0,0 +1,6 @@
|
||||||
|
const audio = document.querySelector('#audio');
|
||||||
|
const btnka = document.querySelector('#btnka');
|
||||||
|
|
||||||
|
btnka.addEventListener('click', () => {
|
||||||
|
audio.paused ? audio.play() : audio.pause();
|
||||||
|
});
|
|
@ -57,12 +57,18 @@
|
||||||
</a>
|
</a>
|
||||||
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
<ul class="dropdown-menu" aria-labelledby="navbarDropdown">
|
||||||
|
|
||||||
|
<li>
|
||||||
|
|
||||||
|
<a class="dropdown-item text-white" href="{% url 'main:profile' %}">Личный кабинет</a>
|
||||||
|
<a class="dropdown-item text-white" href="{% url 'main:admin_logout_user' %}">Выйти</a>
|
||||||
|
|
||||||
|
<a class="dropdown-item text-white" href="{% url 'keycloak_login' %}">Войти</a>
|
||||||
|
|
||||||
|
</li>
|
||||||
|
|
||||||
<li><a class="dropdown-item text-white" href="#">Личный кабинет</a></li>
|
|
||||||
<li><a class="dropdown-item text-white" href="{% url 'keycloak_login' %}">Войти</a></li>
|
|
||||||
<li><a class="dropdown-item text-white" href="{% url 'keycloak_logout' %}">Выйти</a></li>
|
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -71,10 +77,10 @@
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
<main class="d-flex flex-column flex-md-row align-items-center justify-content-around">
|
<main class="d-flex flex-column flex-md-row align-items-center justify-content-around">
|
||||||
<img alt="backimg" src="{% static '/deps/images/bg-image2.png' %}" class="fixed-top z-0 vw-100 vh-100 object-fit-cover"/>
|
<img alt="backimg" src="{% static '/deps/images/фон2.png' %}" class="fixed-top z-0 vw-100 vh-100 object-fit-cover"/>
|
||||||
|
|
||||||
<div class="button-container d-flex flex-column justify-content-around align-items-center ">
|
<div class="button-container d-flex flex-column justify-content-around align-items-center ">
|
||||||
<a href="/" class="button m-2" id="shop-button">
|
<a href="/products" class="button m-2" id="shop-button">
|
||||||
<div class="icon-shop"></div>
|
<div class="icon-shop"></div>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'main:about' %}" class="button m-2" id="about-button">
|
<a href="{% url 'main:about' %}" class="button m-2" id="about-button">
|
||||||
|
|
|
@ -1,11 +1,39 @@
|
||||||
|
{% extends "main/base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
{% block content %}
|
||||||
<head>
|
<div class="row">
|
||||||
<meta charset="UTF-8">
|
<div class="container mt-5">
|
||||||
<title>Title</title>
|
<div class="row justify-content-center">
|
||||||
</head>
|
<!-- Профиль с данными пользователя -->
|
||||||
<body>
|
<div class="col-md-5 position-relative">
|
||||||
<h1>CALLBACK</h1>
|
<div class="bg-white p-4 mb-4 mx-2 rounded custom-shadow">
|
||||||
</body>
|
<h3 class="text-center mb-4">Профиль пользователя</h3>
|
||||||
</html>
|
<form action="#" method="post" enctype="multipart/form-data">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12 mb-3 text-center">
|
||||||
|
<img src="{% static 'deps/img/no_image.jpg' %}" alt="Аватар пользователя" class="img-fluid rounded-circle" style="max-width: 150px;">
|
||||||
|
<input type="file" class="form-control mt-3" id="id_image" name="image" accept="image/*">
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<strong>Имя:</strong> {{ userinfo.given_name|default:'Не указано' }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<strong>Фамилия:</strong> {{ userinfo.family_name|default:'Не указано' }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<strong>Имя пользователя:</strong> {{ userinfo.preferred_username|default:'Не указано' }}
|
||||||
|
</div>
|
||||||
|
<div class="mb-3">
|
||||||
|
<strong>Email:</strong> {{ userinfo.email|default:'Не указано' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% load static %}
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Title</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>LOGOUT</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -1,118 +1,42 @@
|
||||||
|
|
||||||
{% extends "main/base.html" %}
|
{% extends "main/base.html" %}
|
||||||
{% load static %}
|
{% load static %}
|
||||||
|
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="container mt-5">
|
<div class="container mt-5">
|
||||||
<div class="row justify-content-center">
|
<div class="row justify-content-center">
|
||||||
<!-- Профиль с данными пользователя -->
|
<!-- Профиль с данными пользователя -->
|
||||||
<div class="col-md-5">
|
<div class="col-md-5 position-relative">
|
||||||
<div class="bg-white p-4 mb-4 mx-2 rounded custom-shadow">
|
<div class="bg-white p-4 mb-4 mx-2 rounded custom-shadow">
|
||||||
<h3 class="text-center mb-4">Профиль пользователя</h3>
|
<h3 class="text-center mb-4">Профиль пользователя</h3>
|
||||||
<form action="#" method="post" enctype="multipart/form-data">
|
<form action="#" method="post" enctype="multipart/form-data">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12 mb-3 text-center">
|
<div class="col-md-12 mb-3 text-center">
|
||||||
|
<img src="{% static 'deps/img/no_image.jpg' %}" alt="Аватар пользователя" class="img-fluid rounded-circle" style="max-width: 150px;">
|
||||||
<img src="{% static "deps/img/no_image.jpg" %}"
|
<input type="file" class="form-control mt-3" id="id_image" name="image" accept="image/*">
|
||||||
alt="Аватар пользователя" class="img-fluid rounded-circle"
|
|
||||||
style="max-width: 150px;">
|
|
||||||
|
|
||||||
<input type="file" class="form-control mt-3" id="id_image"
|
|
||||||
name='image'
|
|
||||||
accept="image/*">
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12 mb-3">
|
<div class="col-md-12 mb-3">
|
||||||
<label for="id_first_name" class="form-label">Имя*</label>
|
<label for="id_first_name" class="form-label">Имя*</label>
|
||||||
<input type="text" class="form-control" id="id_first_name"
|
<input type="text" class="form-control" id="id_first_name" name="first_name" placeholder="Введите ваше имя" value="{{ userinfo.given_name|default:'' }}" required>
|
||||||
name="first_name"
|
|
||||||
placeholder="Введите ваше имя"
|
|
||||||
value="{{ userinfo.firstName }}"
|
|
||||||
required>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12 mb-3">
|
<div class="col-md-12 mb-3">
|
||||||
<label for="id_last_name" class="form-label">Фамилия*</label>
|
<label for="id_last_name" class="form-label">Фамилия*</label>
|
||||||
<input type="text" class="form-control" id="id_last_name"
|
<input type="text" class="form-control" id="id_last_name" name="last_name" placeholder="Введите вашу фамилию" value="{{ userinfo.family_name|default:'' }}" required>
|
||||||
name="last_name"
|
|
||||||
placeholder="Введите вашу фамилию"
|
|
||||||
value="{{ userinfo.lastName }}"
|
|
||||||
required>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12 mb-3">
|
<div class="col-md-12 mb-3">
|
||||||
<label for="id_username" class="form-label">Имя пользователя*</label>
|
<label for="id_username" class="form-label">Имя пользователя*</label>
|
||||||
<input type="text" class="form-control" id="id_username"
|
<input type="text" class="form-control" id="id_username" name="username" placeholder="Введите ваше имя пользователя" value="{{ userinfo.preferred_username|default:'' }}" required>
|
||||||
name="username"
|
|
||||||
placeholder="Введите ваше имя пользователя"
|
|
||||||
value="{{ userinfo.username }}"
|
|
||||||
required>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-12 mb-3">
|
<div class="col-md-12 mb-3">
|
||||||
<label for="id_email" class="form-label">Email*</label>
|
<label for="id_email" class="form-label">Email*</label>
|
||||||
<input type="email" class="form-control" id="id_email"
|
<input type="email" class="form-control" id="id_email" name="email" placeholder="Введите ваш email" value="{{ userinfo.email|default:'' }}" required>
|
||||||
name="email"
|
|
||||||
placeholder="Введите ваш email *youremail@example.com"
|
|
||||||
value="{{ userinfo.email }}"
|
|
||||||
required>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button type="submit" class="btn btn-dark">Сохранить</button>
|
<button type="submit" class="btn btn-dark">Сохранить</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Оформленные заказы -->
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class=" bg-white p-4 mb-4 mx-2 rounded custom-shadow">
|
|
||||||
<h3 class="text-center mb-4">Мои заказы</h3>
|
|
||||||
<!-- Разметка заказов -->
|
|
||||||
<div class="container">
|
|
||||||
<div class="accordion" id="accordionExample">
|
|
||||||
{% for order in orders %}
|
|
||||||
<div class="accordion-item">
|
|
||||||
<h2 class="accordion-header" id="heading{{ order.id }}">
|
|
||||||
<button class="accordion-button {% if order != orders.0 %}collapsed{% endif %}" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{ order.id }}" aria-expanded="false" aria-controls="collapse{{ order.id }}">
|
|
||||||
Заказ № {{ order.id }} - {{ order.created_timestamp }} | Статус: <strong class="mx-2">{{order.requires_delivery}}</strong>
|
|
||||||
</button>
|
|
||||||
</h2>
|
|
||||||
<div id="collapse{{ order.id }}" class="accordion-collapse collapse {% if order == orders.0 %}show{% endif %}" aria-labelledby="heading{{ order.id }}" data-bs-parent="#accordionExample">
|
|
||||||
<div class="accordion-body">
|
|
||||||
<table class="table table-dark table-hover">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Товар</th>
|
|
||||||
<th>Количество</th>
|
|
||||||
<th>Цена</th>
|
|
||||||
<th>Общая стоимость</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for item in order.orderitem_set.all %}
|
|
||||||
<tr>
|
|
||||||
<td><a class="text-white" href="{% url 'goods:product' item.product.slug %}">{{ item.product.name }}</a></td>
|
|
||||||
<td>{{ item.quantity }}</td>
|
|
||||||
<td>{{ item.price }}</td>
|
|
||||||
<td>{{ item.products_price }}</td>
|
|
||||||
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,12 +4,14 @@ from . import views
|
||||||
app_name = 'main'
|
app_name = 'main'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path('admin_logout_user/',views.admin_logout_user,name = 'admin_logout_user'),
|
||||||
path('callback/', views.callback, name='callback'),
|
path('callback/', views.callback, name='callback'),
|
||||||
|
path('profile/',views.profile,name='profile'),
|
||||||
|
|
||||||
path('', views.product_list, name='product_list'),
|
|
||||||
path('about/', views.about, name='about'),
|
path('about/', views.about, name='about'),
|
||||||
path('products/', views.product_list, name='product_list'),
|
|
||||||
path('<slug:category_slug>/', views.product_list,
|
path('products/', views.callback, name='product_list'),
|
||||||
|
path('<slug:category_slug>/', views.callback,
|
||||||
name='product_list_by_category'),
|
name='product_list_by_category'),
|
||||||
path('<int:id>/<slug:slug>/', views.product_detail,
|
path('<int:id>/<slug:slug>/', views.product_detail,
|
||||||
name='product_detail'),
|
name='product_detail'),
|
||||||
|
|
194
main/views.py
194
main/views.py
|
@ -1,26 +1,20 @@
|
||||||
import json
|
import requests
|
||||||
|
|
||||||
from django.shortcuts import render,get_object_or_404
|
from django.shortcuts import render,get_object_or_404
|
||||||
from django.http import HttpResponse, JsonResponse
|
from django.http import HttpResponse, HttpResponseRedirect
|
||||||
|
import logging
|
||||||
|
from djangoProject1 import settings
|
||||||
from .models import Category, Product
|
from .models import Category, Product
|
||||||
from cart.forms import CartAddProductForm
|
from cart.forms import CartAddProductForm
|
||||||
|
from jose import jwt
|
||||||
|
from django.contrib import messages
|
||||||
|
|
||||||
|
YOUR_REDIRECT_URI = 'http://127.0.0.1:8000/callback/'
|
||||||
def home(request):
|
def home(request):
|
||||||
# Ваш код для отображения главной страницы
|
# Ваш код для отображения главной страницы
|
||||||
return render(request, 'main/base.html')
|
return render(request, 'main/base.html')
|
||||||
|
|
||||||
def product_list(request, category_slug=None):
|
|
||||||
category = None
|
|
||||||
categories = Category.objects.all()
|
|
||||||
products = Product.objects.filter(available=True)
|
|
||||||
if category_slug:
|
|
||||||
category = get_object_or_404(Category,
|
|
||||||
slug=category_slug)
|
|
||||||
products = products.filter(category=category)
|
|
||||||
return render(request,'main/product/list.html',
|
|
||||||
{'category': category,
|
|
||||||
'categories': categories,
|
|
||||||
'products': products})
|
|
||||||
|
|
||||||
def product_detail(request, id, slug):
|
def product_detail(request, id, slug):
|
||||||
product = get_object_or_404(Product,
|
product = get_object_or_404(Product,
|
||||||
|
@ -38,5 +32,173 @@ def product_detail(request, id, slug):
|
||||||
# Функция для страницы "О нас"
|
# Функция для страницы "О нас"
|
||||||
def about(request):
|
def about(request):
|
||||||
return render(request, 'main/about.html')
|
return render(request, 'main/about.html')
|
||||||
def callback(request):
|
|
||||||
|
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')
|
||||||
|
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
|
||||||
|
import jwt
|
||||||
|
|
||||||
|
def decode_access_token(access_token):
|
||||||
|
|
||||||
|
payload = jwt.decode(access_token, options={"verify_signature": False})
|
||||||
|
return payload
|
||||||
|
|
||||||
|
|
||||||
|
def callback(request,category_slug=None):
|
||||||
|
#Получение кода авторизации из запроса
|
||||||
|
context = {}
|
||||||
|
authorization_code = request.GET.get('code')
|
||||||
|
if authorization_code:
|
||||||
|
try:
|
||||||
|
access_token = exchange_code_for_token(authorization_code, YOUR_REDIRECT_URI)
|
||||||
|
user_info = decode_access_token(access_token)
|
||||||
|
request.session['user_info'] = user_info
|
||||||
|
|
||||||
|
|
||||||
|
context['userinfo'] = request.session.get('user_info', {})
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
context['error'] = str(e)
|
||||||
|
else:
|
||||||
|
context['error'] = "Authorization code not found."
|
||||||
|
category = None
|
||||||
|
categories = Category.objects.all()
|
||||||
|
products = Product.objects.filter(available=True)
|
||||||
|
if category_slug:
|
||||||
|
category = get_object_or_404(Category,
|
||||||
|
slug=category_slug)
|
||||||
|
products = products.filter(category=category)
|
||||||
|
return render(request, 'main/product/list.html',{'category': category,
|
||||||
|
'categories': categories,
|
||||||
|
'products': products})
|
||||||
|
from django.shortcuts import render, redirect
|
||||||
|
|
||||||
|
def profile(request):
|
||||||
|
if request.method == 'POST':
|
||||||
|
|
||||||
|
user_info = request.session.get('user_info', {})
|
||||||
|
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', ''))
|
||||||
|
|
||||||
|
|
||||||
|
request.session['user_info'] = user_info
|
||||||
|
|
||||||
|
|
||||||
return render(request, 'main/callback.html')
|
return render(request, 'main/callback.html')
|
||||||
|
else:
|
||||||
|
|
||||||
|
context = {'userinfo': request.session.get('user_info', {})}
|
||||||
|
return render(request, 'main/callback.html', context)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Настройте логирование
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
def get_keycloak_admin_token():
|
||||||
|
data = {
|
||||||
|
'client_id': settings.KEYCLOAK_CONFIG['CLIENT_ID'],
|
||||||
|
'client_secret': settings.KEYCLOAK_CONFIG['CLIENT_SECRET'],
|
||||||
|
'grant_type': 'client_credentials',
|
||||||
|
}
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
f"{settings.KEYCLOAK_CONFIG['SERVER_URL']}/realms/{settings.KEYCLOAK_CONFIG['REALM']}/protocol/openid-connect/token",
|
||||||
|
data=data
|
||||||
|
)
|
||||||
|
return response.json()['access_token']
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_sessions(admin_token, user_id):
|
||||||
|
headers = {'Authorization': f'Bearer {admin_token}'}
|
||||||
|
response = requests.get(
|
||||||
|
f"{settings.KEYCLOAK_CONFIG['SERVER_URL']}/admin/realms/{settings.KEYCLOAK_CONFIG['REALM']}/users/{user_id}/sessions",
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
logger.error(f"Error fetching user sessions: {response.status_code}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def logout_user(admin_token,session_id):
|
||||||
|
headers = {
|
||||||
|
'Authorization': f'Bearer {admin_token}',
|
||||||
|
}
|
||||||
|
response = requests.delete(
|
||||||
|
f"{settings.KEYCLOAK_CONFIG['SERVER_URL']}/admin/realms/{settings.KEYCLOAK_CONFIG['REALM']}/sessions/{session_id}",
|
||||||
|
headers=headers
|
||||||
|
)
|
||||||
|
return response.status_code == 204
|
||||||
|
def get_user_id_from_session(request):
|
||||||
|
user_info = request.session.get('user_info', {})
|
||||||
|
user_id = user_info.get('sub')
|
||||||
|
if isinstance(user_id, str):
|
||||||
|
return user_id
|
||||||
|
else:
|
||||||
|
logger.error(f"Invalid user ID format in session: {user_id}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def admin_logout_user(request):
|
||||||
|
user_id = get_user_id_from_session(request)
|
||||||
|
|
||||||
|
|
||||||
|
admin_token = get_keycloak_admin_token()
|
||||||
|
if not admin_token:
|
||||||
|
logger.error("Can't obtain admin token.")
|
||||||
|
messages.error(request, "Can't obtain admin token for logout.")
|
||||||
|
return redirect(settings.KEYCLOAK_CONFIG['LOGOUT_REDIRECT_URL'])
|
||||||
|
|
||||||
|
sessions = get_user_sessions(admin_token, user_id)
|
||||||
|
if sessions is None or not sessions:
|
||||||
|
logger.error("Can't find user sessions.")
|
||||||
|
messages.error(request, "No active sessions found for user.")
|
||||||
|
return render(request,'main/logout.html')
|
||||||
|
|
||||||
|
all_sessions_logged_out = True
|
||||||
|
for session in sessions:
|
||||||
|
if not logout_user(admin_token, session['id']):
|
||||||
|
all_sessions_logged_out = False
|
||||||
|
logger.error(f"Failed to log out session {session['id']}")
|
||||||
|
|
||||||
|
if all_sessions_logged_out:
|
||||||
|
logger.info("User successfully logged out from all Keycloak sessions.")
|
||||||
|
messages.success(request, "Successfully logged out.")
|
||||||
|
else:
|
||||||
|
messages.warning(request, "Partial logout. Some sessions may still be active.")
|
||||||
|
|
||||||
|
request.session.flush() # Clear Django session
|
||||||
|
return render(request,'main/logout.html')
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,23 @@
|
||||||
|
from django.contrib.auth.backends import BaseBackend
|
||||||
|
from django.contrib.auth.models import User
|
||||||
|
|
||||||
|
from main.views import decode_access_token
|
||||||
|
|
||||||
|
|
||||||
|
class KeycloakBackend(BaseBackend):
|
||||||
|
def authenticate(self, request, token=None):
|
||||||
|
# Ваш код для проверки токена через Keycloak и извлечения информации о пользователе
|
||||||
|
user_info = decode_access_token(token)
|
||||||
|
if user_info:
|
||||||
|
user, created = User.objects.get_or_create(username=user_info['preferred_username'],
|
||||||
|
defaults={'first_name': user_info.get('given_name', ''),
|
||||||
|
'last_name': user_info.get('family_name', ''),
|
||||||
|
'email': user_info.get('email', '')})
|
||||||
|
return user
|
||||||
|
return None
|
||||||
|
|
||||||
|
def get_user(self, user_id):
|
||||||
|
try:
|
||||||
|
return User.objects.get(pk=user_id)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
return None
|
|
@ -6,6 +6,6 @@ from . import views
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
|
||||||
path('login/', views.keycloak_login, name='keycloak_login'),
|
path('login/', views.keycloak_login, name='keycloak_login'),
|
||||||
path('keycloak_logout/', views.keycloak_logout, name='keycloak_logout'),
|
|
||||||
path('products/', views.keycloak_redirect, name='keycloak_redirect'),
|
path('products/', views.keycloak_redirect, name='keycloak_redirect'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import json
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.http import JsonResponse
|
|
||||||
from django.shortcuts import redirect
|
from django.shortcuts import redirect
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from keycloak import KeycloakOpenID
|
from keycloak import KeycloakOpenID
|
||||||
from urllib.parse import urlencode, urljoin
|
from urllib.parse import urlencode
|
||||||
from django.contrib.auth import logout
|
from django.contrib.auth import logout
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -26,22 +26,6 @@ def keycloak_login(request):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def keycloak_logout(request):
|
|
||||||
logout(request)
|
|
||||||
|
|
||||||
keycloak_server_url = settings.KEYCLOAK_CONFIG['SERVER_URL']
|
|
||||||
realm_name = settings.KEYCLOAK_CONFIG['REALM']
|
|
||||||
redirect_uri = settings.KEYCLOAK_CONFIG['POST_LOGOUT_REDIRECT_URI']
|
|
||||||
|
|
||||||
|
|
||||||
params = {'redirect_uri': redirect_uri}
|
|
||||||
keycloak_logout_url = (
|
|
||||||
f'{keycloak_server_url}/realms/{realm_name}/protocol/openid-connect/logout'
|
|
||||||
f'?{urlencode(params)}'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
return redirect(keycloak_logout_url)
|
|
||||||
|
|
||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from django.views.decorators.http import require_http_methods
|
from django.views.decorators.http import require_http_methods
|
||||||
|
|
Loading…
Reference in New Issue