From f34cea84813623a03e3a3b08e0308a28838b20c4 Mon Sep 17 00:00:00 2001
From: duanhongyi <duanhongyi@doopai.com>
Date: Mon, 26 Oct 2020 16:38:23 +0800
Subject: [PATCH 1/2] feat(token): add get token api

---
 rootfs/api/tests/test_auth.py | 14 ++++++++++++++
 rootfs/api/urls.py            |  2 ++
 rootfs/api/views.py           | 12 ++++++++++++
 rootfs/requirements.txt       |  1 -
 4 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/rootfs/api/tests/test_auth.py b/rootfs/api/tests/test_auth.py
index abb50bc7b..96ee72b10 100644
--- a/rootfs/api/tests/test_auth.py
+++ b/rootfs/api/tests/test_auth.py
@@ -374,6 +374,20 @@ def test_regenerate(self):
         response = self.client.post(url, {})
         self.assertEqual(response.status_code, 401, response.data)
 
+    def test_key(self):
+        url = '/v2/auth/tokens/autotest2/'
+        self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.admin_token)
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200, response.data)
+
+        self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.user1_token)
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 200, response.data)
+
+        self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.user2_token)
+        response = self.client.get(url)
+        self.assertEqual(response.status_code, 403, response.data)
+
     @mock.patch('django_auth_ldap.backend.logger')
     def test_auth_no_ldap_by_default(self, mock_logger):
         """Ensure that LDAP authentication is disabled by default."""
diff --git a/rootfs/api/urls.py b/rootfs/api/urls.py
index e481d20b4..7930e553c 100644
--- a/rootfs/api/urls.py
+++ b/rootfs/api/urls.py
@@ -131,6 +131,8 @@
         views_obtain_auth_token),
     url(r'^auth/tokens/$',
         views.TokenManagementViewSet.as_view({'post': 'regenerate'})),
+    url(r'^auth/tokens/(?P<username>[\w.@+-]+)/?$',
+        views.TokenManagementViewSet.as_view({'get': 'token'})),
     # admin sharing
     url(r'^admin/perms/(?P<username>[\w.@+-]+)/?$',
         views.AdminPermsViewSet.as_view({'delete': 'destroy'})),
diff --git a/rootfs/api/views.py b/rootfs/api/views.py
index 97db8c059..9b3e6d7e5 100644
--- a/rootfs/api/views.py
+++ b/rootfs/api/views.py
@@ -80,6 +80,8 @@ def list(self, request, **kwargs):
         return Response(serializer.data)
 
     def destroy(self, request, **kwargs):
+        if settings.LDAP_ENDPOINT:
+            raise DryccException("You cannot destroy user when ldap is enabled.")
         calling_obj = self.get_object()
         target_obj = calling_obj
 
@@ -104,6 +106,8 @@ def destroy(self, request, **kwargs):
     def passwd(self, request, **kwargs):
         if not request.data.get('new_password'):
             raise DryccException("new_password is a required field")
+        if settings.LDAP_ENDPOINT:
+            raise DryccException("You cannot change password when ldap is enabled.")
 
         caller_obj = self.get_object()
         target_obj = self.get_object()
@@ -157,6 +161,14 @@ def regenerate(self, request, **kwargs):
         token = Token.objects.create(user=obj)
         return Response({'token': token.key})
 
+    def token(self, request, **kwargs):
+        if self.request.user.username == kwargs['username'] \
+                or self.request.user.is_superuser:
+            obj = get_object_or_404(User, username=kwargs['username'])
+            token = Token.objects.get(user=obj)
+            return Response({'token': token.key})
+        return Response(status=status.HTTP_403_FORBIDDEN)
+
 
 class BaseDryccViewSet(viewsets.OwnerViewSet):
     """
diff --git a/rootfs/requirements.txt b/rootfs/requirements.txt
index dd50ab8b1..00eeab0f3 100644
--- a/rootfs/requirements.txt
+++ b/rootfs/requirements.txt
@@ -17,7 +17,6 @@ packaging==20.4
 pyasn1==0.4.8
 pynsq==0.9.0
 psycopg2-binary==2.8.5
-pyldap==3.0.0.post1
 pyOpenSSL==19.1.0
 pytz==2020.1
 requests==2.24.0

From 9901c884ccd6e90b3a3bec93420e9b83c3e2d1b2 Mon Sep 17 00:00:00 2001
From: lijianguo <lijianguo@zhumengyuan.com>
Date: Wed, 4 Nov 2020 16:30:09 +0800
Subject: [PATCH 2/2] chore(controller):replace whitelist with allowlist

---
 README.md                             |  4 +-
 codecov.yml                           |  1 +
 rootfs/api/migrations/0001_initial.py |  2 +-
 rootfs/api/models/app.py              |  6 +--
 rootfs/api/models/appsettings.py      | 22 ++++----
 rootfs/api/serializers.py             |  2 +-
 rootfs/api/tests/test_app_settings.py | 76 +++++++++++++--------------
 rootfs/api/urls.py                    |  6 +--
 rootfs/api/views.py                   | 23 ++++----
 rootfs/drycc/urls.py                  |  2 +-
 rootfs/scheduler/resources/ingress.py | 12 ++---
 11 files changed, 78 insertions(+), 78 deletions(-)

diff --git a/README.md b/README.md
index ad2bfe986..121880c67 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,8 @@
 
 # Drycc Controller
 
-[![Build Status](https://travis-ci.org/drycc/controller.svg?branch=master)](https://travis-ci.org/drycc/controller)
-[![codecov.io](https://codecov.io/github/drycc/controller/coverage.svg?branch=master)](https://codecov.io/github/drycc/controller?branch=master)
+[![Build Status](https://travis-ci.org/drycc/controller.svg?branch=main)](https://travis-ci.org/drycc/controller)
+[![codecov.io](https://codecov.io/github/drycc/controller/coverage.svg?branch=main)](https://codecov.io/github/drycc/controller?branch=main)
 
 Drycc (pronounced DAY-iss) Workflow is an open source Platform as a Service (PaaS) that adds a developer-friendly layer to any [Kubernetes](http://kubernetes.io) cluster, making it easy to deploy and manage applications on your own servers.
 
diff --git a/codecov.yml b/codecov.yml
index 2ea669f02..365083ca9 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -2,6 +2,7 @@
 comment:
   layout: header, diff
 coverage:
+  branch: main
   status:
     project:
       default:
diff --git a/rootfs/api/migrations/0001_initial.py b/rootfs/api/migrations/0001_initial.py
index 53dffa2d3..1b1942bc5 100644
--- a/rootfs/api/migrations/0001_initial.py
+++ b/rootfs/api/migrations/0001_initial.py
@@ -47,7 +47,7 @@ class Migration(migrations.Migration):
                 ('created', models.DateTimeField(auto_now_add=True)),
                 ('updated', models.DateTimeField(auto_now=True)),
                 ('routable', models.NullBooleanField(default=None)),
-                ('whitelist', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), default=None, size=None)),
+                ('allowlist', django.contrib.postgres.fields.ArrayField(base_field=models.CharField(max_length=50), default=None, size=None)),
                 ('autoscale', jsonfield.fields.JSONField(blank=True, default={})),
                 ('label', jsonfield.fields.JSONField(blank=True, default={})),
                 ('app', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.App')),
diff --git a/rootfs/api/models/app.py b/rootfs/api/models/app.py
index 5de945211..ecb0bb4c7 100644
--- a/rootfs/api/models/app.py
+++ b/rootfs/api/models/app.py
@@ -189,9 +189,9 @@ def _refresh_ingress(self, hosts, tls_map, ssl_redirect):
             "tls": [{"secretName": k, "hosts": v} for k, v in tls_map.items()],
             "ssl_redirect": ssl_redirect
         }
-        whitelist = self.appsettings_set.latest().whitelist
-        if whitelist:
-            kwargs.update({"whitelist": whitelist})
+        allowlist = self.appsettings_set.latest().allowlist
+        if allowlist:
+            kwargs.update({"allowlist": allowlist})
         try:
             # In order to create an ingress, we must first have a namespace.
             if ingress == "":
diff --git a/rootfs/api/models/appsettings.py b/rootfs/api/models/appsettings.py
index d162630de..ae5fa0ffd 100644
--- a/rootfs/api/models/appsettings.py
+++ b/rootfs/api/models/appsettings.py
@@ -19,9 +19,9 @@ class AppSettings(UuidAuditedModel):
     owner = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.PROTECT)
     app = models.ForeignKey('App', on_delete=models.CASCADE)
     routable = models.NullBooleanField(default=None)
-    # the default values is None to differentiate from user sending an empty whitelist
+    # the default values is None to differentiate from user sending an empty allowlist
     # and user just updating other fields meaning the values needs to be copied from prev release
-    whitelist = ArrayField(models.CharField(max_length=50), default=None)
+    allowlist = ArrayField(models.CharField(max_length=50), default=None)
     autoscale = JSONField(default={}, blank=True)
     label = JSONField(default={}, blank=True)
 
@@ -37,14 +37,14 @@ def __init__(self, *args, **kwargs):
     def __str__(self):
         return "{}-{}".format(self.app.id, str(self.uuid)[:7])
 
-    def new(self, user, whitelist):
+    def new(self, user, allowlist):
         """
-        Create a new application appSettings using the provided whitelist
+        Create a new application appSettings using the provided allowlist
         on behalf of a user.
         """
 
         app_settings = AppSettings.objects.create(
-            owner=user, app=self.app, whitelist=whitelist)
+            owner=user, app=self.app, allowlist=allowlist)
 
         return app_settings
 
@@ -62,16 +62,16 @@ def _update_routable(self, previous_settings):
             self.app.routable(new)
             self.summary += ["{} changed routablity from {} to {}".format(self.owner, old, new)]
 
-    def _update_whitelist(self, previous_settings):
+    def _update_allowlist(self, previous_settings):
         # If no previous settings then assume it is the first record and set as empty
         # to prevent from database constraint violation
         if not previous_settings:
-            setattr(self, 'whitelist', [])
-        old = getattr(previous_settings, 'whitelist', [])
-        new = getattr(self, 'whitelist', None)
+            setattr(self, 'allowlist', [])
+        old = getattr(previous_settings, 'allowlist', [])
+        new = getattr(self, 'allowlist', None)
         # if nothing changed copy the settings from previous
         if new is None and old is not None:
-            setattr(self, 'whitelist', old)
+            setattr(self, 'allowlist', old)
         elif set(old) != set(new):
             added = ', '.join(k for k in set(new)-set(old))
             added = 'added ' + added if added else ''
@@ -165,7 +165,7 @@ def save(self, *args, **kwargs):
 
         try:
             self._update_routable(previous_settings)
-            self._update_whitelist(previous_settings)
+            self._update_allowlist(previous_settings)
             self._update_autoscale(previous_settings)
             self._update_label(previous_settings)
         except (UnprocessableEntity, NotFound):
diff --git a/rootfs/api/serializers.py b/rootfs/api/serializers.py
index 5837caeca..a40e5308a 100644
--- a/rootfs/api/serializers.py
+++ b/rootfs/api/serializers.py
@@ -594,7 +594,7 @@ class Meta:
         fields = '__all__'
 
     @staticmethod
-    def validate_whitelist(data):
+    def validate_allowlist(data):
         for address in data:
             try:
                 ipaddress.ip_address(address)
diff --git a/rootfs/api/tests/test_app_settings.py b/rootfs/api/tests/test_app_settings.py
index 219257d6f..34b0baca2 100644
--- a/rootfs/api/tests/test_app_settings.py
+++ b/rootfs/api/tests/test_app_settings.py
@@ -41,85 +41,85 @@ def test_settings_routable(self, mock_requests):
         self.assertEqual(response.status_code, 201, response.data)
         self.assertFalse(app.appsettings_set.latest().routable)
 
-    def test_settings_whitelist(self, mock_requests):
+    def test_settings_allowlist(self, mock_requests):
         """
-        Test that addresses can be added/deleted to whitelist
+        Test that addresses can be added/deleted to allowlist
         """
         app_id = self.create_app()
         app = App.objects.get(id=app_id)
-        # add addresses to empty whitelist
+        # add addresses to empty allowlist
         addresses = ["0.0.0.0/0"]
-        whitelist = {'addresses': addresses}
+        allowlist = {'addresses': addresses}
         response = self.client.post(
-            '/v2/apps/{app_id}/whitelist'.format(**locals()),
-            whitelist)
+            '/v2/apps/{app_id}/allowlist'.format(**locals()),
+            allowlist)
         self.assertEqual(response.status_code, 201, response.data)
         self.assertEqual(set(response.data['addresses']),
-                         set(app.appsettings_set.latest().whitelist), response.data)
+                         set(app.appsettings_set.latest().allowlist), response.data)
         self.assertEqual(set(response.data['addresses']), set(addresses), response.data)
 
-        # get the whitelist
-        response = self.client.get('/v2/apps/{app_id}/whitelist'.format(**locals()))
+        # get the allowlist
+        response = self.client.get('/v2/apps/{app_id}/allowlist'.format(**locals()))
         self.assertEqual(response.status_code, 200, response.data)
         self.assertEqual(set(response.data['addresses']),
-                         set(app.appsettings_set.latest().whitelist), response.data)
+                         set(app.appsettings_set.latest().allowlist), response.data)
         self.assertEqual(set(response.data['addresses']), set(addresses), response.data)
 
-        # delete an address from whitelist
-        whitelist = {'addresses': ["0.0.0.0/0"]}
+        # delete an address from allowlist
+        allowlist = {'addresses': ["0.0.0.0/0"]}
         addresses.remove("0.0.0.0/0")
         response = self.client.delete(
-            '/v2/apps/{app_id}/whitelist'.format(**locals()),
-            whitelist)
+            '/v2/apps/{app_id}/allowlist'.format(**locals()),
+            allowlist)
         self.assertEqual(response.status_code, 204, response.data)
-        self.assertEqual(set(addresses), set(app.appsettings_set.latest().whitelist))
+        self.assertEqual(set(addresses), set(app.appsettings_set.latest().allowlist))
 
-        # add addresses to empty whitelist
+        # add addresses to empty allowlist
         addresses = ["0.0.0.0/0"]
-        whitelist = {'addresses': addresses}
+        allowlist = {'addresses': addresses}
         response = self.client.post(
-            '/v2/apps/{app_id}/whitelist'.format(**locals()),
-            whitelist)
+            '/v2/apps/{app_id}/allowlist'.format(**locals()),
+            allowlist)
         self.assertEqual(response.status_code, 201, response.data)
         self.assertEqual(set(response.data['addresses']),
-                         set(app.appsettings_set.latest().whitelist), response.data)
+                         set(app.appsettings_set.latest().allowlist), response.data)
         self.assertEqual(set(response.data['addresses']), set(addresses), response.data)
 
-        # add addresses to non-empty whitelist
-        whitelist = {'addresses': ["2.3.4.5"]}
+        # add addresses to non-empty allowlist
+        allowlist = {'addresses': ["2.3.4.5"]}
         addresses.extend(["2.3.4.5"])
         response = self.client.post(
-            '/v2/apps/{app_id}/whitelist'.format(**locals()),
-            whitelist)
+            '/v2/apps/{app_id}/allowlist'.format(**locals()),
+            allowlist)
         self.assertEqual(response.status_code, 201, response.data)
         self.assertEqual(set(response.data['addresses']),
-                         set(app.appsettings_set.latest().whitelist), response.data)
+                         set(app.appsettings_set.latest().allowlist), response.data)
         self.assertEqual(set(response.data['addresses']), set(addresses), response.data)
 
-        # add exisitng addresses to whitelist
+        # add exisitng addresses to allowlist
         response = self.client.post(
-            '/v2/apps/{app_id}/whitelist'.format(**locals()),
-            whitelist)
+            '/v2/apps/{app_id}/allowlist'.format(**locals()),
+            allowlist)
         self.assertEqual(response.status_code, 409, response.data)
-        # delete non-exisitng address from whitelist
-        whitelist = {'addresses': ["2.3.4.6"]}
+        # delete non-exisitng address from allowlist
+        allowlist = {'addresses': ["2.3.4.6"]}
         response = self.client.delete(
-            '/v2/apps/{app_id}/whitelist'.format(**locals()),
-            whitelist)
+            '/v2/apps/{app_id}/allowlist'.format(**locals()),
+            allowlist)
         self.assertEqual(response.status_code, 422)
         # pass invalid address
-        whitelist = {'addresses': ["2.3.4.6.7"]}
+        allowlist = {'addresses': ["2.3.4.6.7"]}
         response = self.client.post(
-            '/v2/apps/{app_id}/whitelist'.format(**locals()),
-            whitelist)
+            '/v2/apps/{app_id}/allowlist'.format(**locals()),
+            allowlist)
         self.assertEqual(response.status_code, 400, response.data)
-        # update other appsettings and whitelist should be retained
+        # update other appsettings and allowlist should be retained
         settings = {'routable': False}
         response = self.client.post(
             '/v2/apps/{app.id}/settings'.format(**locals()),
             settings)
         self.assertEqual(response.status_code, 201, response.data)
-        self.assertEqual(set(addresses), set(app.appsettings_set.latest().whitelist))
+        self.assertEqual(set(addresses), set(app.appsettings_set.latest().allowlist))
 
     def test_kubernetes_service_failure(self, mock_requests):
         """
@@ -131,7 +131,7 @@ def test_kubernetes_service_failure(self, mock_requests):
         with mock.patch('scheduler.resources.service.Service.update') as mock_kube:
             mock_kube.side_effect = KubeException('Boom!')
             addresses = ["2.3.4.5"]
-            url = '/v2/apps/{}/whitelist'.format(app_id)
+            url = '/v2/apps/{}/allowlist'.format(app_id)
             response = self.client.post(url, {'addresses': addresses})
             self.assertEqual(response.status_code, 201, response.data)
 
diff --git a/rootfs/api/urls.py b/rootfs/api/urls.py
index 7930e553c..2bac67b0b 100644
--- a/rootfs/api/urls.py
+++ b/rootfs/api/urls.py
@@ -68,9 +68,9 @@
     # application settings
     url(r"^apps/(?P<id>{})/settings/?$".format(settings.APP_URL_REGEX),
         views.AppSettingsViewSet.as_view({'get': 'retrieve', 'post': 'create'})),
-    # application ip whitelist
-    url(r"^apps/(?P<id>{})/whitelist/?$".format(settings.APP_URL_REGEX),
-        views.WhitelistViewSet.as_view({'post': 'create', 'get': 'list', 'delete': 'delete'})),
+    # application ip allowlist
+    url(r"^apps/(?P<id>{})/allowlist/?$".format(settings.APP_URL_REGEX),
+        views.AllowlistViewSet.as_view({'post': 'create', 'get': 'list', 'delete': 'delete'})),
     # application TLS settings
     url(r"^apps/(?P<id>{})/tls/?$".format(settings.APP_URL_REGEX),
         views.TLSViewSet.as_view({'get': 'retrieve', 'post': 'create'})),
diff --git a/rootfs/api/views.py b/rootfs/api/views.py
index 9b3e6d7e5..cd3ca8f1e 100644
--- a/rootfs/api/views.py
+++ b/rootfs/api/views.py
@@ -374,31 +374,31 @@ class AppSettingsViewSet(AppResourceViewSet):
     serializer_class = serializers.AppSettingsSerializer
 
 
-class WhitelistViewSet(AppResourceViewSet):
+class AllowlistViewSet(AppResourceViewSet):
     model = models.AppSettings
     serializer_class = serializers.AppSettingsSerializer
 
     def list(self, *args, **kwargs):
         appSettings = self.get_app().appsettings_set.latest()
-        data = {"addresses": appSettings.whitelist}
+        data = {"addresses": appSettings.allowlist}
         return Response(data, status=status.HTTP_200_OK)
 
     def create(self, request, **kwargs):
         appSettings = self.get_app().appsettings_set.latest()
-        addresses = self.get_serializer().validate_whitelist(request.data.get('addresses'))
-        addresses = list(set(appSettings.whitelist) | set(addresses))
-        new_appsettings = appSettings.new(self.request.user, whitelist=addresses)
-        return Response({"addresses": new_appsettings.whitelist}, status=status.HTTP_201_CREATED)
+        addresses = self.get_serializer().validate_allowlist(request.data.get('addresses'))
+        addresses = list(set(appSettings.allowlist) | set(addresses))
+        new_appsettings = appSettings.new(self.request.user, allowlist=addresses)
+        return Response({"addresses": new_appsettings.allowlist}, status=status.HTTP_201_CREATED)
 
     def delete(self, request, **kwargs):
         appSettings = self.get_app().appsettings_set.latest()
-        addresses = self.get_serializer().validate_whitelist(request.data.get('addresses'))
+        addresses = self.get_serializer().validate_allowlist(request.data.get('addresses'))
 
-        unfound_addresses = set(addresses) - set(appSettings.whitelist)
+        unfound_addresses = set(addresses) - set(appSettings.allowlist)
         if len(unfound_addresses) != 0:
-            raise UnprocessableEntity('addresses {} does not exist in whitelist'.format(unfound_addresses))  # noqa
-        addresses = list(set(appSettings.whitelist) - set(addresses))
-        appSettings.new(self.request.user, whitelist=addresses)
+            raise UnprocessableEntity('addresses {} does not exist in allowlist'.format(unfound_addresses))  # noqa
+        addresses = list(set(appSettings.allowlist) - set(addresses))
+        appSettings.new(self.request.user, allowlist=addresses)
         return Response(status=status.HTTP_204_NO_CONTENT)
 
 
@@ -764,7 +764,6 @@ def deploy(self, volume, pre_mount_path):
             for container_type, path in volume.path.items():
                 summary += "{container_type} mount path {path} with volume {name}.".\
                                format(container_type=container_type, path=path, name=volume.name)  # noqa
-            print(summary)
             self.release = latest_release.new(
                 self.request.user,
                 config=latest_release.config,
diff --git a/rootfs/drycc/urls.py b/rootfs/drycc/urls.py
index 53e0acfba..5bf8b9b2a 100644
--- a/rootfs/drycc/urls.py
+++ b/rootfs/drycc/urls.py
@@ -1,7 +1,7 @@
 """
 URL routing patterns for the Drycc project.
 
-This is the "master" urls.py which then includes the urls.py files of
+This is the "main" urls.py which then includes the urls.py files of
 installed apps.
 """
 
diff --git a/rootfs/scheduler/resources/ingress.py b/rootfs/scheduler/resources/ingress.py
index 3bea94f4d..f9d9e8ef6 100644
--- a/rootfs/scheduler/resources/ingress.py
+++ b/rootfs/scheduler/resources/ingress.py
@@ -52,10 +52,10 @@ class NginxManifest(BaseManifest):
     def manifest(self, api_version, ingress, ingress_class, namespace, **kwargs):
         data = BaseManifest.manifest(
             self, api_version, ingress, ingress_class, namespace, **kwargs)
-        if "whitelist" in kwargs:
-            whitelist = ", ".join(kwargs.pop("whitelist"))
+        if "allowlist" in kwargs:
+            allowlist = ", ".join(kwargs.pop("allowlist"))
             data["metadata"]["annotations"].update({
-                "nginx.ingress.kubernetes.io/whitelist-source-range": whitelist
+                "nginx.ingress.kubernetes.io/whitelist-source-range": allowlist
             })
         if "ssl_redirect" in kwargs:
             ssl_redirect = kwargs.pop("ssl_redirect")
@@ -73,11 +73,11 @@ class TraefikManifest(BaseManifest):
     def manifest(self, api_version, ingress, ingress_class, namespace, **kwargs):
         data = BaseManifest.manifest(
             self, api_version, ingress, ingress_class, namespace, **kwargs)
-        if "whitelist" in kwargs:
-            whitelist = ", ".join(kwargs.pop("whitelist"))
+        if "allowlist" in kwargs:
+            allowlist = ", ".join(kwargs.pop("allowlist"))
             data["metadata"]["annotations"].update({
                 "ingress.kubernetes.io/whitelist-x-forwarded-for": "true",
-                "traefik.ingress.kubernetes.io/whitelist-source-range": whitelist
+                "traefik.ingress.kubernetes.io/whitelist-source-range": allowlist
             })
         if "ssl_redirect" in kwargs:
             ssl_redirect = kwargs.pop("ssl_redirect")
