Commit 589d66e2 by Mushtaq Ali

Encrypt credentials - EDUCATOR-1392

parent ed7896fb
PEP8_THRESHOLD=50
PYLINT_THRESHOLD=855
PEP8_THRESHOLD=49
PYLINT_THRESHOLD=761
production-requirements:
pip install -r requirements.txt
......
......@@ -19,6 +19,9 @@ MANAGERS = ADMINS
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'insecure-secret-key'
# Set this value in the environment-specific files (e.g. local.py, production.py, test.py)
FERNET_KEYS = ['insecure-ferent-key']
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
......
"""
Local environment settings.
"""
from VEDA.settings.base import *
from VEDA.settings.utils import get_logger_config
......
import yaml
from os import environ
"""
Production environment settings.
"""
from VEDA.settings.base import *
from VEDA.utils import get_config
from VEDA.settings.utils import get_logger_config
......
......@@ -12,4 +12,6 @@ DATABASES = {
}
}
FERNET_KEYS = ['test-ferent-key']
LOGGING = get_logger_config(debug=False, dev_env=True, local_loglevel='DEBUG')
"""
Veda Admin.
"""
from django.contrib import admin
from VEDA_OS01.models import (
......@@ -7,6 +10,9 @@ from VEDA_OS01.models import (
class CourseAdmin(admin.ModelAdmin):
"""
Course Admin.
"""
ordering = ['institution']
list_display = [
'course_name',
......@@ -27,6 +33,9 @@ class CourseAdmin(admin.ModelAdmin):
class VideoAdmin(admin.ModelAdmin):
"""
Admin for Video model.
"""
model = Video
list_display = [
'edx_id',
......@@ -49,6 +58,9 @@ class VideoAdmin(admin.ModelAdmin):
class EncodeAdmin(admin.ModelAdmin):
"""
Admin for Encode model.
"""
model = Encode
ordering = ['encode_name']
list_display = [
......@@ -69,6 +81,9 @@ class EncodeAdmin(admin.ModelAdmin):
class URLAdmin(admin.ModelAdmin):
"""
Admin for URL model.
"""
model = URL
list_display = [
'video_id_get',
......@@ -93,16 +108,25 @@ class URLAdmin(admin.ModelAdmin):
class DestinationAdmin(admin.ModelAdmin):
"""
Admin for Destination model.
"""
model = Destination
list_display = ['destination_name', 'destination_active']
class InstitutionAdmin(admin.ModelAdmin):
"""
Admin for Institution model.
"""
model = Institution
list_display = ['institution_name', 'institution_code']
class VideoUploadAdmin(admin.ModelAdmin):
"""
Admin for VedaUpload model.
"""
model = VedaUpload
list_display = [
'client_information',
......@@ -114,10 +138,17 @@ class VideoUploadAdmin(admin.ModelAdmin):
class TranscriptCredentialsAdmin(admin.ModelAdmin):
"""
Admin for TranscriptCredentials model.
"""
model = TranscriptCredentials
exclude = ('api_key', 'api_secret')
class TranscriptProcessMetadataAdmin(admin.ModelAdmin):
"""
Admin for TranscriptProcessMetadata model.
"""
model = TranscriptProcessMetadata
......
# -*- coding: utf-8 -*-
# Generated by Django 1.9 on 2017-10-19 13:17
from __future__ import unicode_literals
from django.db import migrations
import fernet_fields.fields
class Migration(migrations.Migration):
dependencies = [
('VEDA_OS01', '0002_auto_20171016_1211'),
]
operations = [
migrations.AlterField(
model_name='transcriptcredentials',
name='api_key',
field=fernet_fields.fields.EncryptedTextField(max_length=255, verbose_name=b'API key'),
),
migrations.AlterField(
model_name='transcriptcredentials',
name='api_secret',
field=fernet_fields.fields.EncryptedTextField(max_length=255, verbose_name=b'API secret'),
),
]
......@@ -4,11 +4,13 @@ Models for Video Pipeline
import json
import uuid
from django.db import models
from fernet_fields import EncryptedTextField
from model_utils.models import TimeStampedModel
from opaque_keys import InvalidKeyError
from opaque_keys.edx.keys import CourseKey
def _createHex():
return uuid.uuid1().hex
......@@ -216,7 +218,7 @@ class ListField(models.TextField):
return value
class Institution (models.Model):
class Institution(models.Model):
institution_code = models.CharField(max_length=4)
institution_name = models.CharField(max_length=50)
......@@ -227,7 +229,10 @@ class Institution (models.Model):
)
class Course (models.Model):
class Course(models.Model):
"""
Model for Course.
"""
course_name = models.CharField('Course Name', max_length=100)
# TODO: Change Name (this is reversed)
......@@ -407,7 +412,10 @@ class Course (models.Model):
)
class Video (models.Model):
class Video(models.Model):
"""
Model for Video.
"""
# TODO: Change field name
inst_class = models.ForeignKey(Course)
video_active = models.BooleanField('Video Active?', default=True)
......@@ -513,7 +521,10 @@ class Video (models.Model):
return u'{edx_id}'.format(edx_id=self.edx_id)
class Destination (models.Model):
class Destination(models.Model):
"""
Model for Destination.
"""
destination_name = models.CharField('Destination', max_length=200, null=True, blank=True)
destination_active = models.BooleanField('Destination Active', default=False)
destination_nick = models.CharField('Nickname (3 Char.)', max_length=3, null=True, blank=True)
......@@ -522,7 +533,10 @@ class Destination (models.Model):
return u'%s'.format(self.destination_name) or u''
class Encode (models.Model):
class Encode(models.Model):
"""
Model for Encode.
"""
encode_destination = models.ForeignKey(Destination)
encode_name = models.CharField('Encode Name', max_length=100, null=True, blank=True)
profile_active = models.BooleanField('Encode Profile Active', default=False)
......@@ -570,7 +584,10 @@ class Encode (models.Model):
return u'{encode_profile}'.format(encode_profile=self.encode_name)
class URL (models.Model):
class URL(models.Model):
"""
Model for URL.
"""
encode_profile = models.ForeignKey(Encode)
videoID = models.ForeignKey(Video)
encode_url = models.CharField('Destination URL', max_length=500, null=True, blank=True)
......@@ -597,7 +614,7 @@ class URL (models.Model):
)
class VedaUpload (models.Model):
class VedaUpload(models.Model):
"""
Internal Upload Tool
"""
......@@ -664,8 +681,8 @@ class TranscriptCredentials(TimeStampedModel):
help_text='This value must match the value of organization in studio/edx-platform.'
)
provider = models.CharField('Transcript provider', max_length=50, choices=TranscriptProvider.CHOICES)
api_key = models.CharField('API key', max_length=255)
api_secret = models.CharField('API secret', max_length=255, null=True, blank=True)
api_key = EncryptedTextField('API key', max_length=255)
api_secret = EncryptedTextField('API secret', max_length=255)
class Meta:
unique_together = ('org', 'provider')
......
""" Model tests """
from django.conf import settings
from django.test import override_settings
from django.test.testcases import TransactionTestCase
from cryptography.fernet import InvalidToken
from VEDA_OS01.models import TranscriptCredentials, TranscriptProvider
class TranscriptCredentialsModelTest(TransactionTestCase):
"""
Transcript credentials model tests
"""
def setUp(self):
"""
Test setup.
"""
self.credentials_data = {
'org': 'MAx',
'provider': TranscriptProvider.THREE_PLAY,
'api_key': 'test-key',
'api_secret': 'test-secret'
}
TranscriptCredentials.objects.create(**self.credentials_data)
def tearDown(self):
"""
Test teardown.
"""
# Invalidate here so that evry new test would have FERNET KEYS from tests.py initially.
self.invalidate_fernet_cached_properties()
def invalidate_fernet_cached_properties(self):
"""
Invalidates transcript credential fernet field's cached properties.
"""
def invalidate_fernet_cached_property(field_name):
"""
Invalidates fernet fields cached properties.
"""
field = TranscriptCredentials._meta.get_field(field_name)
if field.keys:
del field.keys
if field.fernet_keys:
del field.fernet_keys
if field.fernet:
del field.fernet
invalidate_fernet_cached_property('api_key')
invalidate_fernet_cached_property('api_secret')
def test_decrypt(self):
"""
Tests transcript credential fields are correctly decrypted.
"""
# Verify that api key is correctly fetched.
transcript_credentials = TranscriptCredentials.objects.get(
org=self.credentials_data['org'], provider=self.credentials_data['provider']
)
self.assertEqual(transcript_credentials.api_key, self.credentials_data['api_key'])
self.assertEqual(transcript_credentials.api_secret, self.credentials_data['api_secret'])
def test_decrypt_different_key(self):
"""
Tests decryption with one more key pre-pended. Note that we still have the old key with which value was
encrypted so we should be able to decrypt it again.
"""
old_keys_set = ['test-ferent-key']
self.assertEqual(settings.FERNET_KEYS, old_keys_set)
new_keys_set = ['new-fernet-key'] + settings.FERNET_KEYS
# Invalidate cached properties so that we get the latest keys
self.invalidate_fernet_cached_properties()
with override_settings(FERNET_KEYS=new_keys_set):
self.assertEqual(settings.FERNET_KEYS, new_keys_set)
transcript_credentials = TranscriptCredentials.objects.get(
org=self.credentials_data['org'], provider=self.credentials_data['provider']
)
self.assertEqual(transcript_credentials.api_key, self.credentials_data['api_key'])
self.assertEqual(transcript_credentials.api_secret, self.credentials_data['api_secret'])
def test_decrypt_different_key_set(self):
"""
Tests decryption with different fernet key set. Note that now we don't have the old fernet key with which
value was encrypted so we would not be able to decrypt it and we should get an Invalid Token.
"""
old_keys_set = ['test-ferent-key']
self.assertEqual(settings.FERNET_KEYS, old_keys_set)
new_keys_set = ['new-fernet-key']
# Invalidate cached properties so that we get the latest keys
self.invalidate_fernet_cached_properties()
with override_settings(FERNET_KEYS=new_keys_set):
self.assertEqual(settings.FERNET_KEYS, new_keys_set)
with self.assertRaises(InvalidToken):
TranscriptCredentials.objects.get(
org=self.credentials_data['org'], provider=self.credentials_data['provider']
)
......@@ -21,6 +21,9 @@ DATABASES:
SECRET_KEY: ""
# Fernet keys
FERNET_KEYS: []
# Django DEBUG global
# (set to false in prod)
debug: false
......
......@@ -53,7 +53,7 @@
#
# ------------------------------
[MASTER]
ignore = pavelib_test_code
ignore = pavelib_test_code, migrations
persistent = yes
load-plugins = edx_lint.pylint,pylint_django,pylint_celery
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment