diff --git a/docs/settings.rst b/docs/settings.rst index 8b4ad4053..20bf9e5e1 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -22,7 +22,7 @@ DJANGO_ADMIN_URL n/a r'^admin/' DJANGO_CACHES CACHES (default) locmem redis DJANGO_DATABASES DATABASES (default) See code See code DJANGO_DEBUG DEBUG True False -DJANGO_SECRET_KEY SECRET_KEY CHANGEME!!! raises error +DJANGO_SECRET_KEY SECRET_KEY !!!SET DJANGO_SECRET_KEY!!! raises error DJANGO_SECURE_BROWSER_XSS_FILTER SECURE_BROWSER_XSS_FILTER n/a True DJANGO_SECURE_SSL_REDIRECT SECURE_SSL_REDIRECT n/a True DJANGO_SECURE_CONTENT_TYPE_NOSNIFF SECURE_CONTENT_TYPE_NOSNIFF n/a True diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index 61d617701..2ff52b029 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -13,12 +13,12 @@ except NotImplementedError: PROJECT_DIR_PATH = os.path.realpath(os.path.curdir) -def remove_file(file_path): +def remove_file(file_path: str) -> None: if os.path.exists(file_path): os.remove(file_path) -def remove_open_source_project_only_files(): +def remove_open_source_project_only_files() -> None: filenames = [ 'CONTRIBUTORS.txt' ] @@ -26,7 +26,7 @@ def remove_open_source_project_only_files(): os.remove(os.path.join(PROJECT_DIR_PATH, filename)) -def remove_gplv3_files(): +def remove_gplv3_files() -> None: filenames = [ 'COPYING' ] @@ -34,7 +34,7 @@ def remove_gplv3_files(): os.remove(os.path.join(PROJECT_DIR_PATH, filename)) -def remove_pycharm_files(): +def remove_pycharm_files() -> None: idea_dir_path = os.path.join(PROJECT_DIR_PATH, '.idea') if os.path.exists(idea_dir_path): shutil.rmtree(idea_dir_path) @@ -44,7 +44,7 @@ def remove_pycharm_files(): shutil.rmtree(docs_dir_path) -def remove_docker_files(): +def remove_docker_files() -> None: shutil.rmtree(os.path.join(PROJECT_DIR_PATH, 'compose')) filenames = [ @@ -56,7 +56,7 @@ def remove_docker_files(): os.remove(os.path.join(PROJECT_DIR_PATH, filename)) -def remove_heroku_files(): +def remove_heroku_files() -> None: filenames = [ 'Procfile', 'runtime.txt' @@ -65,7 +65,7 @@ def remove_heroku_files(): remove_file(os.path.join(PROJECT_DIR_PATH, filename)) -def remove_elasticbeanstalk_files(): +def remove_elasticbeanstalk_files() -> None: ebextensions_dir_path = os.path.join(PROJECT_DIR_PATH, '.ebextensions') if os.path.exists(ebextensions_dir_path): shutil.rmtree(ebextensions_dir_path) @@ -77,7 +77,7 @@ def remove_elasticbeanstalk_files(): os.remove(os.path.join(PROJECT_DIR_PATH, filename)) -def try_remove_paas_files(): +def try_remove_paas_files() -> None: none_paas_files_left = True if '{{ cookiecutter.use_heroku }}'.lower() != 'y': @@ -96,7 +96,7 @@ def try_remove_paas_files(): remove_file(os.path.join(PROJECT_DIR_PATH, 'requirements.txt')) -def remove_grunt_files(): +def remove_grunt_files() -> None: filenames = [ 'Gruntfile.js' ] @@ -104,7 +104,7 @@ def remove_grunt_files(): os.remove(os.path.join(PROJECT_DIR_PATH, filename)) -def remove_gulp_files(): +def remove_gulp_files() -> None: filenames = [ 'gulpfile.js' ] @@ -112,7 +112,7 @@ def remove_gulp_files(): os.remove(os.path.join(PROJECT_DIR_PATH, filename)) -def remove_packagejson_file(): +def remove_packagejson_file() -> None: filenames = [ 'package.json' ] @@ -120,45 +120,79 @@ def remove_packagejson_file(): os.remove(os.path.join(PROJECT_DIR_PATH, filename)) -def remove_celery_app(): +def remove_celery_app() -> None: task_app_path = os.path.join(PROJECT_DIR_PATH, '{{ cookiecutter.project_slug }}', 'taskapp') shutil.rmtree(task_app_path) -def append_to_gitignore(path): +def append_to_gitignore(path) -> None: gitignore_file_path = os.path.join(PROJECT_DIR_PATH, '.gitignore') with open(gitignore_file_path, 'a') as gitignore_file: gitignore_file.write(path) gitignore_file.write(os.linesep) -def generate_random_string(length=50): +def generate_random_string(length: int, + using_digits: bool = False, + using_ascii_letters: bool = False, + using_punctuation: bool = False) -> str: """ Returns a securely generated random string. - The default length of 12 with the a-z, A-Z, 0-9 character set returns - a 71-bit value. log_2((26+26+10)^12) =~ 71 bits + For instance, opting out for 50 symbol-long, [a-z][A-Z][0-9] string + would yield log_2((26+26+50)^50) ~= 334 bit strength. """ - punctuation = string.punctuation.replace('"', '').replace("'", '') - punctuation = punctuation.replace('\\', '') - if using_sysrandom: - symbols = [random.choice(string.digits + string.ascii_letters + punctuation) - for i in range(length)] - return ''.join(symbols) + if not using_sysrandom: + return None - print( - "Cookiecutter Django couldn't find a secure pseudo-random number generator on your system. " - "Please set your SECRET_KEY variables manually." - ) - return "CHANGEME!!!" + symbols = [] + if using_digits: + symbols += string.digits + if using_ascii_letters: + symbols += string.ascii_letters + if using_punctuation: + symbols += string.punctuation \ + .replace('"', '') \ + .replace("'", '') \ + .replace('\\', '') + return ''.join([random.choice(symbols) for i in range(length)]) -def set_secret_key(file_path): - with open(file_path) as file: - file_contents = file.read() - SECRET_KEY = generate_random_string() - file_contents = file_contents.replace('CHANGEME!!!', SECRET_KEY, 1) - with open(file_path, 'w') as file: +def replace_flag_with_random_string(file_path: str, + flag: str, + *args, + **kwargs) -> None: + random_string = generate_random_string(*args, **kwargs) + if random_string is None: + print("We couldn't find a secure pseudo-random number generator on your system. " + "Please, {} manually.".format(flag)) + random_string = flag + + with open(file_path, 'r+') as file: + file_contents = file.read().replace(flag, random_string) + file.seek(0) file.write(file_contents) + file.truncate() + + +def set_django_secret_key(file_path: str) -> None: + replace_flag_with_random_string(file_path, '!!!SET DJANGO_SECRET_KEY!!!', + length=50, + using_digits=True, + using_ascii_letters=True, + using_punctuation=True) + + +def set_postgres_user(file_path: str) -> None: + replace_flag_with_random_string(file_path, '!!!SET POSTGRES_USER!!!', + length=8, + using_ascii_letters=True) + + +def set_postgres_password(file_path: str) -> None: + replace_flag_with_random_string(file_path, '!!!SET POSTGRES_PASSWORD!!!', + length=30, + using_digits=True, + using_ascii_letters=True) def main(): @@ -201,11 +235,17 @@ def main(): append_to_gitignore('.envs/') append_to_gitignore('.env') - set_secret_key(os.path.join(PROJECT_DIR_PATH, 'config', 'settings', 'local.py')) - set_secret_key(os.path.join(PROJECT_DIR_PATH, 'config', 'settings', 'test.py')) - set_secret_key(os.path.join(PROJECT_DIR_PATH, '.envs', '.production', '.django')) - set_secret_key(os.path.join(PROJECT_DIR_PATH, '.envs', '.local', '.postgres')) - set_secret_key(os.path.join(PROJECT_DIR_PATH, '.envs', '.production', '.postgres')) + set_django_secret_key(os.path.join(PROJECT_DIR_PATH, 'config', 'settings', 'local.py')) + set_django_secret_key(os.path.join(PROJECT_DIR_PATH, 'config', 'settings', 'test.py')) + set_django_secret_key(os.path.join(PROJECT_DIR_PATH, '.envs', '.production', '.django')) + + envs_local_postgres = os.path.join(PROJECT_DIR_PATH, '.envs', '.local', '.postgres') + set_postgres_user(envs_local_postgres) + set_postgres_password(envs_local_postgres) + + envs_production_postgres = os.path.join(PROJECT_DIR_PATH, '.envs', '.production', '.postgres') + set_postgres_user(envs_production_postgres) + set_postgres_password(envs_production_postgres) if __name__ == '__main__': diff --git a/{{cookiecutter.project_slug}}/.envs/.local/.postgres b/{{cookiecutter.project_slug}}/.envs/.local/.postgres index 7fbedf3aa..f795886d9 100644 --- a/{{cookiecutter.project_slug}}/.envs/.local/.postgres +++ b/{{cookiecutter.project_slug}}/.envs/.local/.postgres @@ -1,2 +1,2 @@ POSTGRES_USER={{cookiecutter.project_slug}} -POSTGRES_PASSWORD=CHANGEME!!! +POSTGRES_PASSWORD=!!!SET POSTGRES_PASSWORD!!! diff --git a/{{cookiecutter.project_slug}}/.envs/.production/.django b/{{cookiecutter.project_slug}}/.envs/.production/.django index 009186c9a..729e3e63d 100644 --- a/{{cookiecutter.project_slug}}/.envs/.production/.django +++ b/{{cookiecutter.project_slug}}/.envs/.production/.django @@ -1,6 +1,6 @@ # DJANGO_READ_DOT_ENV_FILE=True DJANGO_SETTINGS_MODULE=config.settings.production -DJANGO_SECRET_KEY=CHANGEME!!! +DJANGO_SECRET_KEY=!!!SET DJANGO_SECRET_KEY!!! DJANGO_ALLOWED_HOSTS=.{{ cookiecutter.domain_name }} DJANGO_ADMIN_URL= diff --git a/{{cookiecutter.project_slug}}/.envs/.production/.postgres b/{{cookiecutter.project_slug}}/.envs/.production/.postgres index d7240b68d..b5295478d 100644 --- a/{{cookiecutter.project_slug}}/.envs/.production/.postgres +++ b/{{cookiecutter.project_slug}}/.envs/.production/.postgres @@ -1,2 +1,2 @@ -POSTGRES_USER= -POSTGRES_PASSWORD=CHANGEME!!! +POSTGRES_USER=!!!SET POSTGRES_USER!!! +POSTGRES_PASSWORD=!!!SET POSTGRES_PASSWORD!!! diff --git a/{{cookiecutter.project_slug}}/config/settings/local.py b/{{cookiecutter.project_slug}}/config/settings/local.py index efbaf30b2..a4ace944a 100644 --- a/{{cookiecutter.project_slug}}/config/settings/local.py +++ b/{{cookiecutter.project_slug}}/config/settings/local.py @@ -24,7 +24,7 @@ TEMPLATES[0]['OPTIONS']['debug'] = DEBUG # ------------------------------------------------------------------------------ # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # Note: This key only used for development and testing. -SECRET_KEY = env('DJANGO_SECRET_KEY', default='CHANGEME!!!') +SECRET_KEY = env('DJANGO_SECRET_KEY', default='!!!SET DJANGO_SECRET_KEY!!!') # Mail settings # ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/config/settings/test.py b/{{cookiecutter.project_slug}}/config/settings/test.py index 0cd3e665a..e110a7dd2 100644 --- a/{{cookiecutter.project_slug}}/config/settings/test.py +++ b/{{cookiecutter.project_slug}}/config/settings/test.py @@ -17,7 +17,7 @@ TEMPLATES[0]['OPTIONS']['debug'] = False # ------------------------------------------------------------------------------ # See: https://docs.djangoproject.com/en/dev/ref/settings/#secret-key # Note: This key only used for development and testing. -SECRET_KEY = env('DJANGO_SECRET_KEY', default='CHANGEME!!!') +SECRET_KEY = env('DJANGO_SECRET_KEY', default='!!!SET DJANGO_SECRET_KEY!!!') # Mail settings # ------------------------------------------------------------------------------ diff --git a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py index 48c02eb39..148e1ec23 100644 --- a/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py +++ b/{{cookiecutter.project_slug}}/merge_production_dotenvs_in_dotenv.py @@ -1,7 +1,10 @@ import os +from typing import Sequence + import pytest PROJECT_DIR_PATH = os.path.dirname(os.path.realpath(__file__)) + PRODUCTION_DOTENV_DIR_PATH = os.path.join(PROJECT_DIR_PATH, '.envs', '.production') PRODUCTION_DOTENV_FILE_PATHS = [ os.path.join(PRODUCTION_DOTENV_DIR_PATH, '.django'), @@ -11,8 +14,10 @@ PRODUCTION_DOTENV_FILE_PATHS = [ DOTENV_FILE_PATH = os.path.join(PROJECT_DIR_PATH, '.env') -def merge(output_file_path, merged_file_paths, append_linesep=True): - with open(output_file_path, 'w+') as output_file: +def merge(output_file_path: str, + merged_file_paths: Sequence[str], + append_linesep: bool = True) -> None: + with open(output_file_path, 'w') as output_file: for merged_file_path in merged_file_paths: with open(merged_file_path, 'r') as merged_file: merged_file_content = merged_file.read()