mirror of
https://github.com/jwinterm/modoboa.git
synced 2026-10-17 05:41:25 +02:00
Few updates
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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' },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user