1
0
mirror of https://gitlab.com/psono/psono-server synced 2025-04-18 09:44:01 +03:00

Merge branch 'develop' into 'master'

Preparing v5.0.14

See merge request psono/psono-server!231
This commit is contained in:
Sascha Pfeiffer 2025-04-16 18:40:49 +00:00
commit f9e4b8e354
12 changed files with 72 additions and 7 deletions

View File

@ -301,8 +301,8 @@ build-sbom:
- echo $CI_JOB_TOKEN | docker login --username=gitlab-ci-token --password-stdin registry.gitlab.com
- docker pull $CONTAINER_TEST_IMAGE
- curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
- syft scan psono/psono-combo-enterprise:latest -o cyclonedx-json > sbom.json
- mv /builds/psono/psono-server/sbom.json ../
- syft scan $CONTAINER_TEST_IMAGE -o cyclonedx-json > sbom.json
- mv ./sbom.json ../
- rm -Rf *
- rm -Rf .* 2> /dev/null || true
- mv ../sbom.json ./

View File

@ -182,6 +182,9 @@ if isinstance(ALLOWED_SECOND_FACTORS, str) and ALLOWED_SECOND_FACTORS:
elif isinstance(ALLOWED_SECOND_FACTORS, str):
ALLOWED_SECOND_FACTORS = []
FAVICON_SERVICE_URL = config_get('FAVICON_SERVICE_URL', 'https://favicon.psono.com/v1/icon/')
ALLOW_USER_SEARCH_BY_EMAIL = str(config_get('ALLOW_USER_SEARCH_BY_EMAIL', False)).lower() == 'true'
ALLOW_USER_SEARCH_BY_USERNAME_PARTIAL = str(config_get('ALLOW_USER_SEARCH_BY_USERNAME_PARTIAL', False)).lower() == 'true'
@ -708,6 +711,7 @@ def generate_signature():
'management': MANAGEMENT_ENABLED,
'files': FILES_ENABLED,
'allowed_file_repository_types': ALLOWED_FILE_REPOSITORY_TYPES,
'favicon_service_url': FAVICON_SERVICE_URL,
'auto_prolongation_token_time_valid': AUTO_PROLONGATION_TOKEN_TIME_VALID,
'allowed_second_factors': ALLOWED_SECOND_FACTORS,
'disable_central_security_reports': DISABLE_CENTRAL_SECURITY_REPORTS,

View File

@ -0,0 +1,19 @@
# Generated by Django 4.2.20 on 2025-04-16 10:20
from django.db import migrations
import timezone_field.fields
class Migration(migrations.Migration):
dependencies = [
('restapi', '0039_link_share_allow_write_alter_ivalt_mobile_and_more'),
]
operations = [
migrations.AddField(
model_name='user',
name='zoneinfo',
field=timezone_field.fields.TimeZoneField(null=True),
),
]

View File

@ -4,6 +4,7 @@ from django.dispatch import receiver
from django.core.cache import cache
from django.conf import settings
from django.utils import timezone
from timezone_field import TimeZoneField
from decimal import Decimal
import binascii
@ -76,6 +77,7 @@ class User(models.Model):
last_login = models.DateTimeField(default=timezone.now)
hashing_algorithm = models.CharField('hashing algorithm', max_length=32, default=DEFAULT_HASHING_ALGORITHM, choices=HASHING_ALGORITHMS,)
hashing_parameters = models.JSONField('hashing parameters', default=default_hashing_parameters)
zoneinfo = TimeZoneField(null=True)
credit = models.DecimalField(max_digits=24, decimal_places=16, default=Decimal(str(0)))

View File

@ -1,5 +1,5 @@
from django.utils.crypto import constant_time_compare
from timezone_field.backends import TimeZoneNotFoundError, get_tz_backend
from rest_framework import serializers, exceptions
import nacl.utils
from nacl.exceptions import CryptoError
@ -9,8 +9,10 @@ import nacl.encoding
class ActivateTokenSerializer(serializers.Serializer):
verification = serializers.CharField(required=True)
verification_nonce = serializers.CharField(max_length=64, required=True)
zoneinfo = serializers.CharField(required=False, default=None, allow_null=True)
def validate(self, attrs: dict) -> dict:
zoneinfo = attrs.get('zoneinfo', None)
verification_hex = attrs.get('verification', '')
verification = nacl.encoding.HexEncoder.decode(verification_hex)
verification_nonce_hex = attrs.get('verification_nonce', '')
@ -55,5 +57,12 @@ class ActivateTokenSerializer(serializers.Serializer):
msg = 'VERIFICATION_CODE_INCORRECT'
raise exceptions.ValidationError(msg)
tz_backend = get_tz_backend(use_pytz=None)
try:
zoneinfo = tz_backend.to_tzobj(zoneinfo)
except TimeZoneNotFoundError:
zoneinfo = None
attrs['zoneinfo'] = zoneinfo
attrs['token'] = token
return attrs

View File

@ -1,6 +1,7 @@
from django.conf import settings
from django.contrib.auth.hashers import check_password
from rest_framework import serializers, exceptions
from timezone_field.backends import TimeZoneNotFoundError, get_tz_backend
import re
import bcrypt
@ -28,8 +29,10 @@ class UserUpdateSerializer(serializers.Serializer):
hashing_parameters = serializers.DictField(required=False, )
language = serializers.CharField(max_length=16, required=False, allow_null=True)
zoneinfo = serializers.CharField(required=False, default=None, allow_null=True)
def validate(self, attrs: dict) -> dict:
zoneinfo = attrs.get('zoneinfo', None)
email = attrs.get('email')
authkey_old = attrs.get('authkey_old')
authkey = attrs.get('authkey', False)
@ -98,6 +101,14 @@ class UserUpdateSerializer(serializers.Serializer):
msg = 'INVALID_HASHING_PARAMETER'
raise exceptions.ValidationError(msg)
tz_backend = get_tz_backend(use_pytz=None)
try:
zoneinfo = tz_backend.to_tzobj(zoneinfo)
except TimeZoneNotFoundError:
zoneinfo = None
attrs['zoneinfo'] = zoneinfo
attrs['hashing_algorithm'] = hashing_algorithm
attrs['hashing_parameters'] = hashing_parameters

View File

@ -1,4 +1,3 @@
from anymail.exceptions import AnymailUnsupportedFeature
from rest_framework import status
from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
@ -7,6 +6,7 @@ from django.utils import timezone
from django.conf import settings
from django.template.loader import render_to_string
from django.utils import translation
from django.utils.formats import date_format
import os
from email.mime.image import MIMEImage
@ -54,6 +54,7 @@ class ActivateTokenView(GenericAPIView):
)
token = serializer.validated_data['token']
zoneinfo = serializer.validated_data['zoneinfo']
token.active = True
token.user_validator = None
@ -62,6 +63,8 @@ class ActivateTokenView(GenericAPIView):
ip_address = get_ip(request)
login_datetime = timezone.now()
if not request.user.zoneinfo and zoneinfo:
request.user.zoneinfo = zoneinfo
request.user.last_login = login_datetime
request.user.save()
@ -74,8 +77,16 @@ class ActivateTokenView(GenericAPIView):
email = decrypt_with_db_secret(request.user.email)
with translation.override(request.LANGUAGE_CODE):
if request.user.zoneinfo:
login_datetime_timezone = login_datetime.astimezone(request.user.zoneinfo)
login_datetime_timezone_str = date_format(login_datetime_timezone, format='DATETIME_FORMAT') + ' ' + login_datetime_timezone.tzname()
else:
login_datetime_timezone_str = date_format(login_datetime, format='DATETIME_FORMAT') + ' UTC'
subject = render_to_string('email/new_login_subject.txt', {
'ip_address': ip_address,
'login_datetime_timezone': login_datetime_timezone_str,
'login_datetime': login_datetime,
'email': email,
'username': request.user.username,
@ -85,6 +96,7 @@ class ActivateTokenView(GenericAPIView):
}).replace('\n', ' ').replace('\r', '')
msg_plain = render_to_string('email/new_login.txt', {
'ip_address': ip_address,
'login_datetime_timezone': login_datetime_timezone_str,
'login_datetime': login_datetime,
'email': email,
'username': request.user.username,
@ -94,6 +106,7 @@ class ActivateTokenView(GenericAPIView):
})
msg_html = render_to_string('email/new_login.html', {
'ip_address': ip_address,
'login_datetime_timezone': login_datetime_timezone_str,
'login_datetime': login_datetime,
'email': email,
'username': request.user.username,

View File

@ -62,6 +62,9 @@ class UserUpdate(GenericAPIView):
if 'language' in request.data and request.data['language'] is not None:
request.user.language = str(request.data['language'])
if serializer.validated_data['zoneinfo'] is not None:
request.user.zoneinfo = serializer.validated_data['zoneinfo']
# Password Change
if 'authkey' in request.data and request.data['authkey'] is not None:
request.user.authkey = make_password(str(request.data['authkey']))

View File

@ -98,7 +98,7 @@
<p style="font-family:sans-serif;font-size:14px;font-weight:normal;margin: 0 0 15px;">
{% trans "We noticed a new login to your Psono account:" %}</p>
<p style="font-family:sans-serif;font-size:14px;font-weight:normal;margin: 0 0 15px;">
<b>{% trans "Date & Time:" %}</b> {{ login_datetime|localize }}</p>
<b>{% trans "Date & Time:" %}</b> {{ login_datetime_timezone }}</p>
<p style="font-family:sans-serif;font-size:14px;font-weight:normal;margin: 0 0 15px;">
<b>{% trans "IP Address:" %}</b> {{ ip_address }}</p>
<p style="font-family:sans-serif;font-size:14px;font-weight:normal;margin: 0 0 15px;">

View File

@ -3,7 +3,7 @@
{% trans "Hello," %}
{% trans "We noticed a new login to your Psono account:" %}
{% trans "Date & Time:" %} {{ login_datetime|localize }}
{% trans "Date & Time:" %} {{ login_datetime_timezone }}
{% trans "IP Address:" %} {{ ip_address }}
{% trans "Username:" %} {{ username }}

View File

@ -32,4 +32,5 @@ dj-database-url==2.1.0
toml==0.10.2
azure-storage-blob==12.13.0
webauthn==2.5.2
tzdata==2023.4
tzdata==2023.4
django-timezone-field==7.1.0

View File

@ -84,6 +84,7 @@ django==4.2.20
# django-filter
# django-redis
# django-storages
# django-timezone-field
# djangorestframework
# sentry-sdk
django-anymail==12.0
@ -96,6 +97,8 @@ django-redis==5.4.0
# via -r requirements.in
django-storages[azure,boto3,google,libcloud,sftp]==1.14.2
# via -r requirements.in
django-timezone-field==7.1
# via -r requirements.in
djangorestframework==3.15.2
# via -r requirements.in
duo-client==4.2.3