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:
		@ -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",
 | 
				
			||||||
        ]
 | 
					        ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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())
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
@ -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
 | 
				
			||||||
 | 
					                label=${t`Configuration`}
 | 
				
			||||||
 | 
					                name="config">
 | 
				
			||||||
 | 
					                <ak-codemirror mode="yaml" value="${until(new OutpostsApi(DEFAULT_CONFIG).outpostsOutpostsDefaultSettings({}).then(config => {
 | 
				
			||||||
                        let fc = config.config;
 | 
					                        let fc = config.config;
 | 
				
			||||||
                        if (this.outpost) {
 | 
					                        if (this.outpost) {
 | 
				
			||||||
                            fc = this.outpost.config;
 | 
					                            fc = this.outpost.config;
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                return html`<ak-form-element-horizontal
 | 
					                        return YAML.stringify(fc);
 | 
				
			||||||
                    label=${t`Configuration`}
 | 
					                    }))}"></ak-codemirror>
 | 
				
			||||||
                    name="config">
 | 
					 | 
				
			||||||
                    <ak-codemirror mode="yaml" value="${YAML.stringify(fc)}"></ak-codemirror>
 | 
					 | 
				
			||||||
                <p class="pf-c-form__helper-text">${t`Set custom attributes using YAML or JSON.`}</p>
 | 
					                <p class="pf-c-form__helper-text">${t`Set custom attributes using YAML or JSON.`}</p>
 | 
				
			||||||
                </ak-form-element-horizontal>`;
 | 
					            </ak-form-element-horizontal>
 | 
				
			||||||
            }))}
 | 
					 | 
				
			||||||
        </form>`;
 | 
					        </form>`;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user