From ba2f0725ee3fb3d22703197c0d819070600c0f63 Mon Sep 17 00:00:00 2001 From: Alex Dusenbery Date: Wed, 17 Jul 2019 10:56:03 -0400 Subject: [PATCH] EDUCATOR-4498 | Add optional output-file option to generate_jwt_signing_key command. --- .../commands/generate_jwt_signing_key.py | 25 +++++++++++++++++-- .../tests/test_generate_jwt_signing_key.py | 15 +++++++++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/openedx/core/djangoapps/oauth_dispatch/management/commands/generate_jwt_signing_key.py b/openedx/core/djangoapps/oauth_dispatch/management/commands/generate_jwt_signing_key.py index 0d6e2ba1ef..71f22cf020 100644 --- a/openedx/core/djangoapps/oauth_dispatch/management/commands/generate_jwt_signing_key.py +++ b/openedx/core/djangoapps/oauth_dispatch/management/commands/generate_jwt_signing_key.py @@ -10,6 +10,7 @@ import random import string from argparse import RawTextHelpFormatter +import yaml from Cryptodome.PublicKey import RSA from django.conf import settings from django.core.management.base import BaseCommand @@ -66,6 +67,12 @@ class Command(BaseCommand): dest='add_previous_public_keys', help='Whether to NOT add the previous set of public keys to the new public key set', ) + parser.add_argument( + '--output-file', + action='store', + type=str, + help='Optional YML file in which output should be stored. Needs to be absolute path.', + ) group = parser.add_mutually_exclusive_group() group.add_argument( @@ -88,8 +95,15 @@ class Command(BaseCommand): options['key_size'], options['key_id'] or self._generate_key_id(options['key_id_size']), ) - self._output_public_keys(jwk_key, options['add_previous_public_keys']) - self._output_private_keys(jwk_key) + public_keys = self._output_public_keys(jwk_key, options['add_previous_public_keys']) + private_keys = self._output_private_keys(jwk_key) + if options['output_file']: + jwt_auth_data = { + 'JWT_AUTH': public_keys, + } + jwt_auth_data['JWT_AUTH'].update(private_keys) + with open(options['output_file'], 'w') as f_out: # pylint: disable=open-builtin + yaml.safe_dump(jwt_auth_data, stream=f_out) def _generate_key_id(self, size, chars=string.ascii_uppercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) @@ -120,6 +134,9 @@ class Command(BaseCommand): ) print(" ") print(" COMMON_JWT_PUBLIC_SIGNING_JWK_SET: '{}'".format(serialized_public_keys)) + return { + 'COMMON_JWT_PUBLIC_SIGNING_JWK_SET': serialized_public_keys, + } def _add_previous_public_keys(self, public_keys): previous_signing_keys = settings.JWT_AUTH.get('JWT_PUBLIC_SIGNING_JWK_SET') @@ -144,3 +161,7 @@ class Command(BaseCommand): print(" EDXAPP_JWT_PRIVATE_SIGNING_JWK: '{}'".format(serialized_keypair_json)) print(" ") print(" EDXAPP_JWT_SIGNING_ALGORITHM: 'RS512'") + return { + 'EDXAPP_JWT_PRIVATE_SIGNING_JWK': serialized_keypair_json, + 'EDXAPP_JWT_SIGNING_ALGORITHM': 'RS512', + } diff --git a/openedx/core/djangoapps/oauth_dispatch/management/commands/tests/test_generate_jwt_signing_key.py b/openedx/core/djangoapps/oauth_dispatch/management/commands/tests/test_generate_jwt_signing_key.py index df81d09419..a1bbc93789 100644 --- a/openedx/core/djangoapps/oauth_dispatch/management/commands/tests/test_generate_jwt_signing_key.py +++ b/openedx/core/djangoapps/oauth_dispatch/management/commands/tests/test_generate_jwt_signing_key.py @@ -4,11 +4,14 @@ Tests the ``generate_jwt_signing_key`` management command. # pylint: disable=missing-docstring from __future__ import absolute_import +import os import sys +import tempfile from contextlib import contextmanager from StringIO import StringIO import ddt +import yaml from django.core.management import call_command from django.test import TestCase from mock import patch @@ -43,13 +46,18 @@ class TestGenerateJwtSigningKey(TestCase): ) self.assertEqual(log_message_exists, expected_to_exist) - def _assert_key_output(self, output_stream): + def _assert_key_output(self, output_stream, filename): expected_in_output = ( 'EDXAPP_JWT_PRIVATE_SIGNING_JWK', 'EDXAPP_JWT_SIGNING_ALGORITHM', 'COMMON_JWT_PUBLIC_SIGNING_JWK_SET' ) for expected in expected_in_output: self.assertIn(expected, output_stream.getvalue()) + with open(filename) as file_obj: # pylint: disable=open-builtin + output_from_yaml = yaml.safe_load(file_obj) + for expected in expected_in_output: + self.assertIn(expected, output_from_yaml['JWT_AUTH']) + def _assert_presence_of_old_keys(self, mock_log, add_previous_public_keys): self._assert_log_message(mock_log, 'Old JWT_PUBLIC_SIGNING_JWK_SET', expected_to_exist=add_previous_public_keys) @@ -73,11 +81,14 @@ class TestGenerateJwtSigningKey(TestCase): command_options['key_id'] = TEST_KEY_IDENTIFIER if key_id_size: command_options['key_id_size'] = key_id_size + _, filename = tempfile.mkstemp(suffix='.yml') + command_options['output_file'] = filename with self._captured_output() as (output_stream, _): with patch(LOGGER) as mock_log: call_command(COMMAND_NAME, **command_options) - self._assert_key_output(output_stream) + self._assert_key_output(output_stream, filename) self._assert_presence_of_old_keys(mock_log, add_previous_public_keys) self._assert_presence_of_key_id(mock_log, output_stream, provide_key_id, key_id_size) + os.remove(filename)