mirror of
https://github.com/ansible/awx.git
synced 2026-04-05 01:59:25 -02:30
automatically encrypt/decrypt main_oauth2application.client_secret
see: https://github.com/ansible/awx/issues/1416
This commit is contained in:
@@ -42,6 +42,7 @@ from rest_framework import serializers
|
|||||||
|
|
||||||
# AWX
|
# AWX
|
||||||
from awx.main.utils.filters import SmartFilter
|
from awx.main.utils.filters import SmartFilter
|
||||||
|
from awx.main.utils.encryption import encrypt_value, decrypt_value, get_encryption_key
|
||||||
from awx.main.validators import validate_ssh_private_key
|
from awx.main.validators import validate_ssh_private_key
|
||||||
from awx.main.models.rbac import batch_role_ancestor_rebuilding, Role
|
from awx.main.models.rbac import batch_role_ancestor_rebuilding, Role
|
||||||
from awx.main import utils
|
from awx.main import utils
|
||||||
@@ -821,3 +822,16 @@ class AskForField(models.BooleanField):
|
|||||||
# self.name will be set by the model metaclass, not this field
|
# self.name will be set by the model metaclass, not this field
|
||||||
raise Exception('Corresponding allows_field cannot be accessed until model is initialized.')
|
raise Exception('Corresponding allows_field cannot be accessed until model is initialized.')
|
||||||
return self._allows_field
|
return self._allows_field
|
||||||
|
|
||||||
|
|
||||||
|
class OAuth2ClientSecretField(models.CharField):
|
||||||
|
|
||||||
|
def get_db_prep_value(self, value, connection, prepared=False):
|
||||||
|
return super(OAuth2ClientSecretField, self).get_db_prep_value(
|
||||||
|
encrypt_value(value), connection, prepared
|
||||||
|
)
|
||||||
|
|
||||||
|
def from_db_value(self, value, expression, connection, context):
|
||||||
|
if value.startswith('$encrypted$'):
|
||||||
|
return decrypt_value(get_encryption_key('value', pk=None), value)
|
||||||
|
return value
|
||||||
|
|||||||
22
awx/main/migrations/0029_v330_encrypt_oauth2_secret.py
Normal file
22
awx/main/migrations/0029_v330_encrypt_oauth2_secret.py
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.11 on 2018-04-03 20:48
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
import awx.main.fields
|
||||||
|
from django.db import migrations
|
||||||
|
import oauth2_provider.generators
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('main', '0028_v330_modify_application'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='oauth2application',
|
||||||
|
name='client_secret',
|
||||||
|
field=awx.main.fields.OAuth2ClientSecretField(blank=True, db_index=True, default=oauth2_provider.generators.generate_client_secret, max_length=1024),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -9,6 +9,9 @@ from django.utils.translation import ugettext_lazy as _
|
|||||||
|
|
||||||
# Django OAuth Toolkit
|
# Django OAuth Toolkit
|
||||||
from oauth2_provider.models import AbstractApplication, AbstractAccessToken
|
from oauth2_provider.models import AbstractApplication, AbstractAccessToken
|
||||||
|
from oauth2_provider.generators import generate_client_secret
|
||||||
|
|
||||||
|
from awx.main.fields import OAuth2ClientSecretField
|
||||||
|
|
||||||
|
|
||||||
DATA_URI_RE = re.compile(r'.*') # FIXME
|
DATA_URI_RE = re.compile(r'.*') # FIXME
|
||||||
@@ -39,6 +42,10 @@ class OAuth2Application(AbstractApplication):
|
|||||||
null=True,
|
null=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
client_secret = OAuth2ClientSecretField(
|
||||||
|
max_length=1024, blank=True, default=generate_client_secret, db_index=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class OAuth2AccessToken(AbstractAccessToken):
|
class OAuth2AccessToken(AbstractAccessToken):
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import pytest
|
import pytest
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
from django.db import connection
|
||||||
|
|
||||||
|
from awx.main.utils.encryption import decrypt_value, get_encryption_key
|
||||||
from awx.api.versioning import reverse, drf_reverse
|
from awx.api.versioning import reverse, drf_reverse
|
||||||
from awx.main.models.oauth import (OAuth2Application as Application,
|
from awx.main.models.oauth import (OAuth2Application as Application,
|
||||||
OAuth2AccessToken as AccessToken,
|
OAuth2AccessToken as AccessToken,
|
||||||
@@ -65,6 +68,26 @@ def test_oauth_application_update(oauth_application, organization, patch, admin,
|
|||||||
assert updated_app.organization == organization
|
assert updated_app.organization == organization
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_oauth_application_encryption(admin, organization, post):
|
||||||
|
response = post(
|
||||||
|
reverse('api:o_auth2_application_list'), {
|
||||||
|
'name': 'test app',
|
||||||
|
'organization': organization.pk,
|
||||||
|
'client_type': 'confidential',
|
||||||
|
'authorization_grant_type': 'password',
|
||||||
|
}, admin, expect=201
|
||||||
|
)
|
||||||
|
pk = response.data.get('id')
|
||||||
|
secret = response.data.get('client_secret')
|
||||||
|
with connection.cursor() as cursor:
|
||||||
|
encrypted = cursor.execute(
|
||||||
|
'SELECT client_secret FROM main_oauth2application WHERE id={}'.format(pk)
|
||||||
|
).fetchone()[0]
|
||||||
|
assert encrypted.startswith('$encrypted$')
|
||||||
|
assert decrypt_value(get_encryption_key('value', pk=None), encrypted) == secret
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db
|
@pytest.mark.django_db
|
||||||
def test_oauth_token_create(oauth_application, get, post, admin):
|
def test_oauth_token_create(oauth_application, get, post, admin):
|
||||||
response = post(
|
response = post(
|
||||||
|
|||||||
Reference in New Issue
Block a user