Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .envs/.dev.example
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ S3_PUBLIC_ENDPOINT_URL=http://localhost:9000
# S3_SECRET_KEY=

DJANGO_SETTINGS_MODULE=settings.dev
DJANGO_KEYCLOAK_REALM=dev
DJANGO_KEYCLOAK_URL=http://keycloak:8080
SECRET_KEY=<CHANGE_SECRET_KEY>
DJANGO_DEBUG=True
TIMEZONE=Europe/Berlin
Expand Down
2 changes: 1 addition & 1 deletion app/recordings/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from recordings.views import DashboardView, RecordingDetailView, RecordingPlayListView

app_name = "users"
app_name = "recordings"
urlpatterns = [
path("", DashboardView.as_view(), name="dashboard"),
path("<str:short_id>/", RecordingDetailView.as_view(), name="detail"),
Expand Down
8 changes: 8 additions & 0 deletions app/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@
OIDC_OP_LOGOUT_ENDPOINT = env("OIDC_OP_LOGOUT_ENDPOINT")
OIDC_OP_LOGOUT_URL_METHOD = 'sorbay.auth_backend.provider_logout'

########################################
# KEYCLOAK
########################################
KEYCLOAK_URL = env("DJANGO_KEYCLOAK_URL")
KEYCLOAK_REALM = env("DJANGO_KEYCLOAK_REALM")
KEYCLOAK_ADMIN_USER = env("KEYCLOAK_ADMIN")
KEYCLOAK_ADMIN_PASSWORD = env("KEYCLOAK_ADMIN_PASSWORD")

########################################
# DJANGO REST FRAMEWORK
########################################
Expand Down
66 changes: 66 additions & 0 deletions app/sorbay/keycloak.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import logging
from django.conf import settings
from keycloak import KeycloakAdmin

from users.models import User


class KeycloakError(Exception):
pass


logger = logging.getLogger(__name__)


class KeycloakAPI:
"""API, giving direct access to the Keycloak server"""

@property
def keycloak_admin(self):
keycloak_admin = KeycloakAdmin(
server_url=settings.KEYCLOAK_URL,
username=settings.KEYCLOAK_ADMIN_USER,
password=settings.KEYCLOAK_ADMIN_PASSWORD,
realm_name="master",
verify=True
)
keycloak_admin.realm_name = settings.KEYCLOAK_REALM
return keycloak_admin

def create_user(self, email, first_name, last_name, role, verified_email, credentials,
org=None, ):
"""Creates a new user on Keycloak and in Django"""
logger.info(f"Creating user {email}")
id = self.keycloak_admin.create_user(
{
"firstName": first_name,
"lastName": last_name,
"email": email,
"enabled": True,
"emailVerified": verified_email,
"username": email,
"credentials": credentials,
}
)
u = User.objects.create_user(
username=id,
email=email,
org=org,
role=role,
first_name=first_name,
last_name=last_name
)
return u

def change_password(self, user, new_password, temporary):
self.keycloak_admin.set_user_password(
password=new_password,
temporary=temporary,
user_id=user.keycloak_id
)

def change_email(self, user, email):
self.keycloak_admin.update_user(
user_id=user.keycloak_id,
payload={"email": email}
)
3 changes: 3 additions & 0 deletions app/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ <h1 class="navbar-brand navbar-brand-autodark d-none-navbar-horizontal pe-0 pe-m
</div>
</a>
<div class="dropdown-menu dropdown-menu-end dropdown-menu-arrow">
<a class="navbar-brand" href="{% url "users:settings" %}">
<input type="submit" class="dropdown-item" value="Settings">
</a>
<form method="post" action="{% url 'oidc_logout' %}">
{% csrf_token %}
<input type="submit" class="dropdown-item" value="Sign Out">
Expand Down
57 changes: 57 additions & 0 deletions app/templates/users/settings.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,60 @@
{% extends "base.html" %}

{% block title %}Settings{% endblock %}

{% block page_header %}
<div class="container-xl">
<div class="page-header d-print-none pb-3">
<div class="row g-2 align-items-center">
<div class="col">
<!-- Page pre-title -->
<div class="page-pretitle">
User
</div>
<h2 class="page-title">
Settings
</h2>
</div>
</div>
</div>
</div>
{% endblock %}

{% block main %}
<div class="card col-8 align-self-center">
<div class="row g-0">
<div class="col-3 d-none d-md-block border-end">
<div class="card-body">
<h4 class="subheader">User Settings</h4>

<div class="list-group list-group-transparent">
<a href="{% url "users:updatenames" %}" class="list-group-item list-group-item-action d-flex align-items-center">Update The Names</a>
<a href="{% url "users:updateemail" %}" class="list-group-item list-group-item-action d-flex align-items-center">Update The email</a>
<a href="{% url "users:updatepassword" %}" class="list-group-item list-group-item-action d-flex align-items-center">Update the Password</a>
</div>
</div>
<div class="card-body">
<h4 class="subheader">Recording</h4>

<div class="list-group list-group-transparent">
<a href="#example" class="list-group-item list-group-item-action d-flex align-items-center">Recording settings</a>
<a href="#example" class="list-group-item list-group-item-action d-flex align-items-center">List Of Devices</a>
</div>
</div>
</div>
{% if view.template_name == 'users/settings.html' %}
<div class="col d-flex align-items-center justify-content-center">
{% else %}
<div class="col d-flex flex-column">
{% endif %}
{% block content_settings %}

<div class="col-4 mb-3 align-self-center d-flex align-self-center justify-content-center">
<div class="cursor-not-allowed bg-light py-3">Select the settings you want</div>
</div>
{% endblock %}

</div>
</div>
</div>
{% endblock main %}
12 changes: 12 additions & 0 deletions app/templates/users/update-email.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% extends "users/settings.html" %}
{% load forms %}

{% block title %}Update User's Email{% endblock title %}

{% block content_settings %}
<div class="card-body">
<h2 class="mb-4">My Email</h2>
{% url "users:updateemail" as action %}
{% render_form form=form submit_btn="Submit" action=action %}
</div>
{% endblock content_settings %}
12 changes: 12 additions & 0 deletions app/templates/users/update-name.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% extends "users/settings.html" %}
{% load forms %}

{% block title %}Update User's name{% endblock title %}

{% block content_settings %}
<div class="card-body">
<h2 class="mb-4">My Names</h2>
{% url "users:updatenames" as action %}
{% render_form form=form submit_btn="Submit" action=action %}
</div>
{% endblock content_settings %}
12 changes: 12 additions & 0 deletions app/templates/users/update-password.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{% extends "users/settings.html" %}
{% load forms %}

{% block title %}Update User's Password{% endblock title %}

{% block content_settings %}
<div class="card-body">
<h2 class="mb-4">Update Password</h2>
{% url "users:updatepassword" as action %}
{% render_form form=form submit_btn="Submit" action=action %}
</div>
{% endblock content_settings %}
42 changes: 41 additions & 1 deletion app/users/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from django import forms
from django.core.exceptions import ValidationError

from users.models import Device
from users.models import Device, User


class RegisterDeviceForm(forms.ModelForm):
Expand All @@ -12,3 +13,42 @@ class RegisterDeviceForm(forms.ModelForm):
class Meta:
model = Device
fields = ['token', 'name', 'application', 'release']


class UserNameUpdateForm(forms.ModelForm):

class Meta:
model = User
fields = ['first_name', 'last_name']


class UserEmailUpdateForm(forms.ModelForm):

class Meta:
model = User
fields = ['email']


class UserPasswordUpdateForm(forms.ModelForm):

new_password1 = forms.CharField(
label='New password',
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
strip=False,
)
new_password2 = forms.CharField(
label='New password confirmation',
widget=forms.PasswordInput(attrs={'autocomplete': 'new-password'}),
strip=False,
)

class Meta:
model = User
fields = ['new_password1', 'new_password2']

def clean_new_password2(self):
password1 = self.cleaned_data.get('new_password1')
password2 = self.cleaned_data.get('new_password2')
if password1 and password2 and (password1 != password2):
raise ValidationError('Password Missmach')
return password2
7 changes: 7 additions & 0 deletions app/users/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@

app_name = "users"
urlpatterns = [
path("updatenames/", views.UpdateUserNameView.as_view(), name="updatenames"),
path("updateemail/", views.UpdateUserEmailView.as_view(), name="updateemail"),
path(
"updatepassword/",
views.UpdateUserPasswordView.as_view(),
name="updatepassword"
),
path("settings/", views.SettingsView.as_view(), name="settings"),
path("device/register/<str:payload>/", views.DeviceRegisterView.as_view(),
name="device-register"),
Expand Down
89 changes: 86 additions & 3 deletions app/users/views.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,93 @@
import base64
import json

from django.urls import reverse
from django.contrib.auth.mixins import LoginRequiredMixin
from django.shortcuts import render
from django.views.generic import TemplateView, FormView
from users.forms import RegisterDeviceForm
from django.core.exceptions import ValidationError
from django.shortcuts import render, redirect
from django.views.generic import TemplateView, FormView, UpdateView
from users.forms import (
RegisterDeviceForm,
UserNameUpdateForm,
UserEmailUpdateForm,
UserPasswordUpdateForm,
)
from sorbay.keycloak import KeycloakAPI


class UpdateUserNameView(LoginRequiredMixin, UpdateView):
template_name = "users/update-name.html"
form_class = UserNameUpdateForm

def get_object(self, queryset=None):
return self.request.user

def form_valid(self, form):
user = form.save(commit=False)
first_name = user.first_name
last_name = user.last_name
payload = {'firstName': first_name, 'lastName': last_name}
keycloakAPI = KeycloakAPI()
keycloak_admin = keycloakAPI.keycloak_admin
keycloak_admin.refresh_token()
response = keycloak_admin.update_user(user_id=user.username, payload=payload)
if response:
raise ValidationError("An error occured, please try again later...")
form.save(commit=True)
return redirect(self.get_success_url())

def get_success_url(self):
return reverse("users:settings")


class UpdateUserEmailView(LoginRequiredMixin, UpdateView):
template_name = "users/update-email.html"
form_class = UserEmailUpdateForm

def get_object(self, queryset=None):
return self.request.user

def form_valid(self, form):
user = form.save(commit=False)
email = user.email
payload = {'username': email, 'email': email}
keycloakAPI = KeycloakAPI()
keycloak_admin = keycloakAPI.keycloak_admin
keycloak_admin.refresh_token()
response = keycloak_admin.update_user(user_id=user.username, payload=payload)
if response:
raise ValidationError("An error occured, please try again later...")
form.save(commit=True)
return redirect(self.get_success_url())

def get_success_url(self):
return reverse("users:settings")


class UpdateUserPasswordView(LoginRequiredMixin, UpdateView):
template_name = "users/update-password.html"
form_class = UserPasswordUpdateForm

def get_object(self, queryset=None):
return self.request.user

def form_valid(self, form):
user = form.save(commit=False)
password = form.data['new_password1']
keycloakAPI = KeycloakAPI()
keycloak_admin = keycloakAPI.keycloak_admin
keycloak_admin.refresh_token()
response = keycloak_admin.set_user_password(
user_id=user.username,
password=password,
temporary=False
)
if response:
raise ValidationError("An error occured, please try again later...")
return redirect(self.get_success_url())

def get_success_url(self):
return reverse("users:settings")


class SettingsView(LoginRequiredMixin, TemplateView):
Expand Down