From c4528612d5624e69c7c307dad889aa2557e2e1bf Mon Sep 17 00:00:00 2001 From: ibuler Date: Tue, 15 Aug 2023 13:45:44 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BF=AE=E6=94=B9=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/settings/api/settings.py | 8 + apps/settings/api/sms.py | 12 +- apps/settings/serializers/__init__.py | 6 +- apps/settings/serializers/basic.py | 32 +--- apps/settings/serializers/feature.py | 80 +++++++++ .../settings/serializers/{email.py => msg.py} | 10 +- apps/settings/serializers/other.py | 20 +-- apps/settings/serializers/security.py | 158 +++++++++--------- apps/settings/serializers/settings.py | 2 +- apps/settings/serializers/sms.py | 7 - apps/settings/serializers/terminal.py | 6 + apps/settings/serializers/vault.py | 27 --- 12 files changed, 203 insertions(+), 165 deletions(-) create mode 100644 apps/settings/serializers/feature.py rename apps/settings/serializers/{email.py => msg.py} (90%) delete mode 100644 apps/settings/serializers/sms.py delete mode 100644 apps/settings/serializers/vault.py diff --git a/apps/settings/api/settings.py b/apps/settings/api/settings.py index ab7be6e9b..31a25784c 100644 --- a/apps/settings/api/settings.py +++ b/apps/settings/api/settings.py @@ -28,6 +28,11 @@ class SettingsApi(generics.RetrieveUpdateAPIView): 'basic': serializers.BasicSettingSerializer, 'terminal': serializers.TerminalSettingSerializer, 'security': serializers.SecuritySettingSerializer, + 'security_auth': serializers.SecurityAuthSerializer, + 'security_basic': serializers.SecurityBasicSerializer, + 'security_session': serializers.SecuritySessionSerializer, + 'security_password': serializers.SecurityPasswordRuleSerializer, + 'security_login_limit': serializers.SecurityLoginLimitSerializer, 'ldap': serializers.LDAPSettingSerializer, 'email': serializers.EmailSettingSerializer, 'email_content': serializers.EmailContentSettingSerializer, @@ -51,6 +56,9 @@ class SettingsApi(generics.RetrieveUpdateAPIView): 'cmpp2': serializers.CMPP2SMSSettingSerializer, 'custom': serializers.CustomSMSSettingSerializer, 'vault': serializers.VaultSettingSerializer, + 'announcement': serializers.AnnouncementSettingSerializer, + 'ticket': serializers.TicketSettingSerializer, + } rbac_category_permissions = { diff --git a/apps/settings/api/sms.py b/apps/settings/api/sms.py index 01fca4436..ec767828d 100644 --- a/apps/settings/api/sms.py +++ b/apps/settings/api/sms.py @@ -1,18 +1,16 @@ import importlib - from collections import OrderedDict +from django.utils.translation import gettext_lazy as _ +from rest_framework import status +from rest_framework.exceptions import APIException from rest_framework.generics import ListAPIView, GenericAPIView from rest_framework.response import Response -from rest_framework.exceptions import APIException -from rest_framework import status -from django.utils.translation import gettext_lazy as _ -from common.sdk.sms import BACKENDS from common.exceptions import JMSException -from settings.serializers.sms import SMSBackendSerializer +from common.sdk.sms import BACKENDS from settings.models import Setting - +from settings.serializers import SMSBackendSerializer from .. import serializers diff --git a/apps/settings/serializers/__init__.py b/apps/settings/serializers/__init__.py index 7abfb74e3..fe94eb1da 100644 --- a/apps/settings/serializers/__init__.py +++ b/apps/settings/serializers/__init__.py @@ -4,11 +4,11 @@ from .auth import * from .basic import * from .cleaning import * -from .email import * +from .feature import * +from .msg import * +from .msg import * from .other import * from .public import * from .security import * from .settings import * from .terminal import * -from .vault import * - diff --git a/apps/settings/serializers/basic.py b/apps/settings/serializers/basic.py index 1201ee7cb..83c2b65ee 100644 --- a/apps/settings/serializers/basic.py +++ b/apps/settings/serializers/basic.py @@ -1,28 +1,7 @@ -import uuid - from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -class AnnouncementSerializer(serializers.Serializer): - ID = serializers.CharField(required=False, allow_blank=True, allow_null=True) - SUBJECT = serializers.CharField(required=True, max_length=1024, label=_("Subject")) - CONTENT = serializers.CharField(label=_("Content")) - LINK = serializers.URLField( - required=False, allow_null=True, allow_blank=True, - label=_("More url"), default='', - ) - - def to_representation(self, instance): - defaults = {'ID': '', 'SUBJECT': '', 'CONTENT': '', 'LINK': '', 'ENABLED': False} - data = {**defaults, **instance} - return super().to_representation(data) - - def to_internal_value(self, data): - data['ID'] = str(uuid.uuid4()) - return super().to_internal_value(data) - - class BasicSettingSerializer(serializers.Serializer): PREFIX_TITLE = _('Basic') @@ -43,9 +22,14 @@ class BasicSettingSerializer(serializers.Serializer): required=False, max_length=1024, allow_blank=True, allow_null=True, label=_("Global organization name"), help_text=_('The name of global organization to display') ) - ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True) - ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement")) - TICKETS_ENABLED = serializers.BooleanField(required=False, default=True, label=_("Enable tickets")) + HELP_DOCUMENT_URL = serializers.URLField( + required=False, allow_blank=True, allow_null=True, label=_("Help Docs URL"), + help_text=_('default: http://docs.jumpserver.org') + ) + HELP_SUPPORT_URL = serializers.URLField( + required=False, allow_blank=True, allow_null=True, label=_("Help Support URL"), + help_text=_('default: http://www.jumpserver.org/support/') + ) @staticmethod def validate_SITE_URL(s): diff --git a/apps/settings/serializers/feature.py b/apps/settings/serializers/feature.py new file mode 100644 index 000000000..8d8f40c4e --- /dev/null +++ b/apps/settings/serializers/feature.py @@ -0,0 +1,80 @@ +import uuid + +from django.utils.translation import gettext_lazy as _ +from rest_framework import serializers + +from accounts.const import VaultTypeChoices +from common.serializers.fields import EncryptedField + +__all__ = [ + 'AnnouncementSettingSerializer', + 'VaultSettingSerializer', 'TicketSettingSerializer' +] + + +class AnnouncementSerializer(serializers.Serializer): + ID = serializers.CharField(required=False, allow_blank=True, allow_null=True) + SUBJECT = serializers.CharField(required=True, max_length=1024, label=_("Subject")) + CONTENT = serializers.CharField(label=_("Content")) + LINK = serializers.URLField( + required=False, allow_null=True, allow_blank=True, + label=_("More url"), default='', + ) + + def to_representation(self, instance): + defaults = {'ID': '', 'SUBJECT': '', 'CONTENT': '', 'LINK': '', 'ENABLED': False} + data = {**defaults, **instance} + return super().to_representation(data) + + def to_internal_value(self, data): + data['ID'] = str(uuid.uuid4()) + return super().to_internal_value(data) + + +class AnnouncementSettingSerializer(serializers.Serializer): + ANNOUNCEMENT_ENABLED = serializers.BooleanField(label=_('Enable announcement'), default=True) + ANNOUNCEMENT = AnnouncementSerializer(label=_("Announcement")) + + +class VaultSettingSerializer(serializers.Serializer): + VAULT_TYPE = serializers.ChoiceField( + default=VaultTypeChoices.local, choices=VaultTypeChoices.choices, + required=False, label=_('Type') + ) + VAULT_HCP_HOST = serializers.CharField( + max_length=256, allow_blank=True, required=False, label=_('Host') + ) + VAULT_HCP_TOKEN = EncryptedField( + max_length=256, allow_blank=True, required=False, label=_('Token'), default='' + ) + VAULT_HCP_MOUNT_POINT = serializers.CharField( + max_length=256, allow_blank=True, required=False, label=_('Mount Point') + ) + + def validate(self, attrs): + attrs.pop('VAULT_TYPE', None) + return attrs + + +class TicketSettingSerializer(serializers.Serializer): + TICKETS_ENABLED = serializers.BooleanField(required=False, default=True, label=_("Enable tickets")) + TICKET_AUTHORIZE_DEFAULT_TIME = serializers.IntegerField( + min_value=1, max_value=999999, required=False, + label=_("Ticket authorize default time") + ) + TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.ChoiceField( + choices=[('day', _("day")), ('hour', _("hour"))], + label=_("Ticket authorize default time unit"), required=False, + ) + + +class OpsSettingSerializer(serializers.Serializer): + SECURITY_COMMAND_EXECUTION = serializers.BooleanField( + required=False, label=_('Operation center'), + help_text=_('Allow user run batch command or not using ansible') + ) + SECURITY_COMMAND_BLACKLIST = serializers.ListField( + child=serializers.CharField(max_length=1024, ), + label=_('Operation center command blacklist'), + help_text=_("Commands that are not allowed execute.") + ) diff --git a/apps/settings/serializers/email.py b/apps/settings/serializers/msg.py similarity index 90% rename from apps/settings/serializers/email.py rename to apps/settings/serializers/msg.py index db8f7545e..07e8e7205 100644 --- a/apps/settings/serializers/email.py +++ b/apps/settings/serializers/msg.py @@ -6,7 +6,10 @@ from rest_framework import serializers from common.serializers.fields import EncryptedField -__all__ = ['MailTestSerializer', 'EmailSettingSerializer', 'EmailContentSettingSerializer'] +__all__ = [ + 'MailTestSerializer', 'EmailSettingSerializer', + 'EmailContentSettingSerializer', 'SMSBackendSerializer', +] class MailTestSerializer(serializers.Serializer): @@ -73,3 +76,8 @@ class EmailContentSettingSerializer(serializers.Serializer): max_length=512, allow_blank=True, required=False, label=_('Signature'), help_text=_('Tips: Email signature (eg:jumpserver)') ) + + +class SMSBackendSerializer(serializers.Serializer): + name = serializers.CharField(max_length=256, required=True, label=_('Name')) + label = serializers.CharField(max_length=256, required=True, label=_('Label')) diff --git a/apps/settings/serializers/other.py b/apps/settings/serializers/other.py index 1bdfc9465..5b9ee654c 100644 --- a/apps/settings/serializers/other.py +++ b/apps/settings/serializers/other.py @@ -1,6 +1,8 @@ from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +__all__ = ['OtherSettingSerializer'] + class OtherSettingSerializer(serializers.Serializer): PREFIX_TITLE = _('More...') @@ -10,24 +12,6 @@ class OtherSettingSerializer(serializers.Serializer): help_text=_("Perm single to ungroup node") ) - TICKET_AUTHORIZE_DEFAULT_TIME = serializers.IntegerField( - min_value=1, max_value=999999, required=False, - label=_("Ticket authorize default time") - ) - TICKET_AUTHORIZE_DEFAULT_TIME_UNIT = serializers.ChoiceField( - choices=[('day', _("day")), ('hour', _("hour"))], - label=_("Ticket authorize default time unit"), required=False, - ) - HELP_DOCUMENT_URL = serializers.URLField( - required=False, allow_blank=True, allow_null=True, label=_("Help Docs URL"), - help_text=_('default: http://docs.jumpserver.org') - ) - - HELP_SUPPORT_URL = serializers.URLField( - required=False, allow_blank=True, allow_null=True, label=_("Help Support URL"), - help_text=_('default: http://www.jumpserver.org/support/') - ) - # 准备废弃 # PERIOD_TASK_ENABLED = serializers.BooleanField( # required=False, label=_("Enable period task") diff --git a/apps/settings/serializers/security.py b/apps/settings/serializers/security.py index c64791ca9..a5c27f1a1 100644 --- a/apps/settings/serializers/security.py +++ b/apps/settings/serializers/security.py @@ -3,8 +3,31 @@ from rest_framework import serializers from acls.serializers.rules import ip_group_help_text, ip_group_child_validator +__all__ = [ + 'SecurityPasswordRuleSerializer', 'SecuritySessionSerializer', + 'SecurityAuthSerializer', 'SecuritySettingSerializer', + 'SecurityLoginLimitSerializer', 'SecurityBasicSerializer', +] + class SecurityPasswordRuleSerializer(serializers.Serializer): + SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField( + min_value=1, max_value=99999, required=True, + label=_('User password expiration (day)'), + help_text=_( + 'If the user does not update the password during the time, ' + 'the user password will expire failure;The password expiration reminder mail will be ' + 'automatic sent to the user by system within 5 days (daily) before the password expires' + ) + ) + OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField( + min_value=0, max_value=99999, required=True, + label=_('Number of repeated historical passwords'), + help_text=_( + 'Tip: When the user resets the password, it cannot be ' + 'the previous n historical passwords of the user' + ) + ) SECURITY_PASSWORD_MIN_LENGTH = serializers.IntegerField( min_value=6, max_value=30, required=True, label=_('Password minimum length') @@ -33,20 +56,7 @@ login_ip_limit_time_help_text = _( ) -class SecurityAuthSerializer(serializers.Serializer): - SECURITY_MFA_AUTH = serializers.ChoiceField( - choices=( - [0, _('Not enabled')], - [1, _('All users')], - [2, _('Only admin users')], - ), - required=False, label=_("Global MFA auth") - ) - SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY = serializers.BooleanField( - required=False, default=True, - label=_('Third-party login users perform MFA authentication'), - help_text=_('The third-party login modes include OIDC, CAS, and SAML2'), - ) +class SecurityLoginLimitSerializer(serializers.Serializer): SECURITY_LOGIN_LIMIT_COUNT = serializers.IntegerField( min_value=3, max_value=99999, label=_('Limit the number of user login failures') @@ -56,6 +66,7 @@ class SecurityAuthSerializer(serializers.Serializer): label=_('Block user login interval (minute)'), help_text=login_ip_limit_time_help_text ) + SECURITY_LOGIN_IP_LIMIT_COUNT = serializers.IntegerField( min_value=3, max_value=99999, label=_('Limit the number of IP login failures') @@ -75,23 +86,6 @@ class SecurityAuthSerializer(serializers.Serializer): child=serializers.CharField(max_length=1024, validators=[ip_group_child_validator]), help_text=ip_group_help_text ) - SECURITY_PASSWORD_EXPIRATION_TIME = serializers.IntegerField( - min_value=1, max_value=99999, required=True, - label=_('User password expiration (day)'), - help_text=_( - 'If the user does not update the password during the time, ' - 'the user password will expire failure;The password expiration reminder mail will be ' - 'automatic sent to the user by system within 5 days (daily) before the password expires' - ) - ) - OLD_PASSWORD_HISTORY_LIMIT_COUNT = serializers.IntegerField( - min_value=0, max_value=99999, required=True, - label=_('Number of repeated historical passwords'), - help_text=_( - 'Tip: When the user resets the password, it cannot be ' - 'the previous n historical passwords of the user' - ) - ) USER_LOGIN_SINGLE_MACHINE_ENABLED = serializers.BooleanField( required=False, default=False, label=_("Only single device login"), help_text=_("After the user logs in on the new device, other logged-in devices will automatically log out") @@ -113,6 +107,29 @@ class SecurityAuthSerializer(serializers.Serializer): "they can log in directly" ) ) + + +class SecurityAuthSerializer(serializers.Serializer): + SECURITY_MFA_AUTH = serializers.ChoiceField( + choices=( + [0, _('Not enabled')], + [1, _('All users')], + [2, _('Only admin users')], + ), + required=False, label=_("Global MFA auth") + ) + SECURITY_MFA_AUTH_ENABLED_FOR_THIRD_PARTY = serializers.BooleanField( + required=False, default=True, + label=_('Third-party login users perform MFA authentication'), + help_text=_('The third-party login modes include OIDC, CAS, and SAML2'), + ) + OTP_ISSUER_NAME = serializers.CharField( + required=False, max_length=16, label=_('OTP issuer name'), + ) + OTP_VALID_WINDOW = serializers.IntegerField( + min_value=1, max_value=10, + label=_("OTP valid window") + ) SECURITY_MFA_VERIFY_TTL = serializers.IntegerField( min_value=5, max_value=60 * 60 * 10, label=_("MFA verify TTL"), @@ -120,6 +137,11 @@ class SecurityAuthSerializer(serializers.Serializer): "Unit: second, The verification MFA takes effect only when you view the account password" ) ) + SECURITY_MFA_IN_LOGIN_PAGE = serializers.BooleanField( + required=False, default=False, + label=_("MFA in login page"), + help_text=_("Eu security regulations(GDPR) require MFA to be on the login page") + ) VERIFY_CODE_TTL = serializers.IntegerField( min_value=5, max_value=60 * 60 * 10, label=_("Verify code TTL (second)"), @@ -131,15 +153,22 @@ class SecurityAuthSerializer(serializers.Serializer): help_text=_("The password and additional code are sent to a third party " "authentication system for verification") ) - SECURITY_MFA_IN_LOGIN_PAGE = serializers.BooleanField( - required=False, default=False, - label=_("MFA in login page"), - help_text=_("Eu security regulations(GDPR) require MFA to be on the login page") - ) SECURITY_LOGIN_CAPTCHA_ENABLED = serializers.BooleanField( required=False, default=False, label=_("Enable Login captcha"), help_text=_("Enable captcha to prevent robot authentication") ) + SECURITY_CHECK_DIFFERENT_CITY_LOGIN = serializers.BooleanField( + required=False, label=_('Remote Login Protection'), + help_text=_( + 'The system determines whether the login IP address belongs to a common login city. ' + 'If the account is logged in from a common login city, the system sends a remote login reminder' + ) + ) + SECURITY_UNCOMMON_USERS_TTL = serializers.IntegerField( + min_value=30, max_value=99999, required=False, + label=_('Unused user timeout (day)'), + help_text=_("Detect infrequent users daily and disable them if they exceed the predetermined time limit.") + ) def validate(self, attrs): if attrs.get('SECURITY_MFA_AUTH') != 1: @@ -156,15 +185,7 @@ class SecurityAuthSerializer(serializers.Serializer): return data -class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSerializer): - PREFIX_TITLE = _('Security') - - SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField( - required=True, label=_('Enable terminal register'), - help_text=_( - "Allow terminal register, after all terminal setup, you should disable this for security" - ) - ) +class SecuritySessionSerializer(serializers.Serializer): SECURITY_WATERMARK_ENABLED = serializers.BooleanField( required=True, label=_('Enable watermark'), help_text=_('Enabled, the web session and replay contains watermark information') @@ -182,6 +203,13 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri SECURITY_LUNA_REMEMBER_AUTH = serializers.BooleanField( label=_("Remember manual auth") ) + SECURITY_SESSION_SHARE = serializers.BooleanField( + required=True, label=_('Session share'), + help_text=_("Enabled, Allows user active session to be shared with other users") + ) + + +class SecurityBasicSerializer(serializers.Serializer): SECURITY_INSECURE_COMMAND = serializers.BooleanField( required=False, label=_('Insecure command alert') ) @@ -189,35 +217,11 @@ class SecuritySettingSerializer(SecurityPasswordRuleSerializer, SecurityAuthSeri max_length=8192, required=False, allow_blank=True, label=_('Email recipient'), help_text=_('Multiple user using , split') ) - SECURITY_COMMAND_EXECUTION = serializers.BooleanField( - required=False, label=_('Operation center'), - help_text=_('Allow user run batch command or not using ansible') - ) - SECURITY_COMMAND_BLACKLIST = serializers.ListField( - child=serializers.CharField(max_length=1024, ), - label=_('Operation center command blacklist'), - help_text=_("Commands that are not allowed execute.") - ) - SECURITY_SESSION_SHARE = serializers.BooleanField( - required=True, label=_('Session share'), - help_text=_("Enabled, Allows user active session to be shared with other users") - ) - SECURITY_UNCOMMON_USERS_TTL = serializers.IntegerField( - min_value=30, max_value=99999, required=False, - label=_('Unused user timeout (day)'), - help_text=_("Detect infrequent users daily and disable them if they exceed the predetermined time limit.") - ) - SECURITY_CHECK_DIFFERENT_CITY_LOGIN = serializers.BooleanField( - required=False, label=_('Remote Login Protection'), - help_text=_( - 'The system determines whether the login IP address belongs to a common login city. ' - 'If the account is logged in from a common login city, the system sends a remote login reminder' - ) - ) - OTP_ISSUER_NAME = serializers.CharField( - required=False, max_length=16, label=_('OTP issuer name'), - ) - OTP_VALID_WINDOW = serializers.IntegerField( - min_value=1, max_value=10, - label=_("OTP valid window") - ) + + +class SecuritySettingSerializer( + SecurityPasswordRuleSerializer, SecurityAuthSerializer, + SecuritySessionSerializer, SecurityBasicSerializer, + SecurityLoginLimitSerializer, +): + PREFIX_TITLE = _('Security') diff --git a/apps/settings/serializers/settings.py b/apps/settings/serializers/settings.py index c0ff911a7..a0e022f77 100644 --- a/apps/settings/serializers/settings.py +++ b/apps/settings/serializers/settings.py @@ -14,7 +14,7 @@ from .auth import ( ) from .basic import BasicSettingSerializer from .cleaning import CleaningSerializer -from .email import EmailSettingSerializer, EmailContentSettingSerializer +from .msg import EmailSettingSerializer, EmailContentSettingSerializer from .other import OtherSettingSerializer from .security import SecuritySettingSerializer from .terminal import TerminalSettingSerializer diff --git a/apps/settings/serializers/sms.py b/apps/settings/serializers/sms.py deleted file mode 100644 index ec78993a6..000000000 --- a/apps/settings/serializers/sms.py +++ /dev/null @@ -1,7 +0,0 @@ -from django.utils.translation import gettext_lazy as _ -from rest_framework import serializers - - -class SMSBackendSerializer(serializers.Serializer): - name = serializers.CharField(max_length=256, required=True, label=_('Name')) - label = serializers.CharField(max_length=256, required=True, label=_('Label')) diff --git a/apps/settings/serializers/terminal.py b/apps/settings/serializers/terminal.py index ea1412cd1..17782a2e0 100644 --- a/apps/settings/serializers/terminal.py +++ b/apps/settings/serializers/terminal.py @@ -18,6 +18,12 @@ class TerminalSettingSerializer(serializers.Serializer): ('25', '25'), ('50', '50'), ) + SECURITY_SERVICE_ACCOUNT_REGISTRATION = serializers.BooleanField( + required=True, label=_('Enable terminal register'), + help_text=_( + "Allow terminal register, after all terminal setup, you should disable this for security" + ) + ) TERMINAL_PASSWORD_AUTH = serializers.BooleanField(required=False, label=_('Password auth')) TERMINAL_PUBLIC_KEY_AUTH = serializers.BooleanField( required=False, label=_('Public key auth'), diff --git a/apps/settings/serializers/vault.py b/apps/settings/serializers/vault.py deleted file mode 100644 index e25c73e6c..000000000 --- a/apps/settings/serializers/vault.py +++ /dev/null @@ -1,27 +0,0 @@ -from django.utils.translation import gettext_lazy as _ -from rest_framework import serializers - -from accounts.const import VaultTypeChoices -from common.serializers.fields import EncryptedField - -__all__ = ['VaultSettingSerializer'] - - -class VaultSettingSerializer(serializers.Serializer): - VAULT_TYPE = serializers.ChoiceField( - default=VaultTypeChoices.local, choices=VaultTypeChoices.choices, - required=False, label=_('Type') - ) - VAULT_HCP_HOST = serializers.CharField( - max_length=256, allow_blank=True, required=False, label=_('Host') - ) - VAULT_HCP_TOKEN = EncryptedField( - max_length=256, allow_blank=True, required=False, label=_('Token'), default='' - ) - VAULT_HCP_MOUNT_POINT = serializers.CharField( - max_length=256, allow_blank=True, required=False, label=_('Mount Point') - ) - - def validate(self, attrs): - attrs.pop('VAULT_TYPE', None) - return attrs