From 98f2d936d94d50761f32d05f8493dd7ea2258d27 Mon Sep 17 00:00:00 2001 From: Chris Meyers Date: Fri, 17 Nov 2017 09:25:50 -0500 Subject: [PATCH] allow support for saml + 2-factor * python-social-auth has SOCIAL_AUTH_SAML_SECURITY_CONFIG, which is forwarded to python-saml settings configuration. This commit exposes SOCIAL_AUTH_SAML_SECURITY_CONFIG to configure tower in tower to allow users to set requestedAuthnContext, which will disable the requesting of password type auth from the idp. Thus, it's up to the idp to choose which auth to use (i.e. 2-factor). --- awx/conf/fields.py | 36 ++++++++++++++++++++++++++++ awx/sso/conf.py | 19 +++++++++++++++ awx/sso/fields.py | 58 +++++++++++++++++++++------------------------- 3 files changed, 81 insertions(+), 32 deletions(-) diff --git a/awx/conf/fields.py b/awx/conf/fields.py index 9a748593c7..8e93ad7ad3 100644 --- a/awx/conf/fields.py +++ b/awx/conf/fields.py @@ -53,6 +53,42 @@ class StringListField(ListField): return super(StringListField, self).to_representation(value) +class StringListBooleanField(ListField): + + default_error_messages = { + 'type_error': _('Expected None, True, False, a string or list of strings but got {input_type} instead.'), + } + child = CharField() + + def to_representation(self, value): + if isinstance(value, (list, tuple)): + return super(StringListBooleanField, self).to_representation(value) + elif value in fields.NullBooleanField.TRUE_VALUES: + return True + elif value in fields.NullBooleanField.FALSE_VALUES: + return False + elif value in fields.NullBooleanField.NULL_VALUES: + return None + elif isinstance(value, basestring): + return self.child.to_representation(value) + else: + self.fail('type_error', input_type=type(value)) + + def to_internal_value(self, data): + if isinstance(data, (list, tuple)): + return super(StringListBooleanField, self).to_internal_value(data) + elif data in fields.NullBooleanField.TRUE_VALUES: + return True + elif data in fields.NullBooleanField.FALSE_VALUES: + return False + elif data in fields.NullBooleanField.NULL_VALUES: + return None + elif isinstance(data, basestring): + return self.child.run_validation(data) + else: + self.fail('type_error', input_type=type(data)) + + class URLField(CharField): def __init__(self, **kwargs): diff --git a/awx/sso/conf.py b/awx/sso/conf.py index c75ec4e250..001c527f45 100644 --- a/awx/sso/conf.py +++ b/awx/sso/conf.py @@ -70,6 +70,11 @@ SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER = collections.OrderedDict([ ])), ]) +SOCIAL_AUTH_SAML_SECURITY_CONFIG_HELP_TEXT = _('''\ +Extra https://github.com/onelogin/python-saml#settings\ +''') + + ############################################################################### # AUTHENTICATION BACKENDS DYNAMIC SETTING ############################################################################### @@ -1061,6 +1066,20 @@ register( feature_required='enterprise_auth', ) +register( + 'SOCIAL_AUTH_SAML_SECURITY_CONFIG', + field_class=fields.SAMLSecurityField, + allow_null=True, + default=None, + label=_('SAML Security Config'), + help_text=SOCIAL_AUTH_SAML_SECURITY_CONFIG_HELP_TEXT, + category=_('SAML'), + category_slug='saml', + #placeholder=SOCIAL_AUTH_TEAM_MAP_PLACEHOLDER, + placeholder=collections.OrderedDict(), + feature_required='enterprise_auth', +) + register( 'SOCIAL_AUTH_SAML_ORGANIZATION_MAP', field_class=fields.SocialOrganizationMapField, diff --git a/awx/sso/fields.py b/awx/sso/fields.py index c2400bae31..a483044464 100644 --- a/awx/sso/fields.py +++ b/awx/sso/fields.py @@ -345,41 +345,10 @@ class LDAPUserFlagsField(fields.DictField): return data -class LDAPDNMapField(fields.ListField): +class LDAPDNMapField(fields.StringListBooleanField): - default_error_messages = { - 'type_error': _('Expected None, True, False, a string or list of strings but got {input_type} instead.'), - } child = LDAPDNField() - def to_representation(self, value): - if isinstance(value, (list, tuple)): - return super(LDAPDNMapField, self).to_representation(value) - elif value in fields.NullBooleanField.TRUE_VALUES: - return True - elif value in fields.NullBooleanField.FALSE_VALUES: - return False - elif value in fields.NullBooleanField.NULL_VALUES: - return None - elif isinstance(value, basestring): - return self.child.to_representation(value) - else: - self.fail('type_error', input_type=type(value)) - - def to_internal_value(self, data): - if isinstance(data, (list, tuple)): - return super(LDAPDNMapField, self).to_internal_value(data) - elif data in fields.NullBooleanField.TRUE_VALUES: - return True - elif data in fields.NullBooleanField.FALSE_VALUES: - return False - elif data in fields.NullBooleanField.NULL_VALUES: - return None - elif isinstance(data, basestring): - return self.child.run_validation(data) - else: - self.fail('type_error', input_type=type(data)) - class BaseDictWithChildField(fields.DictField): @@ -649,3 +618,28 @@ class SAMLIdPField(BaseDictWithChildField): class SAMLEnabledIdPsField(fields.DictField): child = SAMLIdPField() + + +class SAMLSecurityField(fields.DictField): + + child_fields = { + 'nameIdEncrypted': fields.BooleanField(required=False), + 'authnRequestsSigned': fields.BooleanField(required=False), + 'logoutRequestSigned': fields.BooleanField(required=False), + 'logoutResponseSigned': fields.BooleanField(required=False), + 'signMetadata': fields.BooleanField(required=False), + 'wantMessagesSigned': fields.BooleanField(required=False), + 'wantAssertionsSigned': fields.BooleanField(required=False), + 'wantAssertionsEncrypted': fields.BooleanField(required=False), + 'wantNameId': fields.BooleanField(required=False), + 'wantNameIdEncrypted': fields.BooleanField(required=False), + 'wantAttributeStatement': fields.BooleanField(required=False), + 'requestedAuthnContext': fields.StringListBooleanField(required=False), + 'requestedAuthnContextComparison': fields.CharField(required=False), + 'metadataValidUntil': fields.CharField(allow_null=True, required=False), + 'metadataCacheDuration': fields.CharField(allow_null=True, required=False), + 'signatureAlgorithm': fields.CharField(allow_null=True, required=False), + 'digestAlgorithm': fields.CharField(allow_null=True, required=False), + } + allow_unknown_keys = True +