forked from shaba/openuds
Replaced session PickleSerializer with Json-bases serializer (custom one). More secure
This commit is contained in:
parent
0ed1f3ccee
commit
748d8d7464
@ -199,7 +199,7 @@ MIDDLEWARE = [
|
||||
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
|
||||
# SESSION_COOKIE_AGE = 3600
|
||||
SESSION_COOKIE_HTTPONLY = False
|
||||
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
||||
SESSION_SERIALIZER = 'uds.core.util.session_serializer.SessionSerializer'
|
||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
||||
|
||||
ROOT_URLCONF = 'server.urls'
|
||||
|
@ -56,3 +56,5 @@ class RESTLoginLogoutCase(TestCase):
|
||||
services = fixtures.services.createServices(provider, number_of_services=2, type_of_service=1)
|
||||
services = services + fixtures.services.createServices(provider, number_of_services=2, type_of_service=2)
|
||||
|
||||
print(provider)
|
||||
print(services)
|
@ -32,6 +32,7 @@
|
||||
"""
|
||||
import typing
|
||||
import logging
|
||||
import codecs
|
||||
|
||||
from django.contrib.sessions.backends.base import SessionBase
|
||||
from django.contrib.sessions.backends.db import SessionStore
|
||||
@ -206,7 +207,7 @@ class Handler:
|
||||
"""
|
||||
try:
|
||||
del self._headers[header]
|
||||
except Exception:
|
||||
except Exception: # nosec: intentionally ingoring exception
|
||||
pass # If not found, just ignore it
|
||||
|
||||
# Auth related
|
||||
@ -240,10 +241,13 @@ class Handler:
|
||||
if is_admin:
|
||||
staff_member = True # Make admins also staff members :-)
|
||||
|
||||
# crypt password and convert to base64
|
||||
passwd = codecs.encode(cryptoManager().symCrypt(password, scrambler), 'base64').decode()
|
||||
|
||||
session['REST'] = {
|
||||
'auth': id_auth,
|
||||
'username': username,
|
||||
'password': cryptoManager().symCrypt(password, scrambler), # Stores "bytes"
|
||||
'password': passwd,
|
||||
'locale': locale,
|
||||
'platform': platform,
|
||||
'is_admin': is_admin,
|
||||
@ -304,6 +308,9 @@ class Handler:
|
||||
"""
|
||||
try:
|
||||
if self._session:
|
||||
# if key is password, its in base64, so decode it and return as bytes
|
||||
if key == 'password':
|
||||
return codecs.decode(self._session['REST'][key], 'base64')
|
||||
return self._session['REST'].get(key)
|
||||
return None
|
||||
except Exception:
|
||||
@ -315,7 +322,11 @@ class Handler:
|
||||
"""
|
||||
try:
|
||||
if self._session:
|
||||
self._session['REST'][key] = value
|
||||
# if key is password, its in base64, so encode it and store as str
|
||||
if key == 'password':
|
||||
self._session['REST'][key] = codecs.encode(value, 'base64').decode()
|
||||
else:
|
||||
self._session['REST'][key] = value
|
||||
self._session.accessed = True
|
||||
self._session.save()
|
||||
except Exception:
|
||||
|
@ -34,6 +34,7 @@ Provides useful functions for authenticating, used by web interface.
|
||||
'''
|
||||
import logging
|
||||
import typing
|
||||
import codecs
|
||||
|
||||
from functools import wraps
|
||||
from django.http import (
|
||||
@ -342,8 +343,7 @@ def authenticateViaCallback(
|
||||
|
||||
result = authInstance.authCallback(params, gm, request)
|
||||
if result.success == auths.AuthenticationSuccess.FAIL or (
|
||||
result.success == auths.AuthenticationSuccess.OK
|
||||
and not gm.hasValidGroups()
|
||||
result.success == auths.AuthenticationSuccess.OK and not gm.hasValidGroups()
|
||||
):
|
||||
raise auths.exceptions.InvalidUserException('User doesn\'t has access to UDS')
|
||||
|
||||
@ -354,9 +354,7 @@ def authenticateViaCallback(
|
||||
return AuthResult(url=result.url)
|
||||
|
||||
if result.username:
|
||||
return registerUser(
|
||||
authenticator, authInstance, result.username or '', request
|
||||
)
|
||||
return registerUser(authenticator, authInstance, result.username or '', request)
|
||||
|
||||
raise auths.exceptions.InvalidUserException('User doesn\'t has access to UDS')
|
||||
|
||||
@ -409,9 +407,7 @@ def webLogin(
|
||||
False # For now, we don't know if the user is authorized until MFA is checked
|
||||
)
|
||||
request.session[USER_KEY] = user.id
|
||||
request.session[PASS_KEY] = cryptoManager().symCrypt(
|
||||
password, cookie
|
||||
) # Stores "bytes"
|
||||
request.session[PASS_KEY] = codecs.encode(cryptoManager().symCrypt(password, cookie), "base64").decode() # as str
|
||||
|
||||
# Ensures that this user will have access through REST api if logged in through web interface
|
||||
# Note that REST api will set the session expiry to selected value if user is an administrator
|
||||
@ -436,8 +432,9 @@ def webPassword(request: HttpRequest) -> str:
|
||||
so we can provide it to remote sessions.
|
||||
"""
|
||||
if hasattr(request, 'session'):
|
||||
passkey = codecs.decode(request.session.get(PASS_KEY, '').encode(), 'base64')
|
||||
return cryptoManager().symDecrpyt(
|
||||
request.session.get(PASS_KEY, ''), getUDSCookie(request)
|
||||
passkey, getUDSCookie(request)
|
||||
) # recover as original unicode string
|
||||
else: # No session, get from _session instead, this is an "client" REST request
|
||||
return cryptoManager().symDecrpyt(request._cryptedpass, request._scrambler) # type: ignore
|
||||
|
@ -37,7 +37,14 @@ from django.utils import timezone
|
||||
|
||||
from uds.core.util import os_detector as OsDetector
|
||||
from uds.core.util.config import GlobalConfig
|
||||
from uds.core.auths.auth import AUTHORIZED_KEY, EXPIRY_KEY, ROOT_ID, USER_KEY, getRootUser, webLogout
|
||||
from uds.core.auths.auth import (
|
||||
AUTHORIZED_KEY,
|
||||
EXPIRY_KEY,
|
||||
ROOT_ID,
|
||||
USER_KEY,
|
||||
getRootUser,
|
||||
webLogout,
|
||||
)
|
||||
from uds.models import User
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
@ -81,21 +88,27 @@ class GlobalRequestMiddleware:
|
||||
if request.user:
|
||||
# return HttpResponse(content='Session Expired', status=403, content_type='text/plain')
|
||||
now = timezone.now()
|
||||
expiry = request.session.get(EXPIRY_KEY, now)
|
||||
try:
|
||||
expiry = datetime.datetime.fromisoformat(
|
||||
request.session.get(EXPIRY_KEY, '')
|
||||
)
|
||||
except ValueError:
|
||||
expiry = now
|
||||
if expiry < now:
|
||||
try:
|
||||
return webLogout(
|
||||
request=request
|
||||
)
|
||||
except Exception:
|
||||
return webLogout(request=request)
|
||||
except Exception: # nosec: intentionaly catching all exceptions and ignoring them
|
||||
pass # If fails, we don't care, we just want to logout
|
||||
return HttpResponse(content='Session Expired', status=403)
|
||||
# Update session timeout..self.
|
||||
request.session[EXPIRY_KEY] = now + datetime.timedelta(
|
||||
seconds=GlobalConfig.SESSION_DURATION_ADMIN.getInt()
|
||||
if request.user.isStaff()
|
||||
else GlobalConfig.SESSION_DURATION_USER.getInt()
|
||||
)
|
||||
request.session[EXPIRY_KEY] = (
|
||||
now
|
||||
+ datetime.timedelta(
|
||||
seconds=GlobalConfig.SESSION_DURATION_ADMIN.getInt()
|
||||
if request.user.isStaff()
|
||||
else GlobalConfig.SESSION_DURATION_USER.getInt()
|
||||
)
|
||||
).isoformat() # store as ISO format, str, json serilizable
|
||||
|
||||
response = self._get_response(request)
|
||||
|
||||
|
54
server/src/uds/core/util/session_serializer.py
Normal file
54
server/src/uds/core/util/session_serializer.py
Normal file
@ -0,0 +1,54 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012-2022 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import typing
|
||||
|
||||
from django.contrib.sessions.serializers import JSONSerializer
|
||||
|
||||
class SessionSerializer(JSONSerializer):
|
||||
"""
|
||||
Serializer for django sessions.
|
||||
"""
|
||||
def dumps(self, data):
|
||||
"""
|
||||
Serialize data for storage in a session.
|
||||
"""
|
||||
return JSONSerializer.dumps(self, data)
|
||||
|
||||
def loads(self, data):
|
||||
"""
|
||||
Deserialize data from a session.
|
||||
"""
|
||||
try:
|
||||
return JSONSerializer.loads(self, data)
|
||||
except Exception:
|
||||
return {} # If pickle session was used, we get an exception, so we return an empty dict
|
Loading…
x
Reference in New Issue
Block a user