Few updates

This commit is contained in:
Antoine Nguyen
2024-06-22 19:44:55 +02:00
committed by Spitfireap
parent 8f730da963
commit 30717b0139
9 changed files with 79 additions and 87 deletions
+5 -2
View File
@@ -1,4 +1,7 @@
{
"API_BASE_URL": "/api/v2/",
"API_DOC_URL": "/api/schema-v2/swagger/"
"API_BASE_URL": "/api/v2/",
"API_DOC_URL": "/api/schema-v2/swagger/",
"OAUTH_AUTHORITY_URL": "http://localhost:8000/api/o",
"OAUTH_CLIENT_ID": "LVQbfIIX3khWR3nDvix1u9yEGHZUxcx53bhJ7FlD",
"OAUTH_REDIRECT_URI": "http://localhost:3000/login/logged"
}
+1 -1
View File
@@ -272,7 +272,7 @@ const userMenuItems = [
},
]
if (authUser.value.role !== 'SimpleUsers') {
if (isAuthenticated && authUser.value.role !== 'SimpleUsers') {
userMenuItems.unshift({
text: $gettext('Admin'),
to: { name: 'Dashboard' },
+5
View File
@@ -16,11 +16,14 @@ import { createApp } from 'vue'
//api
import repository from '@/api/repository.js'
let globalConfig
fetch(import.meta.env.BASE_URL + 'config.json').then((resp) => {
resp.json().then((config) => {
if (!config.API_BASE_URL) {
throw Error('API_BASE_URL is not defined in config.json')
}
globalConfig = config
repository.defaults.baseURL = config.API_BASE_URL
const app = createApp(App)
@@ -32,3 +35,5 @@ fetch(import.meta.env.BASE_URL + 'config.json').then((resp) => {
app.provide('$config', config)
})
})
export const useGlobalConfig = () => globalConfig
+6 -3
View File
@@ -10,13 +10,16 @@ import repository from '@/api/repository'
import accountApi from '@/api/account'
import accountsApi from '@/api/accounts'
import { useGlobalConfig } from '@/main'
export const useAuthStore = defineStore('auth', () => {
const config = useGlobalConfig()
const authUser = ref(null)
const isAuthenticated = ref(false)
const manager = new UserManager({
authority: 'http://localhost:8000/api/o',
client_id: 'LVQbfIIX3khWR3nDvix1u9yEGHZUxcx53bhJ7FlD',
redirect_uri: 'http://localhost:3000/login/logged',
authority: config.OAUTH_AUTHORITY_URL,
client_id: config.OAUTH_CLIENT_ID,
redirect_uri: config.OAUTH_REDIRECT_URI,
response_type: 'code',
scope: 'openid read write',
automaticSilentRenew: true,
-5
View File
@@ -3,9 +3,6 @@
from django.urls import path
from rest_framework import routers
from rest_framework_simplejwt.views import (
TokenRefreshView,
)
from . import views
from . import viewsets
@@ -18,8 +15,6 @@ router.register(r"logs/audit-trail", viewsets.LogViewSet)
urlpatterns = router.urls
urlpatterns += [
path("token/", views.TokenObtainPairView.as_view(), name="token_obtain_pair"),
path("token/refresh/", TokenRefreshView.as_view(), name="token_refresh"),
path(
"password_reset/",
views.DefaultPasswordResetView.as_view(),
+2 -69
View File
@@ -1,33 +1,22 @@
"""Core API v2 views."""
import logging
from smtplib import SMTPException
from django.utils.html import escape
from django.utils.translation import gettext as _
from django.utils.datastructures import MultiValueDictKeyError
from django.contrib.auth import login
from drf_spectacular.utils import extend_schema
from rest_framework import permissions, response, status
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_simplejwt import views as jwt_views
from rest_framework_simplejwt.exceptions import InvalidToken
from rest_framework import permissions, response
from rest_framework.views import APIView
from modoboa.core.password_hashers import get_password_hasher
from modoboa.core.utils import check_for_updates
from modoboa.lib.permissions import IsSuperUser
from modoboa.lib.throttle import (
UserLesserDdosUser,
LoginThrottle,
PasswordResetApplyThrottle,
PasswordResetRequestThrottle,
PasswordResetTotpThrottle,
)
from modoboa.parameters import tools as param_tools
from smtplib import SMTPException
from . import serializers
@@ -43,62 +32,6 @@ def delete_cache_key(class_target, throttles, request):
return
class TokenObtainPairView(jwt_views.TokenObtainPairView):
"""We overwrite this view to deal with password scheme update."""
throttle_classes = [LoginThrottle]
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
except AuthenticationFailed as e:
logger.warning(
_("Failed connection attempt from '%s' as user '%s'"),
request.META["REMOTE_ADDR"],
escape(serializer.initial_data["username"]),
)
raise InvalidToken(e.args[0])
user = serializer.user
login(request, user)
# Reset login throttle
delete_cache_key(LoginThrottle, self.get_throttles(), request)
logger.info(_("User '%s' successfully logged in"), user.username)
if user and user.is_active:
condition = user.is_local and param_tools.get_global_parameter(
"update_scheme", raise_exception=False
)
if condition:
# check if password scheme is correct
scheme = param_tools.get_global_parameter(
"password_scheme", raise_exception=False
)
# use SHA512CRYPT as default fallback
if scheme is None:
pwhash = get_password_hasher("sha512crypt")()
else:
pwhash = get_password_hasher(scheme)()
if not user.password.startswith(pwhash.scheme):
logger.info(
_("Password scheme mismatch. Updating %s password"),
user.username,
)
user.set_password(request.data["password"])
user.save()
if pwhash.needs_rehash(user.password):
logger.info(
_("Password hash parameter missmatch. " "Updating %s password"),
user.username,
)
user.set_password(serializer.data["password"])
user.save()
return response.Response(serializer.validated_data, status=status.HTTP_200_OK)
class EmailPasswordResetView(APIView):
"""
An Api View which provides a method to request a password reset token based on an e-mail address.
+38 -3
View File
@@ -9,13 +9,18 @@ import string
import subprocess
import sys
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
import dj_database_url
import django
from django.conf import settings
from django.core import management
from django.template import Context, Template
from django.utils.encoding import smart_str
from oauth2_provider.models import Application
from modoboa.core.commands import Command
from modoboa.lib.api_client import ModoAPIClient
from modoboa.lib.sysutils import exec_cmd
@@ -197,6 +202,30 @@ class DeployCommand(Command):
extra_settings.append(extension[1])
return extra_settings
def create_frontend_oauth_app(self) -> str:
app = Application.objects.create(
name="Modoboa frontend",
client_type="public",
algorithm="RS256",
authorization_grant_type="authorization-code",
skip_authorization=True,
)
return app.client_id
def generate_rsa_private_key(self, storage_path: str):
"""Generate RSA private key for OIDC support."""
private_key = rsa.generate_private_key(
public_exponent=65537,
key_size=4096,
)
pem = private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.TraditionalOpenSSL,
encryption_algorithm=serialization.NoEncryption(),
)
with open(f"{storage_path}/.env", "wb") as fp:
fp.write(b'OIDC_RSA_PRIVATE_KEY="' + bytes(pem) + b'"\n')
def handle(self, parsed_args):
django.setup()
management.call_command("startproject", parsed_args.name, verbosity=False)
@@ -267,6 +296,8 @@ class DeployCommand(Command):
)
with open("%s/settings.py" % path, "w") as fp:
fp.write(tpl)
self.generate_rsa_private_key(path)
shutil.copyfile("%s/urls.py.tpl" % self._templates_dir, "%s/urls.py" % path)
os.mkdir("%s/media" % parsed_args.name)
@@ -291,11 +322,15 @@ class DeployCommand(Command):
frontend_target_dir = "{}/frontend".format(parsed_args.name)
shutil.copytree(base_frontend_dir, frontend_target_dir)
client_id = self.create_frontend_oauth_app()
frontend_path = getattr(settings, "NEW_ADMIN_URL", "new-admin")
with open("{}/config.json".format(frontend_target_dir), "w") as fp:
fp.write(
"""{
"API_BASE_URL": "https://%s/api/v2"
f"""{
"API_BASE_URL": "https://{allowed_host}/api/v2",
"OAUTH_AUTHORITY_URL": "https://{allowed_host}/api/o",
"OAUTH_CLIENT_ID": "{client_id}",
"OAUTH_REDIRECT_URI": "https://{allowed_host}/{frontend_path}/login/logged"
}
"""
% allowed_host
)
@@ -10,15 +10,16 @@ https://docs.djangoproject.com/en/2.2/ref/settings/
from logging.handlers import SysLogHandler
import os
import environ
{% if devmode %}
from modoboa.core.dev_settings import * # noqa
{% endif %}
env = environ.Env()
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.realpath(os.path.dirname(os.path.dirname(__file__)))
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/2.2/howto/deployment/checklist/
@@ -62,6 +63,7 @@ INSTALLED_APPS = (
'reversion',
'ckeditor',
'ckeditor_uploader',
'oauth2_provider',
'rest_framework',
'rest_framework.authtoken',
'drf_spectacular',
@@ -198,6 +200,22 @@ STATICFILES_DIRS = (
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
# oAuth2 settings
OAUTH2_PROVIDER = {
'OIDC_ENABLED': True,
'OIDC_RP_INITIATED_LOGOUT_ENABLED': True,
'OIDC_RP_INITIATED_LOGOUT_ALWAYS_PROMPT': True,
'OIDC_RSA_PRIVATE_KEY': env('OIDC_RSA_PRIVATE_KEY'),
'SCOPES': {
'openid': 'OpenID Connect scope',
'read': 'Read scope',
'write': 'Write scope',
'introspection': 'Introspect token scope',
},
'DEFAULT_SCOPES': ['openid', 'read', 'write'],
}
# Rest framework settings
REST_FRAMEWORK = {
@@ -211,7 +229,7 @@ REST_FRAMEWORK = {
'password_recovery_apply': '25/hour'
},
'DEFAULT_AUTHENTICATION_CLASSES': (
'modoboa.core.drf_authentication.JWTAuthenticationWith2FA',
'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
),
+1 -1
View File
@@ -6,13 +6,13 @@ django-xforwardedfor-middleware==2.0
django-otp==1.4.1
django-filter
django-rename-app==0.1.7
django-environ
dj-database-url
djangorestframework>=3.14,<3.16
drf-spectacular
uritemplate
djangorestframework-simplejwt==5.3.1
django-oauth-toolkit
django-cors-headers