EDUCATOR-4498 | Add optional output-file option to generate_jwt_signing_key command.

This commit is contained in:
Alex Dusenbery
2019-07-17 10:56:03 -04:00
committed by Alex Dusenbery
parent ad2444204a
commit ba2f0725ee
2 changed files with 36 additions and 4 deletions

View File

@@ -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',
}

View File

@@ -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)