python: Use standard secrets module to generate random tokens.

There are three functional side effects:

• Correct an insignificant but mathematically offensive bias toward
repeated characters in generate_api_key introduced in commit
47b4283c4b4c70ecde4d3c8de871c90ee2506d87; its entropy is increased
from 190.52864 bits to 190.53428 bits.

• Use the base32 alphabet in confirmation.models.generate_key; its
entropy is reduced from 124.07820 bits to the documented 120 bits, but
now it uses 1 syscall instead of 24.

• Use the base32 alphabet in get_bigbluebutton_url; its entropy is
reduced from 51.69925 bits to 50 bits, but now it uses 1 syscall
instead of 10.

(The base32 alphabet is A-Z 2-7.  We could probably replace all of
these with plain secrets.token_urlsafe, since I expect most callers
can handle the full urlsafe_b64 alphabet A-Z a-z 0-9 - _ without
problems.)

Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
Anders Kaseorg 2020-09-04 19:02:13 -07:00 committed by Tim Abbott
parent 56546170cf
commit b7b7475672
20 changed files with 51 additions and 57 deletions

View File

@ -2,8 +2,8 @@
__revision__ = '$Id: models.py 28 2009-10-22 15:03:02Z jarek.zgoda $' __revision__ = '$Id: models.py 28 2009-10-22 15:03:02Z jarek.zgoda $'
import datetime import datetime
import string import secrets
from random import SystemRandom from base64 import b32encode
from typing import Mapping, Optional, Union from typing import Mapping, Optional, Union
from urllib.parse import urljoin from urllib.parse import urljoin
@ -37,9 +37,8 @@ def render_confirmation_key_error(request: HttpRequest, exception: ConfirmationK
return render(request, 'confirmation/link_does_not_exist.html') return render(request, 'confirmation/link_does_not_exist.html')
def generate_key() -> str: def generate_key() -> str:
generator = SystemRandom()
# 24 characters * 5 bits of entropy/character = 120 bits of entropy # 24 characters * 5 bits of entropy/character = 120 bits of entropy
return ''.join(generator.choice(string.ascii_lowercase + string.digits) for _ in range(24)) return b32encode(secrets.token_bytes(15)).decode().lower()
ConfirmationObjT = Union[MultiuseInvite, PreregistrationUser, EmailChangeStatus] ConfirmationObjT = Union[MultiuseInvite, PreregistrationUser, EmailChangeStatus]
def get_object_from_key(confirmation_key: str, def get_object_from_key(confirmation_key: str,

View File

@ -1,6 +1,7 @@
import logging import logging
import math import math
import os import os
import secrets
from datetime import datetime, timedelta from datetime import datetime, timedelta
from decimal import Decimal from decimal import Decimal
from functools import wraps from functools import wraps
@ -25,7 +26,6 @@ from corporate.models import (
) )
from zerver.lib.logging_util import log_to_file from zerver.lib.logging_util import log_to_file
from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime from zerver.lib.timestamp import datetime_to_timestamp, timestamp_to_datetime
from zerver.lib.utils import generate_random_token
from zerver.models import Realm, RealmAuditLog, UserProfile, get_system_bot from zerver.models import Realm, RealmAuditLog, UserProfile, get_system_bot
from zproject.config import get_secret from zproject.config import get_secret
@ -54,7 +54,7 @@ def get_latest_seat_count(realm: Realm) -> int:
return max(non_guests, math.ceil(guests / 5)) return max(non_guests, math.ceil(guests / 5))
def sign_string(string: str) -> Tuple[str, str]: def sign_string(string: str) -> Tuple[str, str]:
salt = generate_random_token(64) salt = secrets.token_hex(32)
signer = Signer(salt=salt) signer = Signer(salt=salt)
return signer.sign(string), salt return signer.sign(string), salt

View File

@ -40,9 +40,9 @@ def random_token() -> str:
# of importing cryptography modules when necessary. # of importing cryptography modules when necessary.
# #
# This helps optimize noop provision performance. # This helps optimize noop provision performance.
from zerver.lib.utils import generate_random_token import secrets
return generate_random_token(64) return secrets.token_hex(32)
def generate_django_secretkey() -> str: def generate_django_secretkey() -> str:
"""Secret key generation taken from Django's startproject.py""" """Secret key generation taken from Django's startproject.py"""

View File

@ -20,7 +20,6 @@ rules:
- id: dont-import-models-in-migrations - id: dont-import-models-in-migrations
patterns: patterns:
- pattern-not: from zerver.lib.redis_utils import get_redis_client - pattern-not: from zerver.lib.redis_utils import get_redis_client
- pattern-not: from zerver.lib.utils import generate_random_token
- pattern-not: from zerver.models import filter_pattern_validator - pattern-not: from zerver.models import filter_pattern_validator
- pattern-not: from zerver.models import filter_format_validator - pattern-not: from zerver.models import filter_format_validator
- pattern-not: from zerver.models import generate_email_token_for_stream - pattern-not: from zerver.models import generate_email_token_for_stream

View File

@ -1,6 +1,7 @@
import logging import logging
import os import os
import random import random
import secrets
import shutil import shutil
import subprocess import subprocess
from collections import defaultdict from collections import defaultdict
@ -39,7 +40,7 @@ from zerver.data_import.slack_message_conversion import (
) )
from zerver.lib.emoji import name_to_codepoint from zerver.lib.emoji import name_to_codepoint
from zerver.lib.export import MESSAGE_BATCH_CHUNK_SIZE from zerver.lib.export import MESSAGE_BATCH_CHUNK_SIZE
from zerver.lib.upload import random_name, resize_logo, sanitize_name from zerver.lib.upload import resize_logo, sanitize_name
from zerver.models import ( from zerver.models import (
CustomProfileField, CustomProfileField,
CustomProfileFieldValue, CustomProfileFieldValue,
@ -904,7 +905,7 @@ def get_attachment_path_and_content(fileinfo: ZerverFieldsT, realm_id: int) -> T
'SlackImportAttachment', # This is a special placeholder which should be kept 'SlackImportAttachment', # This is a special placeholder which should be kept
# in sync with 'exports.py' function 'import_message_data' # in sync with 'exports.py' function 'import_message_data'
format(random.randint(0, 255), 'x'), format(random.randint(0, 255), 'x'),
random_name(18), secrets.token_urlsafe(18),
sanitize_name(fileinfo['name']), sanitize_name(fileinfo['name']),
]) ])
attachment_path = f'/user_uploads/{s3_path}' attachment_path = f'/user_uploads/{s3_path}'

View File

@ -1,10 +1,9 @@
# See https://zulip.readthedocs.io/en/latest/subsystems/caching.html for docs # See https://zulip.readthedocs.io/en/latest/subsystems/caching.html for docs
import base64
import hashlib import hashlib
import logging import logging
import os import os
import random
import re import re
import secrets
import sys import sys
import time import time
import traceback import traceback
@ -87,8 +86,7 @@ def get_or_create_key_prefix() -> str:
filename = os.path.join(settings.DEPLOY_ROOT, "var", "remote_cache_prefix") filename = os.path.join(settings.DEPLOY_ROOT, "var", "remote_cache_prefix")
try: try:
fd = os.open(filename, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o444) fd = os.open(filename, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o444)
random_hash = hashlib.sha256(str(random.getrandbits(256)).encode('utf-8')).digest() prefix = secrets.token_hex(16) + ':'
prefix = base64.b16encode(random_hash)[:32].decode('utf-8').lower() + ':'
# This does close the underlying file # This does close the underlying file
with os.fdopen(fd, 'w') as f: with os.fdopen(fd, 'w') as f:
f.write(prefix + "\n") f.write(prefix + "\n")

View File

@ -1,5 +1,6 @@
import logging import logging
import re import re
import secrets
from email.headerregistry import AddressHeader from email.headerregistry import AddressHeader
from email.message import EmailMessage from email.message import EmailMessage
from typing import Dict, List, Optional, Tuple from typing import Dict, List, Optional, Tuple
@ -25,7 +26,6 @@ from zerver.lib.queue import queue_json_publish
from zerver.lib.rate_limiter import RateLimitedObject from zerver.lib.rate_limiter import RateLimitedObject
from zerver.lib.send_email import FromAddress from zerver.lib.send_email import FromAddress
from zerver.lib.upload import upload_message_file from zerver.lib.upload import upload_message_file
from zerver.lib.utils import generate_random_token
from zerver.models import ( from zerver.models import (
Message, Message,
MissedMessageEmailAddress, MissedMessageEmailAddress,
@ -96,7 +96,7 @@ def log_and_report(email_message: EmailMessage, error_message: str, to: Optional
# Temporary missed message addresses # Temporary missed message addresses
def generate_missed_message_token() -> str: def generate_missed_message_token() -> str:
return 'mm' + generate_random_token(32) return 'mm' + secrets.token_hex(16)
def is_missed_message_address(address: str) -> bool: def is_missed_message_address(address: str) -> bool:
try: try:

View File

@ -1,6 +1,7 @@
import datetime import datetime
import logging import logging
import os import os
import secrets
import shutil import shutil
from typing import Any, Dict, Iterable, List, Optional, Tuple from typing import Any, Dict, Iterable, List, Optional, Tuple
@ -30,7 +31,7 @@ from zerver.lib.parallel import run_parallel
from zerver.lib.server_initialization import create_internal_realm, server_initialized from zerver.lib.server_initialization import create_internal_realm, server_initialized
from zerver.lib.streams import render_stream_description from zerver.lib.streams import render_stream_description
from zerver.lib.timestamp import datetime_to_timestamp from zerver.lib.timestamp import datetime_to_timestamp
from zerver.lib.upload import BadImageError, guess_type, random_name, sanitize_name from zerver.lib.upload import BadImageError, guess_type, sanitize_name
from zerver.lib.utils import generate_api_key, process_list_in_batches from zerver.lib.utils import generate_api_key, process_list_in_batches
from zerver.models import ( from zerver.models import (
AlertWord, AlertWord,
@ -668,7 +669,7 @@ def import_uploads(realm: Realm, import_dir: Path, processes: int, processing_av
# function 'upload_message_file' # function 'upload_message_file'
relative_path = "/".join([ relative_path = "/".join([
str(record['realm_id']), str(record['realm_id']),
random_name(18), secrets.token_urlsafe(18),
sanitize_name(os.path.basename(record['path'])), sanitize_name(os.path.basename(record['path'])),
]) ])
path_maps['attachment_path'][record['s3_path']] = relative_path path_maps['attachment_path'][record['s3_path']] = relative_path

View File

@ -1,12 +1,11 @@
import re import re
import secrets
from typing import Any, Dict, Mapping, Optional from typing import Any, Dict, Mapping, Optional
import orjson import orjson
import redis import redis
from django.conf import settings from django.conf import settings
from zerver.lib.utils import generate_random_token
# Redis accepts keys up to 512MB in size, but there's no reason for us to use such size, # Redis accepts keys up to 512MB in size, but there's no reason for us to use such size,
# so we want to stay limited to 1024 characters. # so we want to stay limited to 1024 characters.
MAX_KEY_LENGTH = 1024 MAX_KEY_LENGTH = 1024
@ -34,7 +33,7 @@ def put_dict_in_redis(redis_client: redis.StrictRedis, key_format: str,
error_msg = "Requested key too long in put_dict_in_redis. Key format: %s, token length: %s" error_msg = "Requested key too long in put_dict_in_redis. Key format: %s, token length: %s"
raise ZulipRedisKeyTooLongError(error_msg % (key_format, token_length)) raise ZulipRedisKeyTooLongError(error_msg % (key_format, token_length))
if token is None: if token is None:
token = generate_random_token(token_length) token = secrets.token_hex(token_length // 2)
key = key_format.format(token=token) key = key_format.format(token=token)
with redis_client.pipeline() as pipeline: with redis_client.pipeline() as pipeline:
pipeline.set(key, orjson.dumps(data_to_store)) pipeline.set(key, orjson.dumps(data_to_store))

View File

@ -5,6 +5,7 @@ import logging
import os import os
import random import random
import re import re
import secrets
import shutil import shutil
import unicodedata import unicodedata
import urllib import urllib
@ -30,7 +31,6 @@ from PIL.Image import DecompressionBombError
from zerver.lib.avatar_hash import user_avatar_path from zerver.lib.avatar_hash import user_avatar_path
from zerver.lib.exceptions import ErrorCode, JsonableError from zerver.lib.exceptions import ErrorCode, JsonableError
from zerver.lib.utils import generate_random_token
from zerver.models import Attachment, Message, Realm, RealmEmoji, UserProfile from zerver.models import Attachment, Message, Realm, RealmEmoji, UserProfile
DEFAULT_AVATAR_SIZE = 100 DEFAULT_AVATAR_SIZE = 100
@ -94,9 +94,6 @@ def sanitize_name(value: str) -> str:
assert value not in {'', '.', '..'} assert value not in {'', '.', '..'}
return mark_safe(value) return mark_safe(value)
def random_name(bytes: int=60) -> str:
return base64.urlsafe_b64encode(os.urandom(bytes)).decode('utf-8')
class BadImageError(JsonableError): class BadImageError(JsonableError):
code = ErrorCode.BAD_IMAGE code = ErrorCode.BAD_IMAGE
@ -362,7 +359,7 @@ class S3UploadBackend(ZulipUploadBackend):
target_realm = user_profile.realm target_realm = user_profile.realm
s3_file_name = "/".join([ s3_file_name = "/".join([
str(target_realm.id), str(target_realm.id),
random_name(18), secrets.token_urlsafe(18),
sanitize_name(uploaded_file_name), sanitize_name(uploaded_file_name),
]) ])
url = f"/user_uploads/{s3_file_name}" url = f"/user_uploads/{s3_file_name}"
@ -591,7 +588,7 @@ class S3UploadBackend(ZulipUploadBackend):
def upload_export_tarball(self, realm: Optional[Realm], tarball_path: str, def upload_export_tarball(self, realm: Optional[Realm], tarball_path: str,
percent_callback: Optional[Callable[[Any], None]]=None) -> str: percent_callback: Optional[Callable[[Any], None]]=None) -> str:
# We use the avatar bucket, because it's world-readable. # We use the avatar bucket, because it's world-readable.
key = self.avatar_bucket.Object(os.path.join("exports", generate_random_token(32), key = self.avatar_bucket.Object(os.path.join("exports", secrets.token_hex(16),
os.path.basename(tarball_path))) os.path.basename(tarball_path)))
key.upload_file(tarball_path, Callback=percent_callback) key.upload_file(tarball_path, Callback=percent_callback)
@ -672,7 +669,7 @@ class LocalUploadBackend(ZulipUploadBackend):
path = "/".join([ path = "/".join([
str(user_profile.realm_id), str(user_profile.realm_id),
format(random.randint(0, 255), 'x'), format(random.randint(0, 255), 'x'),
random_name(18), secrets.token_urlsafe(18),
sanitize_name(uploaded_file_name), sanitize_name(uploaded_file_name),
]) ])
@ -819,7 +816,7 @@ class LocalUploadBackend(ZulipUploadBackend):
path = os.path.join( path = os.path.join(
'exports', 'exports',
str(realm.id), str(realm.id),
random_name(18), secrets.token_urlsafe(18),
os.path.basename(tarball_path), os.path.basename(tarball_path),
) )
abs_path = os.path.join(settings.LOCAL_UPLOADS_DIR, 'avatars', path) abs_path = os.path.join(settings.LOCAL_UPLOADS_DIR, 'avatars', path)

View File

@ -1,10 +1,8 @@
import base64
import hashlib import hashlib
import heapq import heapq
import itertools import itertools
import os
import re import re
import string import secrets
from itertools import zip_longest from itertools import zip_longest
from time import sleep from time import sleep
from typing import Any, Callable, Iterator, List, Optional, Sequence, Set, Tuple, TypeVar from typing import Any, Callable, Iterator, List, Optional, Sequence, Set, Tuple, TypeVar
@ -107,14 +105,12 @@ def log_statsd_event(name: str) -> None:
event_name = f"events.{name}" event_name = f"events.{name}"
statsd.incr(event_name) statsd.incr(event_name)
def generate_random_token(length: int) -> str:
return str(base64.b16encode(os.urandom(length // 2)).decode('utf-8').lower())
def generate_api_key() -> str: def generate_api_key() -> str:
choices = string.ascii_letters + string.digits api_key = ""
altchars = ''.join(choices[ord(os.urandom(1)) % 62] for _ in range(2)).encode("utf-8") while len(api_key) < 32:
api_key = base64.b64encode(os.urandom(24), altchars=altchars).decode("utf-8") # One iteration suffices 99.4992% of the time.
return api_key api_key += secrets.token_urlsafe(3 * 9).replace("_", "").replace("-", "")
return api_key[:32]
def has_api_key_format(key: str) -> bool: def has_api_key_format(key: str) -> bool:
return bool(re.fullmatch(r"([A-Za-z0-9]){32}", key)) return bool(re.fullmatch(r"([A-Za-z0-9]){32}", key))

View File

@ -1,3 +1,5 @@
import secrets
from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ObjectDoesNotExist
from django.db import migrations from django.db import migrations
from django.db.backends.postgresql.schema import DatabaseSchemaEditor from django.db.backends.postgresql.schema import DatabaseSchemaEditor
@ -5,11 +7,10 @@ from django.db.migrations.state import StateApps
# Imported to avoid needing to duplicate redis-related code. # Imported to avoid needing to duplicate redis-related code.
from zerver.lib.redis_utils import get_redis_client from zerver.lib.redis_utils import get_redis_client
from zerver.lib.utils import generate_random_token
def generate_missed_message_token() -> str: def generate_missed_message_token() -> str:
return 'mm' + generate_random_token(32) return 'mm' + secrets.token_hex(16)
def move_missed_message_addresses_to_database(apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None: def move_missed_message_addresses_to_database(apps: StateApps, schema_editor: DatabaseSchemaEditor) -> None:
redis_client = get_redis_client() redis_client = get_redis_client()

View File

@ -1,6 +1,7 @@
import ast import ast
import datetime import datetime
import re import re
import secrets
import time import time
from collections import defaultdict from collections import defaultdict
from datetime import timedelta from datetime import timedelta
@ -78,7 +79,7 @@ from zerver.lib.types import (
UserFieldElement, UserFieldElement,
Validator, Validator,
) )
from zerver.lib.utils import generate_random_token, make_safe_digest from zerver.lib.utils import make_safe_digest
from zerver.lib.validator import ( from zerver.lib.validator import (
check_date, check_date,
check_int, check_int,
@ -1464,7 +1465,7 @@ class PushDeviceToken(AbstractPushDeviceToken):
unique_together = ("user", "kind", "token") unique_together = ("user", "kind", "token")
def generate_email_token_for_stream() -> str: def generate_email_token_for_stream() -> str:
return generate_random_token(32) return secrets.token_hex(16)
class Stream(models.Model): class Stream(models.Model):
MAX_NAME_LENGTH = 60 MAX_NAME_LENGTH = 60

View File

@ -3,6 +3,7 @@ import copy
import datetime import datetime
import json import json
import re import re
import secrets
import time import time
import urllib import urllib
from contextlib import contextmanager from contextlib import contextmanager
@ -67,7 +68,6 @@ from zerver.lib.test_helpers import (
) )
from zerver.lib.upload import MEDIUM_AVATAR_SIZE, resize_avatar from zerver.lib.upload import MEDIUM_AVATAR_SIZE, resize_avatar
from zerver.lib.users import get_all_api_keys from zerver.lib.users import get_all_api_keys
from zerver.lib.utils import generate_random_token
from zerver.lib.validator import ( from zerver.lib.validator import (
Validator, Validator,
check_bool, check_bool,
@ -549,7 +549,7 @@ class RateLimitAuthenticationTests(ZulipTestCase):
# We have to mock RateLimitedAuthenticationByUsername.key to avoid key collisions # We have to mock RateLimitedAuthenticationByUsername.key to avoid key collisions
# if tests run in parallel. # if tests run in parallel.
original_key_method = RateLimitedAuthenticationByUsername.key original_key_method = RateLimitedAuthenticationByUsername.key
salt = generate_random_token(32) salt = secrets.token_hex(16)
def _mock_key(self: RateLimitedAuthenticationByUsername) -> str: def _mock_key(self: RateLimitedAuthenticationByUsername) -> str:
return f"{salt}:{original_key_method(self)}" return f"{salt}:{original_key_method(self)}"
@ -2991,7 +2991,7 @@ class GoogleAuthBackendTest(SocialAuthBase):
'redirect_to': '', 'redirect_to': '',
} }
with mock.patch("logging.warning") as mock_warn: with mock.patch("logging.warning") as mock_warn:
token = generate_random_token(ExternalAuthResult.LOGIN_TOKEN_LENGTH) token = secrets.token_hex(ExternalAuthResult.LOGIN_TOKEN_LENGTH // 2)
result = self.get_log_into_subdomain(data, force_token=token) result = self.get_log_into_subdomain(data, force_token=token)
mock_warn.assert_called_once_with("log_into_subdomain: Invalid token given: %s", token) mock_warn.assert_called_once_with("log_into_subdomain: Invalid token given: %s", token)
self.assertEqual(result.status_code, 400) self.assertEqual(result.status_code, 400)

View File

@ -163,7 +163,7 @@ class TestVideoCall(ZulipTestCase):
def test_create_bigbluebutton_link(self) -> None: def test_create_bigbluebutton_link(self) -> None:
with mock.patch('zerver.views.video_calls.random.randint', return_value="1"), mock.patch( with mock.patch('zerver.views.video_calls.random.randint', return_value="1"), mock.patch(
'zerver.views.video_calls.random.SystemRandom.choice', return_value="A"): 'secrets.token_bytes', return_value=b"\x00" * 7):
response = self.client_get("/json/calls/bigbluebutton/create") response = self.client_get("/json/calls/bigbluebutton/create")
self.assert_json_success(response) self.assert_json_success(response)
self.assertEqual(response.json()['url'], self.assertEqual(response.json()['url'],

View File

@ -1,3 +1,4 @@
import secrets
import time import time
from typing import Dict, List, Tuple, Type from typing import Dict, List, Tuple, Type
from unittest import mock from unittest import mock
@ -12,9 +13,8 @@ from zerver.lib.rate_limiter import (
remove_ratelimit_rule, remove_ratelimit_rule,
) )
from zerver.lib.test_classes import ZulipTestCase from zerver.lib.test_classes import ZulipTestCase
from zerver.lib.utils import generate_random_token
RANDOM_KEY_PREFIX = generate_random_token(32) RANDOM_KEY_PREFIX = secrets.token_hex(16)
class RateLimitedTestObject(RateLimitedObject): class RateLimitedTestObject(RateLimitedObject):
def __init__(self, name: str, rules: List[Tuple[int, int]], def __init__(self, name: str, rules: List[Tuple[int, int]],

View File

@ -45,7 +45,7 @@ class RedisUtilsTest(ZulipTestCase):
# Trying to put data under an overly long key should get stopped before even # Trying to put data under an overly long key should get stopped before even
# generating the random token. # generating the random token.
with mock.patch("zerver.lib.redis_utils.generate_random_token") as mock_generate: with mock.patch("secrets.token_hex") as mock_generate:
with self.assertRaises(ZulipRedisKeyTooLongError): with self.assertRaises(ZulipRedisKeyTooLongError):
put_dict_in_redis(self.redis_client, self.key_format, data, put_dict_in_redis(self.redis_client, self.key_format, data,
expiration_seconds=self.expiration_seconds, expiration_seconds=self.expiration_seconds,

View File

@ -1,5 +1,5 @@
import logging import logging
import os import secrets
import urllib import urllib
from functools import wraps from functools import wraps
from typing import Any, Dict, List, Mapping, Optional, cast from typing import Any, Dict, List, Mapping, Optional, cast
@ -289,7 +289,7 @@ def finish_desktop_flow(request: HttpRequest, user_profile: UserProfile,
result = ExternalAuthResult(user_profile=user_profile) result = ExternalAuthResult(user_profile=user_profile)
token = result.store_data() token = result.store_data()
key = bytes.fromhex(otp) key = bytes.fromhex(otp)
iv = os.urandom(12) iv = secrets.token_bytes(12)
desktop_data = (iv + AESGCM(key).encrypt(iv, token.encode(), b"")).hex() desktop_data = (iv + AESGCM(key).encrypt(iv, token.encode(), b"")).hex()
context = {'desktop_data': desktop_data, context = {'desktop_data': desktop_data,
'browser_url': reverse('zerver.views.auth.login_page', 'browser_url': reverse('zerver.views.auth.login_page',

View File

@ -1,4 +1,5 @@
import logging import logging
import secrets
from typing import Any, Dict, List, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
from django.conf import settings from django.conf import settings
@ -19,7 +20,7 @@ from zerver.lib.push_notifications import num_push_devices_for_user
from zerver.lib.streams import access_stream_by_name from zerver.lib.streams import access_stream_by_name
from zerver.lib.subdomains import get_subdomain from zerver.lib.subdomains import get_subdomain
from zerver.lib.users import compute_show_invites_and_add_streams from zerver.lib.users import compute_show_invites_and_add_streams
from zerver.lib.utils import generate_random_token, statsd from zerver.lib.utils import statsd
from zerver.models import PreregistrationUser, Realm, Stream, UserProfile from zerver.models import PreregistrationUser, Realm, Stream, UserProfile
from zerver.views.compatibility import is_outdated_desktop_app, is_unsupported_browser from zerver.views.compatibility import is_outdated_desktop_app, is_unsupported_browser
from zerver.views.portico import hello_view from zerver.views.portico import hello_view
@ -200,7 +201,7 @@ def home_real(request: HttpRequest) -> HttpResponse:
request._log_data['extra'] = "[{}]".format(queue_id) request._log_data['extra'] = "[{}]".format(queue_id)
csp_nonce = generate_random_token(48) csp_nonce = secrets.token_hex(24)
user_permission_info = get_user_permission_info(user_profile) user_permission_info = get_user_permission_info(user_profile)

View File

@ -1,7 +1,8 @@
import hashlib import hashlib
import json import json
import random import random
import string import secrets
from base64 import b32encode
from functools import partial from functools import partial
from typing import Dict from typing import Dict
from urllib.parse import quote, urlencode, urljoin from urllib.parse import quote, urlencode, urljoin
@ -164,7 +165,7 @@ def get_bigbluebutton_url(request: HttpRequest, user_profile: UserProfile) -> Ht
# https://docs.bigbluebutton.org/dev/api.html#create for reference on the api calls # https://docs.bigbluebutton.org/dev/api.html#create for reference on the api calls
# https://docs.bigbluebutton.org/dev/api.html#usage for reference for checksum # https://docs.bigbluebutton.org/dev/api.html#usage for reference for checksum
id = "zulip-" + str(random.randint(100000000000, 999999999999)) id = "zulip-" + str(random.randint(100000000000, 999999999999))
password = ''.join(random.SystemRandom().choice(string.ascii_uppercase + string.digits) for _ in range(10)) password = b32encode(secrets.token_bytes(7))[:10].decode()
checksum = hashlib.sha1(("create" + "meetingID=" + id + "&moderatorPW=" checksum = hashlib.sha1(("create" + "meetingID=" + id + "&moderatorPW="
+ password + "&attendeePW=" + password + "a" + settings.BIG_BLUE_BUTTON_SECRET).encode()).hexdigest() + password + "&attendeePW=" + password + "a" + settings.BIG_BLUE_BUTTON_SECRET).encode()).hexdigest()
url = add_query_to_redirect_url("/calls/bigbluebutton/join", urlencode({ url = add_query_to_redirect_url("/calls/bigbluebutton/join", urlencode({