outposts: improve API validation for config attribute, ensure all required attributes are set

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-05-10 19:24:42 +02:00
parent 8eaaaae2a7
commit cd629dfbaa
4 changed files with 50 additions and 19 deletions

View File

@ -1,23 +1,33 @@
"""Outpost API Views""" """Outpost API Views"""
from dacite.core import from_dict
from dacite.exceptions import DaciteError
from drf_yasg.utils import swagger_auto_schema from drf_yasg.utils import swagger_auto_schema
from rest_framework.decorators import action from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, DateTimeField from rest_framework.fields import BooleanField, CharField, DateTimeField
from rest_framework.request import Request from rest_framework.request import Request
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.serializers import JSONField, ModelSerializer from rest_framework.serializers import JSONField, ModelSerializer, ValidationError
from rest_framework.viewsets import ModelViewSet from rest_framework.viewsets import ModelViewSet
from authentik.core.api.providers import ProviderSerializer from authentik.core.api.providers import ProviderSerializer
from authentik.core.api.utils import PassiveSerializer, is_dict from authentik.core.api.utils import PassiveSerializer, is_dict
from authentik.outposts.models import Outpost, default_outpost_config from authentik.outposts.models import Outpost, OutpostConfig, default_outpost_config
class OutpostSerializer(ModelSerializer): class OutpostSerializer(ModelSerializer):
"""Outpost Serializer""" """Outpost Serializer"""
_config = JSONField(validators=[is_dict]) config = JSONField(validators=[is_dict], source="_config")
providers_obj = ProviderSerializer(source="providers", many=True, read_only=True) providers_obj = ProviderSerializer(source="providers", many=True, read_only=True)
def validate_config(self, config) -> dict:
"""Check that the config has all required fields"""
try:
from_dict(OutpostConfig, config)
except DaciteError as exc:
raise ValidationError(f"Failed to validate config: {str(exc)}") from exc
return config
class Meta: class Meta:
model = Outpost model = Outpost
@ -29,7 +39,7 @@ class OutpostSerializer(ModelSerializer):
"providers_obj", "providers_obj",
"service_connection", "service_connection",
"token_identifier", "token_identifier",
"_config", "config",
] ]

View File

@ -3,6 +3,10 @@ from django.urls import reverse
from rest_framework.test import APITestCase from rest_framework.test import APITestCase
from authentik.core.models import PropertyMapping, User from authentik.core.models import PropertyMapping, User
from authentik.flows.models import Flow
from authentik.outposts.api.outposts import OutpostSerializer
from authentik.outposts.models import default_outpost_config
from authentik.providers.proxy.models import ProxyProvider
class TestOutpostServiceConnectionsAPI(APITestCase): class TestOutpostServiceConnectionsAPI(APITestCase):
@ -22,3 +26,20 @@ class TestOutpostServiceConnectionsAPI(APITestCase):
reverse("authentik_api:outpostserviceconnection-types"), reverse("authentik_api:outpostserviceconnection-types"),
) )
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_outpost_config(self):
"""Test Outpost's config field"""
provider = ProxyProvider.objects.create(name="test", authorization_flow=Flow.objects.first())
invalid = OutpostSerializer(data={
"name": "foo",
"providers": [provider.pk],
"config": {}
})
self.assertFalse(invalid.is_valid())
self.assertIn("config", invalid.errors)
valid = OutpostSerializer(data={
"name": "foo",
"providers": [provider.pk],
"config": default_outpost_config("foo")
})
self.assertTrue(valid.is_valid())

View File

@ -16198,7 +16198,7 @@ definitions:
required: required:
- name - name
- providers - providers
- _config - config
type: object type: object
properties: properties:
pk: pk:
@ -16237,8 +16237,8 @@ definitions:
title: Token identifier title: Token identifier
type: string type: string
readOnly: true readOnly: true
_config: config:
title: config title: Config
type: object type: object
OutpostDefaultConfig: OutpostDefaultConfig:
type: object type: object

View File

@ -102,18 +102,18 @@ export class OutpostForm extends Form<Outpost> {
</select> </select>
<p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p> <p class="pf-c-form__helper-text">${t`Hold control/command to select multiple items.`}</p>
</ak-form-element-horizontal> </ak-form-element-horizontal>
${until(new OutpostsApi(DEFAULT_CONFIG).outpostsOutpostsDefaultSettings({}).then(config => { <ak-form-element-horizontal
let fc = config.config; label=${t`Configuration`}
if (this.outpost) { name="config">
fc = this.outpost.config; <ak-codemirror mode="yaml" value="${until(new OutpostsApi(DEFAULT_CONFIG).outpostsOutpostsDefaultSettings({}).then(config => {
} let fc = config.config;
return html`<ak-form-element-horizontal if (this.outpost) {
label=${t`Configuration`} fc = this.outpost.config;
name="config"> }
<ak-codemirror mode="yaml" value="${YAML.stringify(fc)}"></ak-codemirror> return YAML.stringify(fc);
<p class="pf-c-form__helper-text">${t`Set custom attributes using YAML or JSON.`}</p> }))}"></ak-codemirror>
</ak-form-element-horizontal>`; <p class="pf-c-form__helper-text">${t`Set custom attributes using YAML or JSON.`}</p>
}))} </ak-form-element-horizontal>
</form>`; </form>`;
} }