diff --git a/authome/admin/admin.py b/authome/admin/admin.py index 88bbd53..e20293f 100644 --- a/authome/admin/admin.py +++ b/authome/admin/admin.py @@ -269,7 +269,7 @@ class NormalUser(models.User): class Meta: proxy = True verbose_name="User" - verbose_name_plural="{}Users".format(" " * 11) + verbose_name_plural="{}Users".format(" " * 16) class DeleteMixin(object): def delete_model(self, request, obj): @@ -314,7 +314,7 @@ class SystemUser(models.User): class Meta: proxy = True verbose_name="System User" - verbose_name_plural="{}System Users".format(" " * 9) + verbose_name_plural="{}System Users".format(" " * 14) class SystemUserAdmin(PermissionCheckMixin,DbcaAccountMixin,UserGroupsMixin,DatetimeMixin,CatchModelExceptionMixin,auth.admin.UserAdmin): @@ -462,14 +462,14 @@ class SystemUserToken(models.User): class Meta: proxy = True verbose_name="System User" - verbose_name_plural="{}System User Tokens".format(" " * 8) + verbose_name_plural="{}System User Tokens".format(" " * 13) class NormalUserToken(models.User): objects = models.NormalUserManager() class Meta: proxy = True verbose_name="System User" - verbose_name_plural="{}User Tokens".format(" " * 10) + verbose_name_plural="{}User Tokens".format(" " * 15) class AccessTokenAdmin(DatetimeMixin,CatchModelExceptionMixin,auth.admin.UserAdmin): diff --git a/authome/admin/adminsite.py b/authome/admin/adminsite.py index ad6ca85..a237b03 100644 --- a/authome/admin/adminsite.py +++ b/authome/admin/adminsite.py @@ -12,6 +12,7 @@ from .. import models as auth2_models from .. import utils from .. import signals +from ..cache import cache logger = logging.getLogger(__name__) @@ -109,12 +110,41 @@ def _build_app_dict(self, request, label=None): app_label = auth2_models.UserGroup._meta.app_label if app_label in app_dict: app_dict[app_label]["models"].append({ - 'name': "Renew Apple Secret Key", + 'name': "{}Renew Apple Secret Key".format(" " * 3), 'object_name': "Renew Apple Secret Key", 'perms': [], 'admin_url': reverse("admin:renew_apple_secretkey"), 'add_url': None, }) + if settings.AUTH2_MONITORING_DIR: + if auth2_models.can_access(request.user.email,settings.AUTH2_DOMAIN,'/admin/monitor/'): + app_label = auth2_models.UserGroup._meta.app_label + if app_label in app_dict: + if settings.AUTH2_CLUSTER_ENABLED: + app_dict[app_label]["models"].append({ + 'name': "{1}Healthcheck({0})".format(settings.AUTH2_CLUSTERID," " * 2), + 'object_name': "{}_Healthcheck".format(settings.AUTH2_CLUSTERID), + 'perms': [], + 'admin_url': reverse("admin:auth2_status",kwargs={"clusterid":settings.AUTH2_CLUSTERID}), + 'add_url': None, + }) + for cluster in cache.auth2_clusters.values(): + app_dict[app_label]["models"].append({ + 'name': "{1}Healthcheck({0})".format(cluster.clusterid," " * 2), + 'object_name': "{}_Healthcheck".format(cluster.clusterid), + 'perms': [], + 'admin_url': reverse("admin:auth2_status",kwargs={"clusterid":cluster.clusterid}), + 'add_url': None, + }) + + else: + app_dict[app_label]["models"].append({ + 'name': "{}Healthcheck".format(" " * 2), + 'object_name': "Healthcheck", + 'perms': [], + 'admin_url': reverse("admin:auth2_status"), + 'add_url': None, + }) return app_dict diff --git a/authome/admin/clusteradmin.py b/authome/admin/clusteradmin.py index fb46fe3..399fd4b 100644 --- a/authome/admin/clusteradmin.py +++ b/authome/admin/clusteradmin.py @@ -177,10 +177,7 @@ class SystemUserAccessTokenAdmin(SyncObjectChangeMixin,admin.SystemUserAccessTok def _sync_change(self,objids): return cache.usertokens_changed(objids,True) -class Auth2ClusterAdmin(admin.ExtraToolsMixin,admin.DeleteMixin,admin.DatetimeMixin,admin.CatchModelExceptionMixin,djangoadmin.ModelAdmin): - list_display = ('clusterid','_running_status','default','endpoint','_last_heartbeat','_usergroup_status','_usergroupauthorization_status','_userflow_status','_idp_status') - readonly_fields = ('clusterid','_running_status','default','endpoint','_last_heartbeat','_usergroup_status','_usergroup_lastrefreshed','_usergroupauthorization_status','_usergroupauthorization_lastrefreshed','_userflow_status','_userflow_lastrefreshed','_idp_status','_idp_lastrefreshed','modified','registered') - fields = readonly_fields +class BaseAuth2ClusterAdmin(admin.ExtraToolsMixin,admin.DeleteMixin,admin.DatetimeMixin,admin.CatchModelExceptionMixin,djangoadmin.ModelAdmin): ordering = ('clusterid',) extra_tools = [("cluster status",'cluster_status',"clusterstatus")] @@ -271,3 +268,14 @@ def has_add_permission(self, request, obj=None): def has_delete_permission(self, request, obj=None): return True + +if settings.AUTH2_MONITORING_DIR: + class Auth2ClusterAdmin(BaseAuth2ClusterAdmin): + list_display = ('clusterid','_running_status','default','endpoint','_usergroup_status','_usergroupauthorization_status','_userflow_status','_idp_status') + readonly_fields = ('clusterid','_running_status','default','endpoint','_usergroup_status','_usergroup_lastrefreshed','_usergroupauthorization_status','_usergroupauthorization_lastrefreshed','_userflow_status','_userflow_lastrefreshed','_idp_status','_idp_lastrefreshed','modified','registered') + fields = readonly_fields +else: + class Auth2ClusterAdmin(BaseAuth2ClusterAdmin): + list_display = ('clusterid','_running_status','default','endpoint','_last_heartbeat','_usergroup_status','_usergroupauthorization_status','_userflow_status','_idp_status') + readonly_fields = ('clusterid','_running_status','default','endpoint','_last_heartbeat','_usergroup_status','_usergroup_lastrefreshed','_usergroupauthorization_status','_usergroupauthorization_lastrefreshed','_userflow_status','_userflow_lastrefreshed','_idp_status','_idp_lastrefreshed','modified','registered') + fields = readonly_fields diff --git a/authome/backends.py b/authome/backends.py index 9507b1b..b83d0b6 100644 --- a/authome/backends.py +++ b/authome/backends.py @@ -100,7 +100,7 @@ def get_logout_url(cls): def logout_url(self): return self.LOGOUT_URL.format(base_url=self.base_url) - error_re = re.compile("^\s*(?P[A-Z0-9]+)\s*:") + error_re = re.compile("^\\s*(?P[A-Z0-9]+)\\s*:") def process_error(self, data): try: super().process_error(data) diff --git a/authome/basetest.py b/authome/basetest.py index 245b68a..9a5f543 100644 --- a/authome/basetest.py +++ b/authome/basetest.py @@ -28,67 +28,58 @@ def get(self,path, authorization=None,domain=None,url=None): return super().get(path,headers=headers) -class BaseAuthTestCase(TestCase): - client_class = Auth2Client - home_url = reverse('home') - auth_url = reverse('auth') - auth_optional_url = reverse('auth_optional') - auth_basic_url = reverse('auth_basic') - auth_basic_optional_url = reverse('auth_basic_optional') - test_usergroups = None +class BaseTestCase(TestCase): test_usergroupauthorization = None test_userauthorization = None test_users = None - - - def create_client(self): - return Auth2Client() - - def setUp(self): + test_usergroups = None + @classmethod + def setUpClass(cls): + super(BaseTestCase,cls).setUpClass() + print("*********************************************************") if settings.RELEASE: - print("Running in release mode") + print("Running unitest({}) in release mode".format(cls.__name__)) else: - print("Running in dev mode") - settings.AUTH_CACHE_SIZE=2000 - settings.BASIC_AUTH_CACHE_SIZE=1000 - settings.AUTH_BASIC_CACHE_EXPIRETIME=timedelta(seconds=3600) - settings.AUTH_CACHE_EXPIRETIME=timedelta(seconds=3600) - settings.STAFF_AUTH_CACHE_EXPIRETIME=settings.AUTH_CACHE_EXPIRETIME - settings.AUTH_CACHE_CLEAN_HOURS = [0] - settings.AUTHORIZATION_CACHE_CHECK_HOURS = [0,12] + print("Running unitest({}) in dev mode".format(cls.__name__)) - settings.CHECK_AUTH_BASIC_PER_REQUEST = False - User.objects.filter(email__endswith="@gunfire.com").delete() - User.objects.filter(email__endswith="@gunfire.com.au").delete() - User.objects.filter(email__endswith="@hacker.com").delete() - UserToken.objects.all().delete() - UserGroup.objects.all().exclude(users=["*"],excluded_users__isnull=True).delete() - UserAuthorization.objects.all().delete() + def delete_testdata(self): + #delete user group authorization + if self.test_usergroupauthorization: + for groupname,domain,paths,excluded_paths in self.test_usergroupauthorization : + UserGroupAuthorization.objects.filter(usergroup=UserGroup.objects.get(name=groupname),domain=domain,paths=paths,excluded_paths=excluded_paths).delete() + + #delete user authorization + if self.test_userauthorization: + for user,domain,paths,excluded_paths in self.test_userauthorization: + UserAuthorization.objects.filter(user=user,domain=domain,paths=paths,excluded_paths=excluded_paths).delete() - cache.refresh_authorization_cache(True) - if not UserGroup.objects.filter(users=["*"], excluded_users__isnull=True).exists(): - public_group = UserGroup(name="Public User",groupid="PUBLIC",users=["*"]) - public_group.clean() - public_group.save() - if not CustomizableUserflow.objects.filter(domain="*").exists(): - default_flow = CustomizableUserflow( - domain='*', - default='default', - mfa_set="default_mfa_set", - mfa_reset="default_mfa_reset", - password_reset="default_password_reset", - verifyemail_from="oim@dbca.wa.gov.au", - verifyemail_subject="test" - ) - default_flow.clean() - default_flow.save() + #delete users + if self.test_users: + for user in self.test_users.values(): + user.delete() - cache.clean_auth_cache(True) - cache.refresh_authorization_cache(True) + #delete UserGroup objects + if self.test_usergroups: + del_usergroups = [] + created_usergroups = [(UserGroup.public_group(),self.test_usergroups)] + while created_usergroups: + parent_obj,subgroup_datas = created_usergroups.pop() + for subgroup in subgroup_datas: + if len(subgroup) == 4: + name,users,excluded_users,subgroups = subgroup + session_timeout = None + else: + name,users,excluded_users,session_timeout,subgroups = subgroup + + obj = UserGroup.objects.filter(name=name,parent_group=parent_obj).first() + if obj: + del_usergroups.insert(0,obj) + if subgroups: + created_usergroups.append((obj,subgroups)) - def basic_auth(self, username, password): - return 'Basic {}'.format(base64.b64encode('{}:{}'.format(username, password).encode('utf-8')).decode('utf-8')) + for usergroup in del_usergroups: + usergroup.delete() def populate_testdata(self): #popuate UserGroup objects @@ -96,8 +87,27 @@ def populate_testdata(self): uncreated_usergroups = [(UserGroup.public_group(),self.test_usergroups)] while uncreated_usergroups: parent_obj,subgroup_datas = uncreated_usergroups.pop() - for name,users,excluded_users,subgroups in subgroup_datas: - obj = UserGroup(name=name,groupid=name,users=users,excluded_users=excluded_users,parent_group=parent_obj) + for subgroup in subgroup_datas: + if len(subgroup) == 4: + name,users,excluded_users,subgroups = subgroup + session_timeout = None + else: + name,users,excluded_users,session_timeout,subgroups = subgroup + + if users and isinstance(users,str): + users = [users] + if excluded_users and isinstance(excluded_users,str): + excluded_users = [excluded_users] + + obj = UserGroup.objects.filter(name=name).first() + if obj: + obj.groupid=name + obj.users=users + obj.excluded_users=excluded_users + obj.parent_group=parent_obj + obj.session_timeout=session_timeout + else: + obj = UserGroup(name=name,groupid=name,users=users,excluded_users=excluded_users,parent_group=parent_obj,session_timeout=session_timeout) obj.clean() print("save usergroup={}".format(obj)) obj.save() @@ -107,7 +117,11 @@ def populate_testdata(self): users = OrderedDict() if self.test_users: for test_user in self.test_users: - obj = User(username=test_user[0],email=test_user[1]) + obj = User.objects.filter(username=test_user[0]).first() + if obj: + obj.email = test_user[1] + else: + obj = User(username=test_user[0],email=test_user[1]) obj.clean() obj.save() users[test_user[0]] = obj @@ -135,6 +149,64 @@ def populate_testdata(self): cache.refresh_authorization_cache(True) +class BaseAuthTestCase(BaseTestCase): + client_class = Auth2Client + home_url = reverse('home') + auth_url = reverse('auth') + auth_optional_url = reverse('auth_optional') + auth_basic_url = reverse('auth_basic') + auth_basic_optional_url = reverse('auth_basic_optional') + + + def create_client(self): + return Auth2Client() + + def setUp(self): + settings.AUTH_CACHE_SIZE=2000 + settings.BASIC_AUTH_CACHE_SIZE=1000 + settings.AUTH_BASIC_CACHE_EXPIRETIME=timedelta(seconds=3600) + settings.AUTH_CACHE_EXPIRETIME=timedelta(seconds=3600) + settings.STAFF_AUTH_CACHE_EXPIRETIME=settings.AUTH_CACHE_EXPIRETIME + settings.AUTH_CACHE_CLEAN_HOURS = [0] + settings.AUTHORIZATION_CACHE_CHECK_HOURS = [0,12] + + settings.CHECK_AUTH_BASIC_PER_REQUEST = False + User.objects.filter(email__endswith="@gunfire.com").delete() + User.objects.filter(email__endswith="@gunfire.com.au").delete() + User.objects.filter(email__endswith="@hacker.com").delete() + UserToken.objects.all().delete() + UserGroup.objects.all().exclude(users=["*"],excluded_users__isnull=True).delete() + UserAuthorization.objects.all().delete() + + cache.refresh_authorization_cache(True) + public_group = UserGroup.objects.filter(users=["*"], excluded_users__isnull=True).first() + if public_group: + public_group.session_timeout = 900 + public_group.save() + else: + public_group = UserGroup(name="Public User",groupid="PUBLIC",users=["*"],session_timeout=900) + public_group.clean() + public_group.save() + if not CustomizableUserflow.objects.filter(domain="*").exists(): + default_flow = CustomizableUserflow( + domain='*', + default='default', + mfa_set="default_mfa_set", + mfa_reset="default_mfa_reset", + password_reset="default_password_reset", + verifyemail_from="oim@dbca.wa.gov.au", + verifyemail_subject="test" + ) + default_flow.clean() + default_flow.save() + + cache.clean_auth_cache(True) + cache.refresh_authorization_cache(True) + + + def basic_auth(self, username, password): + return 'Basic {}'.format(base64.b64encode('{}:{}'.format(username, password).encode('utf-8')).decode('utf-8')) + class BaseAuthCacheTestCase(BaseAuthTestCase): def setUp(self): super().setUp() diff --git a/authome/cache/clustercache.py b/authome/cache/clustercache.py index 6cd3302..22daabc 100644 --- a/authome/cache/clustercache.py +++ b/authome/cache/clustercache.py @@ -461,6 +461,32 @@ def _send_request(cluster): except Exception as ex: return (False,str(ex)) + def get_auth2_status(self,clusterid): + """ + get the status of the cluster server + Return server status + """ + def _send_request(cluster): + return requests.get("{}{}".format( + cluster.endpoint, + reverse('cluster:auth2_status',kwargs={"clusterid":cluster.clusterid}) + ),headers=self._get_headers(),timeout=settings.AUTH2_INTERCONNECTION_TIMEOUT,verify=settings.SSL_VERIFY) + res = self._send_request_to_cluster(None,clusterid,_send_request) + return res.text + + def get_auth2_liveness(self,clusterid,serviceid,monitordate): + """ + get the status of the cluster server + Return server status + """ + def _send_request(cluster): + return requests.get("{}{}".format( + cluster.endpoint, + reverse('cluster:auth2_liveness',kwargs={"clusterid":cluster.clusterid,"serviceid":serviceid,"monitordate":monitordate}) + ),headers=self._get_headers(),timeout=settings.AUTH2_INTERCONNECTION_TIMEOUT,verify=settings.SSL_VERIFY) + res = self._send_request_to_cluster(None,clusterid,_send_request) + return res.text + @property def status(self): result = super().status diff --git a/authome/models/clustermodels.py b/authome/models/clustermodels.py index d9fe828..b411de9 100644 --- a/authome/models/clustermodels.py +++ b/authome/models/clustermodels.py @@ -29,7 +29,7 @@ class Auth2Cluster(models.Model): modified = models.DateTimeField(editable=False,auto_now=False) class Meta: - verbose_name_plural = "{}Auth2 Clusters".format(" " * 2) + verbose_name_plural = "{}Auth2 Clusters".format(" " * 7) @classmethod def register(cls,only_update_heartbeat=False): diff --git a/authome/models/debugmodels.py b/authome/models/debugmodels.py index 322c506..d68cac4 100644 --- a/authome/models/debugmodels.py +++ b/authome/models/debugmodels.py @@ -80,7 +80,7 @@ class DebugLog(models.Model): message = models.TextField(editable=False,null=True) class Meta: - verbose_name_plural = "{}Auth2 Logs".format(" " * 0) + verbose_name_plural = "{}Auth2 Logs".format(" " * 4) @classmethod def attach_request(cls,request): diff --git a/authome/models/models.py b/authome/models/models.py index 4054f73..27502e1 100644 --- a/authome/models/models.py +++ b/authome/models/models.py @@ -321,7 +321,7 @@ class IdentityProvider(CacheableMixin,DbObjectMixin,models.Model): created = models.DateTimeField(auto_now_add=timezone.now) class Meta: - verbose_name_plural = "{}Identity Providers".format(" " * 4) + verbose_name_plural = "{}Identity Providers".format(" " * 9) @classmethod def get_model_change_cls(self): @@ -550,7 +550,7 @@ class CustomizableUserflow(CacheableMixin,DbObjectMixin,models.Model): created = models.DateTimeField(auto_now_add=timezone.now) class Meta: - verbose_name_plural = "{}Customizable Userflows".format(" " * 5) + verbose_name_plural = "{}Customizable Userflows".format(" " * 10) @classmethod def get_model_change_cls(self): @@ -875,7 +875,7 @@ class UserGroup(CacheableMixin,DbObjectMixin,models.Model): class Meta: unique_together = [["users","excluded_users"]] - verbose_name_plural = "{}User Groups".format(" " * 7) + verbose_name_plural = "{}User Groups".format(" " * 12) @property @@ -1604,7 +1604,7 @@ class UserAuthorization(CacheableMixin,AuthorizationMixin): class Meta: unique_together = [["user","domain"]] - verbose_name_plural = "{}User Authorizations".format(" " * 2) + verbose_name_plural = "{}User Authorizations".format(" " * 7) @classmethod def get_model_change_cls(self): @@ -1650,7 +1650,7 @@ class UserGroupAuthorization(CacheableMixin,AuthorizationMixin): class Meta: unique_together = [["usergroup","domain"]] - verbose_name_plural = "{}User Group Authorizations".format(" " * 6) + verbose_name_plural = "{}User Group Authorizations".format(" " * 11) @classmethod def get_model_change_cls(self): @@ -1702,7 +1702,7 @@ class User(AbstractUser): class Meta(AbstractUser.Meta): swappable = 'AUTH_USER_MODEL' db_table = "auth_user" - verbose_name_plural = "{}Users".format(" " * 9) + verbose_name_plural = "{}Users".format(" " * 14) unique_together = [["email"]] def clean(self): @@ -1744,7 +1744,7 @@ class UserToken(models.Model): modified = models.DateTimeField(editable=False,db_index=True,auto_now=True) class Meta: - verbose_name_plural = "{}Access Tokens".format(" " * 3) + verbose_name_plural = "{}Access Tokens".format(" " * 8) def __str__(self): return self.user.email @@ -1840,7 +1840,7 @@ class UserTOTP(models.Model): created = models.DateTimeField(null=False,editable=False) class Meta: - verbose_name_plural = "{}User TOTPs".format(" " * 3) + verbose_name_plural = "{}User TOTPs".format(" " * 8) class UserListener(object): @staticmethod diff --git a/authome/models/trafficmodels.py b/authome/models/trafficmodels.py index 7bc6fcd..f7de90b 100644 --- a/authome/models/trafficmodels.py +++ b/authome/models/trafficmodels.py @@ -36,7 +36,7 @@ class TrafficData(models.Model): domains = models.JSONField(null=True,editable=False) class Meta: - verbose_name_plural = "{}Traffic Data".format(" " * 1) + verbose_name_plural = "{}Traffic Data".format(" " * 6) unique_together = [["clusterid","start_time","end_time","batchid"]] class SSOMethodTrafficData(models.Model): @@ -121,7 +121,7 @@ class TrafficReport(models.Model): domains = models.JSONField(null=True,editable=False) class Meta: - verbose_name_plural = "{}Traffic Report".format(" " * 1) + verbose_name_plural = "{}Traffic Report".format(" " * 5) unique_together = [["clusterid","report_type","start_time"]] @classmethod diff --git a/authome/redis.py b/authome/redis.py index dadf1ae..34f8908 100644 --- a/authome/redis.py +++ b/authome/redis.py @@ -14,6 +14,7 @@ from django.conf import settings from . import utils +from .serializers import Processtime logger = logging.getLogger(__name__) @@ -60,6 +61,11 @@ def _cache(self): else: return self._class(self._servers, **self._options) + def ttl(self, key): + return self._cache.ttl(self.make_and_validate_key(key)) + + def expire(self, key,timeout): + return self._cache.expire(self.make_and_validate_key(key), timeout) def _parse_server(self,server=None): """ @@ -187,6 +193,14 @@ def __init__(self, servers, **options ): options["retry"] = redis.retry.Retry(redis.backoff.NoBackoff(),retry_attempts) super().__init__(servers,**options) + def ttl(self, key): + client = self.get_client(key) + return client.ttl(key) + + def expire(self, key,timeout): + client = self.get_client(key, write=True) + return client.expire(key,timeout) + class RedisCacheClient(BaseRedisCacheClient): _redisclient = None def get_client(self, key=None, *, write=False): @@ -266,17 +280,17 @@ def ping(self): working = True if isinstance(redisclients,list): for redisclient in redisclients: - starttime = timezone.local() + starttime = timezone.localtime() status = self.ping_redis(redisclient) - pingstatus[redisclient[0]] = {"ping":status[0],"pingtime":round((timezone.localtime() - starttime).total_seconds(),6)} + pingstatus[redisclient[0]] = {"ping":status[0],"pingtime":Processtime((timezone.localtime() - starttime).total_seconds())} if not status[0]: working = False if status[1]: pingstatus[redisclient[0]]["error"] = status[1] else: - starttime = timezone.local() - status = self.ping_redis(redisclients,pingtimes) - pingstatus[redisclients[0]] = {"ping":status[0],"pingtime":round((timezone.localtime() - starttime).total_seconds(),6)} + starttime = timezone.localtime() + status = self.ping_redis(redisclients) + pingstatus[redisclients[0]] = {"ping":status[0],"pingtime":Processtime((timezone.localtime() - starttime).total_seconds())} if not status[0]: working = False if status[1]: @@ -764,6 +778,14 @@ def __init__(self, *args,auto_failover=False,**kwargs): else: self._client = RedisCluster + def ttl(self, key): + client = self.get_client(key) + return client.ttl(key) + + def expire(self, key,timeout): + client = self.get_client(key, write=True) + return client.expire(key,timeout) + def get_client(self, key=None, *, write=False): # key is used so that the method signature remains the same and custom # cache client can be implemented which might require the key to select @@ -835,7 +857,7 @@ def ping(self): for redisclient in redisclients: starttime = timezone.localtime() status = self.ping_redis(redisclient) - pingstatus[redisclient[0]] = {"ping":status[0],"pingtime":round((timezone.localtime() - starttime).total_seconds(),6)} + pingstatus[redisclient[0]] = {"ping":status[0],"pingtime":Processtime((timezone.localtime() - starttime).total_seconds())} if not status[0]: if redisclient[0] not in self._failed_cluster_nodes: self._failed_cluster_nodes.append(redisclient[0]) diff --git a/authome/response.py b/authome/response.py new file mode 100644 index 0000000..54e58c1 --- /dev/null +++ b/authome/response.py @@ -0,0 +1,89 @@ +import itertools +import mimetypes +import io +import os + +from django.http import HttpResponse,StreamingHttpResponse +from django.utils.http import content_disposition_header + +def filereaderfactory(filein,block_size): + def _filereader(): + return filein.read(block_size) + + return _filereader + +class MultiFileSegmentsResponse(StreamingHttpResponse): + """ + A streaming HTTP response class optimized for a file which is consisted with multi file segments.. + """ + + block_size = 4096 + + def __init__(self, *args, as_attachment=False, filename="", **kwargs): + self.as_attachment = as_attachment + self.filename = filename + self._no_explicit_content_type = ( + "content_type" not in kwargs or kwargs["content_type"] is None + ) + super().__init__(*args, **kwargs) + + def _set_streaming_content(self, files): + streams = [] + self.files = files + for file in files: + filein = open(file,'rb') + self._resource_closers.append(filein.close) + streams.append(filein) + + self.set_headers(streams) + super()._set_streaming_content(itertools.chain(*[iter(filereaderfactory(filein,self.block_size), b"") for filein in streams])) + + def set_headers(self, streams): + """ + Set some common response headers (Content-Length, Content-Type, and + Content-Disposition) based on the `filelike` response content. + """ + content_length = 0 + for i in range(len(streams)): + filelike = streams[i] + filename = self.files[i] + seekable = hasattr(filelike, "seek") and ( + not hasattr(filelike, "seekable") or filelike.seekable() + ) + if hasattr(filelike, "tell"): + if seekable: + initial_position = filelike.tell() + filelike.seek(0, io.SEEK_END) + content_length += filelike.tell() - initial_position + filelike.seek(initial_position) + elif hasattr(filelike, "getbuffer"): + content_length += filelike.getbuffer().nbytes - filelike.tell() + elif os.path.exists(filename): + content_length += os.path.getsize(filename) - filelike.tell() + elif seekable: + length += sum(iter(lambda: len(filelike.read(self.block_size)), 0)) + content_length += length + filelike.seek(-1 * length, io.SEEK_END) + self.headers["Content-Length"] = (content_length) + filename = os.path.basename(self.filename or filename) + if self._no_explicit_content_type: + if filename: + content_type, encoding = mimetypes.guess_type(filename) + # Encoding isn't set to prevent browsers from automatically + # uncompressing files. + content_type = { + "bzip2": "application/x-bzip", + "gzip": "application/gzip", + "xz": "application/x-xz", + }.get(encoding, content_type) + self.headers["Content-Type"] = ( + content_type or "application/octet-stream" + ) + else: + self.headers["Content-Type"] = "application/octet-stream" + + if content_disposition := content_disposition_header( + self.as_attachment, filename + ): + self.headers["Content-Disposition"] = content_disposition + diff --git a/authome/serializers/__init__.py b/authome/serializers/__init__.py index 2b940b9..687c21b 100644 --- a/authome/serializers/__init__.py +++ b/authome/serializers/__init__.py @@ -1 +1 @@ -from .json import JSONEncoder,JSONDecoder,JSONFormater +from .json import JSONEncoder,JSONDecoder,JSONFormater,Processtime diff --git a/authome/serializers/json.py b/authome/serializers/json.py index 69326f3..f40d51f 100644 --- a/authome/serializers/json.py +++ b/authome/serializers/json.py @@ -3,6 +3,16 @@ from django.utils import timezone +class Formatable(object): + def format(self): + return self.__repr__() + +class Processtime(Formatable): + def __init__(self,val): + self.val = val + def __repr__(self): + return "{:.6f}".format(self.val) + class JSONDecoder(json.JSONDecoder): def __init__(self,*args, **kwargs): @@ -45,7 +55,7 @@ def default(self, obj): 'value' : [obj.days,obj.seconds,obj.microseconds] } else: - return JSONEncoder.default(self, obj) + return super().default(obj) class JSONFormater(json.JSONEncoder): """ Instead of letting the default encoder convert datetime to string, @@ -60,5 +70,7 @@ def default(self, obj): return obj.strftime(obj,"%Y-%m-%d") elif isinstance(obj, timedelta): return "{}.{}".format(obj.days * 86400 + obj.seconds,obj.microseconds) + elif isinstance(obj, Formatable): + return obj.format() else: - return JSONEncoder.default(self, obj) + return super().default(obj) diff --git a/authome/settings.py b/authome/settings.py index ea2d88a..b4ceeba 100644 --- a/authome/settings.py +++ b/authome/settings.py @@ -53,6 +53,7 @@ EMAIL_PORT = env('EMAIL_PORT', 25) AUTH2_DOMAIN = env("AUTH2_DOMAIN",default="auth2.dbca.wa.gov.au") +AUTH2_MONITORING_DIR=env("AUTH2_MONITORING_DIR") TOTP_SECRET_KEY_LENGTH = env("TOTP_SECRET_KEY_LENGTH",default=128) TOTP_ISSUER = env("TOTP_ISSUER",default="DBCA") diff --git a/authome/templates/authome/auth2serverstatus.html b/authome/templates/authome/auth2serverstatus.html new file mode 100644 index 0000000..78f73c6 --- /dev/null +++ b/authome/templates/authome/auth2serverstatus.html @@ -0,0 +1,112 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_urls static admin_list %} + +{% block extrastyle %} + + + {{ media.css }} + +{% endblock %} + +{% block extrahead %} +{{ block.super }} + + +{{ media.js }} +{% endblock %} + +{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %} + +{% if not is_popup %} +{% block breadcrumbs %} + +{% endblock %} +{% endif %} + +{% block coltype %}{% endblock %} + +{% block content %} + + + + + + + + + + + + + + {{data}} + +
Auth2 ServerReady TimeHeartbeatPingTimePingStatusResource UsageMonitoring
+{% endblock %} diff --git a/authome/templates/authome/healthcheck_not_enabled.html b/authome/templates/authome/healthcheck_not_enabled.html new file mode 100644 index 0000000..7efee9f --- /dev/null +++ b/authome/templates/authome/healthcheck_not_enabled.html @@ -0,0 +1,32 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_urls static admin_list %} + +{% block extrastyle %} + + + {{ media.css }} + +{% endblock %} + +{% block extrahead %} +{{ block.super }} +{{ media.js }} +{% endblock %} + +{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %} + +{% if not is_popup %} +{% block breadcrumbs %} + +{% endblock %} +{% endif %} + +{% block coltype %}{% endblock %} + +{% block content %} +

{{message}}

+{% endblock %} diff --git a/authome/templates/authome/livenessfile_missing.html b/authome/templates/authome/livenessfile_missing.html new file mode 100644 index 0000000..14236fe --- /dev/null +++ b/authome/templates/authome/livenessfile_missing.html @@ -0,0 +1,32 @@ +{% extends "admin/base_site.html" %} +{% load i18n admin_urls static admin_list %} + +{% block extrastyle %} + + + {{ media.css }} + +{% endblock %} + +{% block extrahead %} +{{ block.super }} +{{ media.js }} +{% endblock %} + +{% block bodyclass %}{{ block.super }} app-{{ opts.app_label }} model-{{ opts.model_name }} change-list{% endblock %} + +{% if not is_popup %} +{% block breadcrumbs %} + +{% endblock %} +{% endif %} + +{% block coltype %}{% endblock %} + +{% block content %} +

{{message}}

+{% endblock %} diff --git a/authome/test_funcs.py b/authome/test_funcs.py index 0452bd2..6e7749c 100644 --- a/authome/test_funcs.py +++ b/authome/test_funcs.py @@ -3,8 +3,9 @@ from . import models from . import utils +from .basetest import BaseTestCase -class UserEmailTestCase(TestCase): +class UserEmailTestCase(BaseTestCase): def test_parse_url(self): print("============================================================================") testcases = [ diff --git a/authome/test_usergroup.py b/authome/test_usergroup.py new file mode 100644 index 0000000..94d1e15 --- /dev/null +++ b/authome/test_usergroup.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- +from datetime import timedelta + +from django.contrib.auth.models import User +from django.urls import reverse +from django.utils import timezone +from django.test import TestCase +from django.conf import settings + +import base64 + +from .models import UserGroup +from .cache import cache +from .basetest import BaseAuthTestCase + +class UsergroupTestCase(BaseAuthTestCase): + def test_sessiontimeout(self): + self.test_usergroups = [ + ("staff","@staff.com",None,0,None), + ("special user","*special*@test.com",None,1800,None), + ("test user","@test.com",None,3600,[ + ("backend test user","*backend*@test.com",None,7200,None), + ("frontend test user","*frontend*@test.com",None,600,None), + ("manager user","*manager*@test.com",None,10800,None) + ]), + ] + self.populate_testdata() + for case in ( + ("test@example.com",UserGroup.public_group().session_timeout), + ("test@staff.com",0), + ("test1@test.com",3600), + ("backend_test1@test.com",7200), + ("frontend_test1@test.com",600), + ("manager_test1@test.com",10800), + ("manager_frontend_test1@test.com",10800), + ("manager_backend_test1@test.com",10800), + + ("special_test1@test.com",3600), + ("special_backend_test1@test.com",7200), + ("special_frontend_test1@test.com",1800), + ("special_manager_test1@test.com",10800), + ("special_manager_frontend_test1@test.com",10800), + ("special_manager_backend_test1@test.com",10800) + ): + usergroups = UserGroup.find_groups(case[0])[0] + sessiontimeout = UserGroup.get_session_timeout(usergroups) or 0 + self.assertEqual(sessiontimeout,case[1],"The session timeout of user({0}) should be {1} instead of {2}".format(case[0],case[1],sessiontimeout)) + + diff --git a/authome/testcluster.py b/authome/testcluster.py index 34576b1..c240511 100644 --- a/authome/testcluster.py +++ b/authome/testcluster.py @@ -80,7 +80,7 @@ def test_previous_session_cache(self): res.raise_for_status() user_profile = res.json() self.assertEqual(user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - session_data = self.get_session_data(session_cookie,"standalone") + session_data,ttl = self.get_session_data(session_cookie,"standalone") print("\n".join("{}={}".format(k,v) for k,v in user_profile.items())) print("\n") @@ -92,7 +92,7 @@ def test_previous_session_cache(self): self.assertEqual(self.clean_cookie(res.cookies.get(settings.SESSION_COOKIE_NAME)),None,msg="Session is migrated from previous session cache. and the client session cookie should not be changed, response should not return session cookie..auth2_envs={}".format(auth2_envs)) user_profile2 = res.json() self.assertEqual(user_profile2["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - session_data2 = self.get_session_data(session_cookie,"standalone2") + session_data2,ttl = self.get_session_data(session_cookie,"standalone2") self.assertEqual(session_data2,session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("\n".join("{}={}".format(k,v) for k,v in user_profile2.items())) print("\n") @@ -165,7 +165,7 @@ def test_migrate_to_default_cluster(self): res.raise_for_status() user_profile = res.json() self.assertEqual(user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - session_data = self.get_session_data(session_cookie,"standalone") + session_data,ttl = self.get_session_data(session_cookie,"standalone") print("\n".join("{}={}".format(k,v) for k,v in user_profile.items())) print("\n") @@ -176,6 +176,8 @@ def test_migrate_to_default_cluster(self): self.cluster_headers["X-LB-HASH-KEY"] = auth01_lb_hash_key res = requests.get(self.get_profile_url("auth01"),headers=self.cluster_headers,cookies={settings.SESSION_COOKIE_NAME:session_cookie},verify=settings.SSL_VERIFY) res.raise_for_status() + print("\n".join("{}={}".format(k,v) for k,v in res.json().items())) + print("\n") auth01_session_cookie = self.clean_cookie(res.cookies[settings.SESSION_COOKIE_NAME]) returned_auth01_lb_hash_key,auth01_clusterid,auth01_signature,auth01_session_key = auth01_session_cookie.split("|",3) self.assertEqual(auth01_lb_hash_key,returned_auth01_lb_hash_key,msg="Hash key in session cookie should have the same value as the request header 'X-LB-HASH-KEY'.auth2_envs={}".format(auth2_envs)) @@ -183,7 +185,7 @@ def test_migrate_to_default_cluster(self): self.assertEqual(session_cookie,auth01_session_key,msg="Cluster session id should be populated from original session key.auth2_envs={}".format(auth2_envs)) auth01_user_profile = res.json() self.assertEqual(auth01_user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - auth01_session_data = self.get_session_data(auth01_session_cookie,"auth01") + auth01_session_data,ttl = self.get_session_data(auth01_session_cookie,"auth01") self.assertEqual(auth01_session_data,session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("Succeed to migrate the session key({}) to cluster enabled session key({})".format(session_cookie,auth01_session_cookie)) print("\n".join("{}={}".format(k,v) for k,v in auth01_user_profile.items())) @@ -208,7 +210,7 @@ def test_migrate_to_default_cluster(self): self.assertEqual(auth01_session_cookie2,auth01_session_cookie,msg="The session cookie should be same no matter how many times a session is migrated to the same cluster server.auth2_envs={}".format(auth2_envs)) auth01_user_profile2 = res.json() self.assertEqual(auth01_user_profile2["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - auth01_session_data2 = self.get_session_data(auth01_session_cookie2,"auth01") + auth01_session_data2,ttl = self.get_session_data(auth01_session_cookie2,"auth01") self.assertEqual(auth01_session_data2,session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) finally: @@ -293,7 +295,7 @@ def test_migrate_to_default_cluster_with_previous_cache(self): res.raise_for_status() user_profile = res.json() self.assertEqual(user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - session_data = self.get_session_data(session_cookie,"standalone") + session_data,ttl = self.get_session_data(session_cookie,"standalone") print("\n".join("{}={}".format(k,v) for k,v in user_profile.items())) print("\n") @@ -311,7 +313,7 @@ def test_migrate_to_default_cluster_with_previous_cache(self): self.assertEqual(session_cookie,auth02_session_key,msg="Cluster session id should be populated from original session key.auth2_envs={}".format(auth2_envs)) auth02_user_profile = res.json() self.assertEqual(auth02_user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - auth02_session_data = self.get_session_data(auth02_session_cookie,"auth02a") + auth02_session_data,ttl = self.get_session_data(auth02_session_cookie,"auth02a") self.assertEqual(auth02_session_data,session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("Succeed to migrate the session key({}) to cluster enabled session key({})".format(session_cookie,auth02_session_cookie)) print("\n".join("{}={}".format(k,v) for k,v in auth02_user_profile.items())) @@ -342,7 +344,7 @@ def test_migrate_to_default_cluster_with_previous_cache(self): self.assertEqual(auth02_session_cookie2,auth02_session_cookie,msg="The session cookie should be the same no matter how many times a session is migrated to the same cluster server.auth2_envs={}".format(auth2_envs)) auth02_user_profile2 = res.json() self.assertEqual(auth02_user_profile2["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - auth02_session_data2 = self.get_session_data(auth02_session_cookie2,"auth02a") + auth02_session_data2,ttl = self.get_session_data(auth02_session_cookie2,"auth02a") self.assertEqual(auth02_session_data2,session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("Succeed to migrate the session key({}) to cluster enabled session key({})".format(session_cookie,auth02_session_cookie2)) print("\n") @@ -415,7 +417,7 @@ def test_migrate_to_other_cluster(self): res.raise_for_status() user_profile = res.json() self.assertEqual(user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs=".format(auth2_envs)) - session_data = self.get_session_data(session_cookie,"standalone") + session_data,ttl = self.get_session_data(session_cookie,"standalone") print("\n".join("{}={}".format(k,v) for k,v in user_profile.items())) #migrate to session to auth02 @@ -435,7 +437,7 @@ def test_migrate_to_other_cluster(self): self.assertEqual(session_cookie,auth02_session_key,msg="Cluster session id should be populated from original session key.auth2_envs=".format(auth2_envs)) auth02_user_profile = res.json() self.assertEqual(auth02_user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs=".format(auth2_envs)) - auth02_session_data = self.get_session_data(auth02_session_cookie,"auth02") + auth02_session_data,ttl = self.get_session_data(auth02_session_cookie,"auth02") self.assertEqual(auth02_session_data,session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("Succeed to migrate the session key({}) to cluster enabled session key({})".format(session_cookie,auth02_session_cookie)) print("\n".join("{}={}".format(k,v) for k,v in auth02_user_profile.items())) @@ -457,7 +459,7 @@ def test_migrate_to_other_cluster(self): self.assertEqual(auth02_session_cookie2,auth02_session_cookie,msg="The session cookie should be the same no matter how many times a session is migrated to the same cluster server.auth2_envs=".format(auth2_envs)) auth02_user_profile2 = res.json() self.assertEqual(auth02_user_profile2["authenticated"],True,msg="User should have already loged in.auth2_envs=".format(auth2_envs)) - auth02_session_data2 = self.get_session_data(auth02_session_cookie2,"auth02") + auth02_session_data2,ttl = self.get_session_data(auth02_session_cookie2,"auth02") self.assertEqual(auth02_session_data2,session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("Succeed to migrate the session key({}) to cluster enabled session key({})".format(session_cookie,auth02_session_cookie2)) @@ -553,7 +555,7 @@ def test_migrate_to_other_cluster_with_previous_cache(self): res.raise_for_status() user_profile = res.json() self.assertEqual(user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - session_data = self.get_session_data(session_cookie,"standalone") + session_data,ttl = self.get_session_data(session_cookie,"standalone") print("\n".join("{}={}".format(k,v) for k,v in user_profile.items())) print("\n") @@ -573,7 +575,7 @@ def test_migrate_to_other_cluster_with_previous_cache(self): self.assertEqual(session_cookie,auth03_session_key,msg="Cluster session id should be populated from original session key.auth2_envs={}".format(auth2_envs)) auth03_user_profile = res.json() self.assertEqual(auth03_user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - auth03_session_data = self.get_session_data(auth03_session_cookie,"auth03") + auth03_session_data,ttl = self.get_session_data(auth03_session_cookie,"auth03") self.assertEqual(auth03_session_data,session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("Succeed to migrate the session key({}) to cluster enabled session key({})".format(session_cookie,auth03_session_cookie)) print("\n".join("{}={}".format(k,v) for k,v in auth03_user_profile.items())) @@ -610,7 +612,7 @@ def test_migrate_to_other_cluster_with_previous_cache(self): self.assertEqual(auth03_session_cookie2,auth03_session_cookie,msg="The session cookie should be the same no matter how many times a session is migrated to the same cluster server.auth2_envs={}".format(auth2_envs)) auth03_user_profile2 = res.json() self.assertEqual(auth03_user_profile2["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - auth03_session_data2 = self.get_session_data(auth03_session_cookie2,"auth03") + auth03_session_data2,ttl = self.get_session_data(auth03_session_cookie2,"auth03") self.assertEqual(auth03_session_data2,session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("Succeed to migrate the session key({}) to cluster enabled session key({})".format(session_cookie,auth03_session_cookie2)) print("\n") @@ -677,7 +679,7 @@ def test_migrate_session_among_clusters(self): res.raise_for_status() auth01_user_profile = res.json() self.assertEqual(auth01_user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs=".format(auth2_envs)) - auth01_session_data = self.get_session_data(auth01_session_cookie,"auth01") + auth01_session_data,ttl = self.get_session_data(auth01_session_cookie,"auth01") print("original session cookie = {}".format(auth01_session_cookie)) print("\n".join("{}={}".format(k,v) for k,v in auth01_user_profile.items())) @@ -692,7 +694,7 @@ def test_migrate_session_among_clusters(self): self.assertEqual(auth02_session_key,auth01_session_key,msg="The session key should not be changed during migration.auth2_envs=".format(auth2_envs)) auth02_user_profile = res.json() self.assertEqual(auth02_user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs=".format(auth2_envs)) - auth02_session_data = self.get_session_data(auth02_session_cookie,"auth02") + auth02_session_data,ttl = self.get_session_data(auth02_session_cookie,"auth02") self.assertEqual(auth02_session_data,auth01_session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("migrated the auth01 sesesion cookie '{}' to auth02 session cookie '{}'".format(auth01_session_cookie,auth02_session_cookie)) print("\n") @@ -712,7 +714,7 @@ def test_migrate_session_among_clusters(self): self.assertEqual(auth02_session_cookie2,auth02_session_cookie,msg="The session cookie should be the same no matter how many times a session is migrated to the same cluster server.auth2_envs=".format(auth2_envs)) auth02_user_profile2 = res.json() self.assertEqual(auth02_user_profile2["authenticated"],True,msg="User should have already loged in.auth2_envs=".format(auth2_envs)) - auth02_session_data2 = self.get_session_data(auth02_session_cookie2,"auth02") + auth02_session_data2,ttl = self.get_session_data(auth02_session_cookie2,"auth02") self.assertEqual(auth02_session_data2,auth01_session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("\n") @@ -724,7 +726,7 @@ def test_migrate_session_among_clusters(self): auth01_session_cookie3 = self.clean_cookie(self.clean_cookie(res.cookies[settings.SESSION_COOKIE_NAME])) self.assertEqual(auth01_session_cookie3,auth01_session_cookie,msg="The session cookie of the migrated back session should be the same value as the original session cookie.auth2_envs=".format(auth2_envs)) self.assertEqual(auth01_user_profile3["authenticated"],True,msg="User should have already loged in.auth2_envs=".format(auth2_envs)) - auth01_session_data3 = self.get_session_data(auth01_session_cookie3,"auth01") + auth01_session_data3,ttl = self.get_session_data(auth01_session_cookie3,"auth01") self.assertEqual(auth01_session_data3,auth01_session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("migrated the auth2 session cookie '{}' back to auth01 session cookie = {}".format(auth02_session_cookie,auth01_session_cookie3)) print("\n") @@ -744,7 +746,7 @@ def test_migrate_session_among_clusters(self): auth01_session_cookie4 = self.clean_cookie(self.clean_cookie(res.cookies[settings.SESSION_COOKIE_NAME])) self.assertEqual(auth01_session_cookie4,auth01_session_cookie,msg="The session cookie should be the same no matter how many times a session is migrated to the same cluster server.auth2_envs=".format(auth2_envs)) self.assertEqual(auth01_user_profile4["authenticated"],True,msg="User should have already loged in.auth2_envs=".format(auth2_envs)) - auth01_session_data4 = self.get_session_data(auth01_session_cookie4,"auth01") + auth01_session_data4,ttl = self.get_session_data(auth01_session_cookie4,"auth01") self.assertEqual(auth01_session_data4,auth01_session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("\n") @@ -811,7 +813,7 @@ def test_cluster_with_previous_cache(self): res.raise_for_status() auth01_user_profile = res.json() self.assertEqual(auth01_user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - auth01_session_data = self.get_session_data(auth01_session_cookie,"auth01") + auth01_session_data,ttl = self.get_session_data(auth01_session_cookie,"auth01") print("\n".join("{}={}".format(k,v) for k,v in auth01_user_profile.items())) print("\n") @@ -825,7 +827,7 @@ def test_cluster_with_previous_cache(self): self.assertEqual(self.clean_cookie(res.cookies.get(settings.SESSION_COOKIE_NAME)),None,msg="Session is only migrated from previous session cache. and the client session cookie should not be changed..auth2_envs={}".format(auth2_envs)) auth01a_user_profile = res.json() self.assertEqual(auth01a_user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - auth01a_session_data = self.get_session_data(auth01_session_cookie,"auth01a") + auth01a_session_data,ttl = self.get_session_data(auth01_session_cookie,"auth01a") self.assertEqual(auth01a_session_data,auth01_session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) print("\n".join("{}={}".format(k,v) for k,v in auth01a_user_profile.items())) print("\n") @@ -912,7 +914,7 @@ def test_migrate_among_clusters_with_previous_cache(self): res.raise_for_status() auth01_user_profile = res.json() self.assertEqual(auth01_user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - auth01_session_data = self.get_session_data(auth01_session_cookie,"auth01") + auth01_session_data,ttl = self.get_session_data(auth01_session_cookie,"auth01") print("\n".join("{}={}".format(k,v) for k,v in auth01_user_profile.items())) print("\n") @@ -930,7 +932,7 @@ def test_migrate_among_clusters_with_previous_cache(self): self.assertEqual(auth03_clusterid,"AUTH2_03",msg="Returned auth2 cluster name in the session cookie should be the same name as the cluster name of cluster 'auth02'.auth2_envs=".format(auth2_envs)) self.assertEqual(auth03_session_key,auth01_session_key,msg="The random generated session key(not including the hash value of lb hash key and auth2 cluster id) should be the same value as the migrated session key.auth2_envs=".format(auth2_envs)) self.assertEqual(auth03_user_profile["authenticated"],True,msg="User should have already loged in.auth2_envs={}".format(auth2_envs)) - auth03_session_data = self.get_session_data(auth03_session_cookie,"auth03") + auth03_session_data,ttl = self.get_session_data(auth03_session_cookie,"auth03") self.assertEqual(auth03_session_data,auth01_session_data,msg="Session data should not be changed during migration.auth2_envs={}".format(auth2_envs)) #session has migrated from previous cache, check whether it is not available in the original session cache diff --git a/authome/testsessiontimeout.py b/authome/testsessiontimeout.py new file mode 100644 index 0000000..66958cd --- /dev/null +++ b/authome/testsessiontimeout.py @@ -0,0 +1,75 @@ +import requests +import time +from datetime import timedelta + +from django.conf import settings +from django.test import TestCase +from django.utils import timezone + +from . import utils +from . import testutils + +from .basetest import BaseTestCase +from . import models +""" +To run this test case, you should have the following resources +1. a shell script to start auth2 server './start_auth2' +2. the set of env files to start different type of auth2 server + A. .env.auth01.rediscluster: a auth2 cluster server using redis cluster + B. .env.auth01.redis: a auth2 cluster server using redis +3. set the user group + A. staff group with no timeout setting + B. public group with timeout setting +""" +class UserSessionTimeoutTestCase(testutils.StartServerMixin,BaseTestCase): + @classmethod + def setUpClass(cls): + super(UserSessionTimeoutTestCase,cls).setUpClass() + cls.disable_messages() + + def _test(self,auth2_env): + try: + sessionage = 36000 + self.start_auth2_server("auth01",18060,auth2_env={"SESSION_AGE":sessionage},start=True) + for user in ["test1@dbca.wa.gov.au","test@test11.com"]: + usergroups = models.UserGroup.find_groups(user)[0] + timeout = models.UserGroup.get_session_timeout(usergroups) or 0 + print("=============================user={} , timeout = {}====================".format(user,timeout)) + before_login = timezone.localtime() + res = requests.get(self.get_login_user_url(user,"auth01"),headers=self.cluster_headers,verify=settings.SSL_VERIFY) + after_login = timezone.localtime() + res.raise_for_status() + session_cookie = self.clean_cookie(res.cookies[settings.SESSION_COOKIE_NAME]) + time.sleep(5) + sessiondata,ttl1 = self.get_session_data(session_cookie,"auth01",exist=True) + if timeout: + minttl = int((before_login + timedelta(seconds=timeout)- timezone.localtime()).total_seconds()) + else: + minttl = int((before_login + timedelta(seconds=sessionage)- timezone.localtime()).total_seconds()) + self.assertTrue(ttl1 >= minttl,"The ttl({1}) of the user({0}) should be greater than {2}".format(user,ttl1,minttl)) + + time.sleep(5) + before_profile = timezone.localtime() + res = requests.get(self.get_profile_url("auth01"),headers=self.cluster_headers,cookies={settings.SESSION_COOKIE_NAME:session_cookie},verify=settings.SSL_VERIFY) + after_profile = timezone.localtime() + + sessiondata,ttl2 = self.get_session_data(session_cookie,"auth01",exist=True) + if timeout: + minttl = int((before_profile + timedelta(seconds=timeout)- timezone.localtime()).total_seconds()) + self.assertTrue(ttl2 > ttl1,"The second ttl({2}) of the user({0}) should be less than the first ttl {1}".format(user,ttl1,ttl2)) + else: + minttl = int((before_login + timedelta(seconds=sessionage)- timezone.localtime()).total_seconds()) + self.assertTrue(ttl2 < ttl1,"The second ttl({2}) of the user({0}) should be less than the first ttl {1}".format(user,ttl1,ttl2)) + self.assertTrue(ttl2 >= minttl,"The ttl({1}) of the user({0}) should be greater than {2}".format(user,ttl2,minttl)) + + + finally: + self.shutdown_all_auth2_servers() + + def test_rediscluster(self): + print("==========test sessiontimeout with rediscluster============") + self._test("auth01.rediscluster") + + def test_redis(self): + print("==========test sessiontimeout with redis============") + self._test("auth01.redis") diff --git a/authome/testutils.py b/authome/testutils.py index 56ab929..5d68c0e 100644 --- a/authome/testutils.py +++ b/authome/testutils.py @@ -51,9 +51,13 @@ def clean_cookie(cls,cookie): @classmethod - def start_auth2_server(cls,servername,port,auth2_env=None): + def start_auth2_server(cls,servername,port,auth2_env=None,start=True): if servername in cls.process_map: raise Exception("Server({}) is already running".format(servername)) + if not start: + cls.process_map[servername] = (None,port) + return + auth2_env = " && ".join("export {0}=\"{1}\"".format(k,(auth2_env or {}).get(k,cls.default_env.get(k))) for k,v in cls.default_env.items()) command = "/bin/bash -c 'set -a && export PORT={2} && source {0}/.env.{1} && {3} && poetry run python manage.py runserver 0.0.0.0:{2}'".format(settings.BASE_DIR,servername,port,auth2_env) @@ -77,6 +81,8 @@ def start_auth2_server(cls,servername,port,auth2_env=None): @classmethod def shutdown_auth2_server(cls,servername="standalone"): if servername in cls.process_map: + if cls.process_map[servername][0] is None: + return print("shutdown auth2 server({})".format(servername)) os.killpg(os.getpgid(cls.process_map[servername][0].pid), signal.SIGTERM) del cls.process_map[servername] @@ -85,6 +91,8 @@ def shutdown_auth2_server(cls,servername="standalone"): @classmethod def shutdown_all_auth2_servers(cls): for servername,server in cls.process_map.items(): + if server[0] is None: + continue print("shutdown auth2 server({})".format(servername)) os.killpg(os.getpgid(server[0].pid), signal.SIGTERM) time.sleep(1) @@ -153,10 +161,12 @@ def get_session_data(self,session_cookie,servername="standalone",exist=True): res = requests.get("{}?{}".format(self.get_absolute_url("/test/session/get",servername),urlencode({"session":session_cookie})),headers=self.cluster_headers,verify=settings.SSL_VERIFY) if exist: self.assertNotEqual(res.status_code,404,"The session({1}) doesn't exist in auth2 server '{0}'".format(servername,session_cookie)) + res.raise_for_status() + data = res.json() + return (data["session"],data["ttl"]) else: + self.assertEqual(res.status_code,404,"The session({1}) doesn't exist in auth2 server '{0}'".format(servername,session_cookie)) return None - res.raise_for_status() - return res.json() def save_traffic_data(self,session_cookie,servername="standalone"): """ diff --git a/authome/urls/clusterurls.py b/authome/urls/clusterurls.py index 75b3d14..181351e 100644 --- a/authome/urls/clusterurls.py +++ b/authome/urls/clusterurls.py @@ -1,4 +1,5 @@ from django.urls import path +from django.conf import settings from django.views.decorators.csrf import csrf_exempt from .. import views @@ -17,3 +18,7 @@ path('healthcheck',views.healthcheckfactory("remote"),name="cluster_healthcheck") ] +if settings.AUTH2_MONITORING_DIR: + urlpatterns.append(path('auth2status/', views.auth2_status,name="auth2_status")) + urlpatterns.append(path('liveness///.html', views.auth2_liveness,name="auth2_liveness")) + diff --git a/authome/urls/urls.py b/authome/urls/urls.py index 06224a8..3a1ca16 100644 --- a/authome/urls/urls.py +++ b/authome/urls/urls.py @@ -13,6 +13,15 @@ logger = logging.getLogger(__name__) admin_urls = admin_site.urls + +if settings.AUTH2_MONITORING_DIR: + if settings.AUTH2_CLUSTER_ENABLED: + admin_urls[0].insert(0,path('auth2status/', views.auth2_status,name="auth2_status")) + admin_urls[0].insert(1,path('liveness///.html', views.auth2_liveness,name="auth2_liveness")) + else: + admin_urls[0].insert(0,path('auth2status', views.auth2_status,name="auth2_status")) + admin_urls[0].insert(1,path('liveness//.html', views.auth2_liveness,name="auth2_liveness")) + admin_urls[0].insert(0,path('authome/tools/apple/secretkey/renew', views.renew_apple_secretkey,name="renew_apple_secretkey")) urlpatterns = [ diff --git a/authome/views/__init__.py b/authome/views/__init__.py index d001a93..cc4a45d 100644 --- a/authome/views/__init__.py +++ b/authome/views/__init__.py @@ -13,3 +13,6 @@ if settings.TESTMODE: from .testviews import * +if settings.AUTH2_MONITORING_DIR: + from .healthcheckviews import * + diff --git a/authome/views/healthcheckviews.py b/authome/views/healthcheckviews.py new file mode 100644 index 0000000..fa25bb9 --- /dev/null +++ b/authome/views/healthcheckviews.py @@ -0,0 +1,65 @@ +import os + +from django.conf import settings +from django.http import HttpResponse,FileResponse +from django.shortcuts import render +from django.template.response import TemplateResponse +from django.utils.html import mark_safe + +from ..cache import cache +from ..response import MultiFileSegmentsResponse + +def _auth2_cluster_status(request,clusterid): + if clusterid == "standalone" or clusterid == settings.AUTH2_CLUSTERID: + p = os.path.join(settings.AUTH2_MONITORING_DIR,"auth2",clusterid) + servers = [] + if os.path.exists(p): + for server in os.listdir(p): + serverpath = os.path.join(p,server) + if os.path.isdir(serverpath): + filepath = os.path.join(serverpath,"serverinfo.html") + readyfilepath = os.path.join(serverpath,"latestreadytime") + if os.path.exists(filepath): + with open(filepath,'r') as f: + if os.path.exists(readyfilepath): + try: + with open(readyfilepath,'r') as f2: + servers.append((f.read(),int(f2.read().strip()))) + except: + servers.append((f.read(),0)) + else: + servers.append((f.read(),0)) + + if servers: + servers.sort(key=lambda d:d[1],reverse=True) + data = "\n".join([s[0] for s in servers]) + context = {"data":mark_safe(data),"clusterid":clusterid} + if clusterid == "standalone": + context["title"] = "Auth2 Server Status" + else: + context["title"] = "Auth2 Cluster Server({}) Status".format(clusterid) + return TemplateResponse(request,"authome/auth2serverstatus.html",context=context) + else: + return TemplateResponse(request,"authome/healthcheck_not_enabled.html",context={"message":"The healthcheck is not enabled for auth2 cluster({})".format(clusterid)}) + else: + return HttpResponse(cache.get_auth2_status(clusterid),content_type="text/html") + + +def _auth2_status(request): + return _auth2_cluster_status(request,"standalone") + +def _auth2_cluster_liveness(request,clusterid,serviceid,monitordate): + if clusterid == "standalone" or clusterid == settings.AUTH2_CLUSTERID: + f = os.path.join(settings.AUTH2_MONITORING_DIR,"auth2",clusterid,serviceid,"liveness","{}.html".format(monitordate)) + if os.path.exists(f): + return MultiFileSegmentsResponse([f,os.path.join(settings.AUTH2_MONITORING_DIR,"auth2",clusterid,serviceid,"livenessfooter.html")],filename="{}-{}-{}.html".format(clusterid,serviceid,monitordate)) + else: + return TemplateResponse(request,"authome/livenessfile_missing.html",context={"message":"The liveness file({}) does not exist".format(f)}) + else: + return HttpResponse(cache.get_auth2_liveness(clusterid,serviceid,monitordate),content_type="text/html") + +def _auth2_liveness(request,serviceid,monitordate): + return _auth2_cluster_liveness(request,"standalone",serviceid,monitordate) + +auth2_status = _auth2_cluster_status if settings.AUTH2_CLUSTER_ENABLED else _auth2_status +auth2_liveness = _auth2_cluster_liveness if settings.AUTH2_CLUSTER_ENABLED else _auth2_liveness diff --git a/authome/views/monitorviews.py b/authome/views/monitorviews.py index 7954c83..bca9d11 100644 --- a/authome/views/monitorviews.py +++ b/authome/views/monitorviews.py @@ -16,6 +16,7 @@ from .. import models from ..cache import cache,get_defaultcache from .. import utils +from ..serializers import JSONFormater defaultcache = get_defaultcache() @@ -288,17 +289,17 @@ def _localhealthcheck(request): working,status = _check_localhealth() content = {"working":working} - if errors: + if status: content["status"] = status - return JsonResponse(content,status=200) + return HttpResponse(json.dumps(content,cls=JSONFormater),status=200,content_type="application/json") def _remotehealthcheck(request): - working,errors = _check_localhealth() + working,status = _check_localhealth() content = {"working":working} - if errors: + if status: content["status"] = status - return JsonResponse(content,status=200) + return HttpResponse(json.dumps(content,cls=JSONFormater),status=200,content_type="application/json") def _check_clusterhealth(): working = False @@ -322,7 +323,7 @@ def _check_clusterhealth(): def _clusterhealthcheck(request): content = _check_clusterhealth() - return JsonResponse(content,status=200) + return HttpResponse(json.dumps(content,cls=JSONFormater),status=200,content_type="application/json") def ping(request): #used by health checker of the kuberneter to monitor the status of auth2 server @@ -333,8 +334,8 @@ def ping(request): if working: code = 200 #in working status, update heartbeat - if settings.AUTH2_CLUSTER_ENABLED: - #in cluster mode, update the heartbeat + if not settings.AUTH2_MONITORING_DIR and settings.AUTH2_CLUSTER_ENABLED: + #in cluster mode, update the heartbeat if auth2 monitoring feature is not enabled cache._current_auth2_cluster.register(only_update_heartbeat=True) for status in pingstatus.values(): if any(not s["ping"] for s in status.values() ): @@ -347,7 +348,7 @@ def ping(request): if pingstatus: content["pingstatus"] = pingstatus - return HttpResponse(json.dumps(content,indent=4),status=code,content_type="application/json") + return HttpResponse(json.dumps(content,indent=4,cls=JSONFormater),status=code,content_type="application/json") def healthcheckfactory(t=None): if t: diff --git a/authome/views/selfservice.py b/authome/views/selfservice.py index 872aeee..c54b5bf 100644 --- a/authome/views/selfservice.py +++ b/authome/views/selfservice.py @@ -13,13 +13,16 @@ from .. import models from ..cache import cache from .. import utils -from .views import get_absolute_url, _populate_response,_get_userflow_pagelayout,_get_next_url,MFA_METHOD_MAPPING +from .views import get_absolute_url, _populate_response,_get_userflow_pagelayout,_get_next_url,MFA_METHOD_MAPPING, auth_required_response_factory logger = logging.getLogger(__name__) def user_setting(request): #get the auth response user = request.user + if not request.user.is_authenticated or not request.user.is_active: + return auth_required_response_factory(request) + auth_key = request.session.session_key back_url = request.GET.get("back") or request.POST.get("back") logout_url = request.GET.get("logout") or request.POST.get("logout") diff --git a/authome/views/testviews.py b/authome/views/testviews.py index d2f010f..cc2ef74 100644 --- a/authome/views/testviews.py +++ b/authome/views/testviews.py @@ -85,9 +85,11 @@ def login_user(request): request.session["idp"] = idp.idp login(request,user,'django.contrib.auth.backends.ModelBackend') - request.session["idp"] = idp.idp - request.session["session_timeout"] = 3600 + usergroups = models.UserGroup.find_groups(email)[0] + timeout = models.UserGroup.get_session_timeout(usergroups) + if timeout: + request.session["session_timeout"] = timeout return views.profile(request) @@ -198,7 +200,8 @@ def get_session(request): cachekey = sessionstore.cache_key session_data = sessioncache.get(cachekey) if session_data: - return JsonResponse(session_data,status=200) + ttl = sessionstore.ttl + return JsonResponse({"session":session_data,"ttl":ttl},status=200) else: return views.response_not_found_factory(request) except Exception as ex: diff --git a/healthcheck.sh b/healthcheck.sh index 408c827..979ec90 100755 --- a/healthcheck.sh +++ b/healthcheck.sh @@ -1,85 +1,107 @@ #!/bin/bash -pingTimeout=1 -monitorInterval=60 -maxMonitorServers=3 +AUTH2_DOMAIN=auth2-dev.dbca.gov.au +PING_TIMEOUT=1 +MONITOR_INTERVAL=60 AUTH2_MONITORING_DIR=~/projects/authome/monitoring -AUTH2_CLUSTERID=auth2-01 +AUTH2_CLUSTERID="AUTH2_01" PORT=8070 +EXPIREDAYS=2 +SERVICEID=${HOSTNAME} +SERVICEID="AUTH02_003" + nowseconds=$(date '+%s') now=$(date '+%Y-%m-%d %H:%M:%S') today=$(date '+%Y-%m-%d') + if [[ "${AUTH2_CLUSTERID}" == "" ]]; then - mkdir -p "${AUTH2_MONITORING_DIR}/auth2/${HOSTNAME}" - serverinfofile="${AUTH2_MONITORING_DIR}/auth2/serverinfo.html" - monitoringfile="${AUTH2_MONITORING_DIR}/auth2/${HOSTNAME}/$(date '+%Y-%m-%d').html" + monitoringhome="${AUTH2_MONITORING_DIR}/auth2/standalone" + mkdir -p "${AUTH2_MONITORING_DIR}/auth2/standalone/${SERVICEID}/liveness" + serverinfofile="${AUTH2_MONITORING_DIR}/auth2/standalone/${SERVICEID}/serverinfo.html" + livenessfooterfile="${AUTH2_MONITORING_DIR}/auth2/standalone/${SERVICEID}/livenessfooter.html" + latestreadytimefile="${AUTH2_MONITORING_DIR}/auth2/standalone/${SERVICEID}/latestreadytime" + livenessfile="${AUTH2_MONITORING_DIR}/auth2/standalone/${SERVICEID}/liveness/${today}.html" else - mkdir -p "${AUTH2_MONITORING_DIR}/auth2/${AUTH2_CLUSTERID}/${HOSTNAME}" - serverinfofile="${AUTH2_MONITORING_DIR}/auth2/${AUTH2_CLUSTERID}/serverinfo.html" - monitoringfile="${AUTH2_MONITORING_DIR}/auth2/${AUTH2_CLUSTERID}/${HOSTNAME}/${today}.html" -fi -if [[ ! -f "${serverinfofile}" ]]; then - cp ~/projects/auth2_chart/static/auth2serverinfo.html "${serverinfofile}" - if [[ "${AUTH2_CLUSTERID}" == "" ]]; then - sed -i "0,//s//Auth2 Server Monitoring Data<\/title>/" ${serverinfofile} - sed -i "0,/<span id=\"clusterhead\">[^<]*<\/span>s//<span id=\"clusterhead\">Auth2 Server Monitoring data<\/span>/" ${serverinfofile} - else - sed -i "0,/<title[^<]*<\/title>/s//<title>The auth2 cluster '${AUTH2_CLUSTERID}' Monitoring Data<\/title>/" ${serverinfofile} - sed -i "0,/<span id=\"clusterhead\">[^<]*<\/span>/s//<span id=\"clusterhead\">Auth2 Cluster(${AUTH2_CLUSTERID}) Monitoring Data<\/span>/" ${serverinfofile} - fi + monitoringhome="${AUTH2_MONITORING_DIR}/auth2/${AUTH2_CLUSTERID}" + mkdir -p "${AUTH2_MONITORING_DIR}/auth2/${AUTH2_CLUSTERID}/${SERVICEID}/liveness" + serverinfofile="${AUTH2_MONITORING_DIR}/auth2/${AUTH2_CLUSTERID}/${SERVICEID}/serverinfo.html" + livenessfooterfile="${AUTH2_MONITORING_DIR}/auth2/${AUTH2_CLUSTERID}/${SERVICEID}/livenessfooter.html" + latestreadytimefile="${AUTH2_MONITORING_DIR}/auth2/${AUTH2_CLUSTERID}/${SERVICEID}/latestreadytime" + livenessfile="${AUTH2_MONITORING_DIR}/auth2/${AUTH2_CLUSTERID}/${SERVICEID}/liveness/${today}.html" fi -#check whether hostname exists or not -count=$(grep "<tr><td>${HOSTNAME}</td>" ${serverinfofile} | wc -l) -if [[ $count -eq 0 ]]; then - #Add that auth2 server into serverinfo file - sed -i "0,/<tbody>/s//<tbody>\n<tr><td>${HOSTNAME}<\/td><td id='${HOSTNAME}readytime'><\!--readytime--><\/td><td id='${HOSTNAME}heartbeat'>${now}<\!--heartbeat--><\/td><td id='${HOSTNAME}processingtime'><\!--processingtime--><\/td><td id='${HOSTNAME}status'><\!--status--><\/td><td><div id='${HOSTNAME}resusage'><\/div><\!--resusage--><\/td><td><ol id='${HOSTNAME}monitoring'><\/ol><\!--monitoring--><\/td><\/tr>/" ${serverinfofile} - #Manage the number of servers in the serverinfo file - count=$(grep "<tr.*</tr>" ${serverinfofile} | wc -l) - if [[ $count -gt ${maxMonitorServers} ]]; then - #delete outdated servers - rows=$(($count - ${maxMonitorServers})) - lastrow=$(awk '/<\/tbody>/{ print NR; exit }' ${serverinfofile}) - firstrow=$((${lastrow} - ${rows})) - lastrow=$((${lastrow} - 1)) - sed -i -e "${firstrow},${lastrow}d" ${serverinfofile} - fi +serverinfochanges="" + +if [[ ! -f "${serverinfofile}" ]]; then + echo -e "<tr>\n<td id='${SERVICEID}'>${SERVICEID}</td>\n<td id='${SERVICEID}readytime'></td>\n<td id='${SERVICEID}heartbeat'>${now}</td>\n<td id='${SERVICEID}processingtime'></td>\n<td id='${SERVICEID}status'>\n<!--start_status-->\n<!--end_status-->\n</td>\n<td id='${SERVICEID}resusage'>\n</td>\n<td>\n<ul id='${SERVICEID}monitoring'>\n</ul>\n</td>\n</tr>" > "${serverinfofile}" + cp /home/rockyc/projects/auth2_chart/static/auth2livenessfooter.html ${livenessfooterfile} else - sed -i "0,/<td id='${HOSTNAME}heartbeat'>.*<\!--heartbeat-->/s//<td id='${HOSTNAME}heartbeat'>${now}<\!--heartbeat-->/" ${serverinfofile} + serverinfochanges="-e \"s/<td id='${SERVICEID}heartbeat'>.*/<td id='${SERVICEID}heartbeat'>${now}<\/td>/\" " fi - -if [[ ! -f "${monitoringfile}" ]]; then - cp ~/projects/auth2_chart/static/auth2monitoring.html "${monitoringfile}" +if [[ ! -f "${livenessfile}" ]]; then + cp /home/rockyc/projects/auth2_chart/static/auth2liveness.html "${livenessfile}" + chmod 775 "${livenessfile}" + newlivenessfile=1 if [[ "${AUTH2_CLUSTERID}" == "" ]]; then - sed -i "0,/<title[^<]*<\/title>/s//<title>The monitoring data for auth2 server($HOSTNAME)<\/title>/" ${monitoringfile} + serverinfochanges="${serverinfochanges} -e \"s/<ul id='${SERVICEID}monitoring'>/<ul id='${SERVICEID}monitoring'>\n<li><a href='\/admin\/liveness\/${SERVICEID}\/${today}.html'>${today}<\/a><\/li>/\" " + sed -i -e "0,/<title[^<]*<\/title>/s//<title>Auth2 server($SERVICEID) Liveness Data<\/title>/" -e "0,/<span id=\"breadcrumb1\">[^<]*<\/span>/s//<span id=\"breadcrumb1\"><a href=\"\/admin\/auth2status\">Auth2 Server Status<\/a><\/span>/" -e "0,/<span id=\"breadcrumb2\">[^<]*<\/span>/s//<span id=\"breadcrumb2\">${today}<\/span>/" -e "0,/<span id=\"breadcrumb2\">[^<]*<\/span>/s//<span id=\"breadcrumb2a\">${SERVICEID}<\/span> \› <span id=\"breadcrumb2b\">${today}<\/span>/" ${livenessfile} else - sed -i "0,/<title[^<]*<\/title>/s//<title>The monitoring data for auth2 server(${AUTH2_CLUSTERID}:$HOSTNAME)<\/title>/" ${monitoringfile} + serverinfochanges="${serverinfochanges} -e \"s/<ul id='${SERVICEID}monitoring'>/<ul id='${SERVICEID}monitoring'>\n<li><a href='\/admin\/liveness\/${AUTH2_CLUSTERID}\/${SERVICEID}\/${today}.html'>${today}<\/a><\/li>/\"" + sed -i -e "0,/<title[^<]*<\/title>/s//<title>Auth2 cluster(${AUTH2_CLUSTERID}:$SERVICEID) Liveness Data<\/title>/" -e "0,/<span id=\"breadcrumb1\">[^<]*<\/span>/s//<span id=\"breadcrumb1a\">Auth2 Cluster Status<\/span> \› <span id=\"breadcrumb1b\"><a href=\"\/admin\/auth2status\/${AUTH2_CLUSTERID}\">${AUTH2_CLUSTERID}<\/a><\/span>/" -e "0,/<span id=\"breadcrumb2\">[^<]*<\/span>/s//<span id=\"breadcrumb2a\">${SERVICEID}<\/span> \› <span id=\"breadcrumb2b\">${today}<\/span>/" ${livenessfile} fi - sed -i "0,/<ol id='${HOSTNAME}monitoring'>/s//<ol id='${HOSTNAME}monitoring'><li><a href='\/admin\/monitor\/${today}.html'>${today}<\/a><\/li>/" ${serverinfofile} + #manage the monitoring files + earliest_date=$(date --date="${today}-${EXPIREDAYS} days") + earliest_seconds=$(date --date="${earliest_date}" "+%s") + for serviceid in $(ls "${monitoringhome}" ); do + if [[ -d "${monitoringhome}/${serviceid}/liveness" ]]; then + serverexpired=1 + for d in $(ls "${monitoringhome}/${serviceid}/liveness" ); do + logdate=${d%.html*} + logdate=$(date --date="${logdate}") + logdate_seconds=$(date --date="${logdate}" "+%s") + logdate=$(date --date="${logdate}" "+%Y-%m-%d") + if [[ ${logdate_seconds} -lt ${earliest_seconds} ]]; then + #remove the expired monitoring file from serverinfofile + #echo "The monitoring file(${d}.html) of auth2 server(${serviceid}) is expired" + sed -i "/${logdate}<\/a><\/li>/d" "${monitoringhome}/${serviceid}/serverinfo.html" + if [[ $? -eq 0 ]]; then + rm -f "${monitoringhome}/${serviceid}/liveness/${d}" + fi + else + serverexpired=0 + break + fi + done + if [[ ${serverexpired} -eq 1 ]]; then + #all monitoring files of this service are expired + #remove service from serverinfofile + #echo "The auth2 server(${serviceid}) is expired" + rm -rf "${monitoringhome}/${serviceid}" + fi + fi + done; +else + newlivenessfile=0 fi - starttime=$(date '+%s.%N') -wget --tries=1 --timeout=${pingTimeout} http://127.0.0.1:${PORT}/ping -o /dev/null -O /tmp/auth2_ping.json +wget --tries=1 --header="Host:${AUTH2_DOMAIN}" --timeout=${PING_TIMEOUT} http://127.0.0.1:${PORT}/ping -o /dev/null -O /tmp/auth2_ping.json status=$? -echo "status=${status}" endtime=$(date '+%s.%N') pingtime=$(perl -e "print (${endtime} - ${starttime}) * 1000") -sed -i "0,/<td id='${HOSTNAME}processingtime'>.*<\!--processingtime-->/s//<td id='${HOSTNAME}processingtime'>$(printf %.2f ${pingtime}) ms<\!--processingtime-->/" ${serverinfofile} +serverinfochanges="${serverinfochanges} -e \"s/<td id='${SERVICEID}processingtime'>.*/<td id='${SERVICEID}processingtime'>$(printf %.2f ${pingtime}) ms<\/td>/\" " #set auth2 starttime if [[ $status -eq 0 ]]; then #auth2 is ready to use - pingstatus=$(cat '/tmp/auth2_ping.json') - pingstatus="${pingstatus//$'\n'/<br>}" - pingstatus="${pingstatus//$'/'/\\/}" - pingstatus=$(echo "${pingstatus}" | tr '"' "'" | tr '(' '<' | tr ')' '>' ) - pingstatus="<pre>${pingstatus}<\/pre>" - - #sed -i "0,/<td id='${HOSTNAME}status'>.*<\!--status-->/s//<td id='${HOSTNAME}status'>$(cat /tmp/auth2_ping.json)<\!--status-->/" ${serverinfofile} - sed -i "0,/<td id='${HOSTNAME}status'>.*<\!--status-->/s//<td id='${HOSTNAME}status'>${pingstatus}<\!--status-->/" ${serverinfofile} + echo "${nowseconds}" > ${latestreadytimefile} + message="<a href='javascript:void(0)' onclick='showdetailusage(\\\"${SERVICEID}\\\")' id='${SERVICEID}detaillink'>+<\/a>Succeed" + message="${message} <div style='display:none' id='${SERVICEID}detail' class='detail'><pre>" + + serverinfochanges="${serverinfochanges} -e \"/<!--start_status-->/,/<!--end_status-->/d\" -e \"/<td id='${SERVICEID}status'>/a <!--start_status-->\n${message}\" -e \"/<td id='${SERVICEID}status'>/r /tmp/auth2_ping.json\" -e \"/<td id='${SERVICEID}status'>/a </pre><\/div>\n<!--end_status-->\"" else - sed -i "0,/<td id='${HOSTNAME}status'>.*<\!--status-->/s//<td id='${HOSTNAME}status'>Failed<\!--status-->/" ${serverinfofile} + serverinfochanges="${serverinfochanges} -e \"/<!--start_status-->/,/<!--end_status-->/d\" -e \"s/<td id='${SERVICEID}status'>.*/<td id='${SERVICEID}status'>\n<!--start_status-->\nFailed\n<!--end_status-->/\" " fi + declare -a auth2pids; declare -a cpuusages; declare -a vsusages; @@ -100,49 +122,54 @@ done totalvsusage=($(perl -e "print ${totalvsusage} / 1024")) totalrsusage=($(perl -e "print ${totalrsusage} / 1024")) -resourceusage="<div class='summary' id='${HOSTNAME}'>Total CPU : $(printf %.1f ${totalcpuusage}) , Virutal Memory : $(printf %.2fM ${totalvsusage}) , Memory : $(printf %.2fM ${totalrsusage})<\/div><div id='${HOSTNAME}detail' class='detail'><ul>" - auth2processes=${#auth2pids[@]}; + +resourceusage="<div class='summary' id='${SERVICEID}'>Processes : ${auth2processes} , Total CPU : $(printf %6.2f%% ${totalcpuusage}) , Virutal Memory : $(printf %8.2fM ${totalvsusage}) , Memory : $(printf %8.2fM ${totalrsusage})<\/div><div id='${SERVICEID}detail' class='detail'><ul>" + for (( i=0; i<${auth2processes}; i++ )); do - resourceusage="${resourceusage}<li>CPU : $(printf %.2f ${cpuusages[i]}) , Virtual Memory : $(printf %.2fM ${vsusages[i]}) , Memory : $(printf %.2fM ${rsusages[i]}) <\/li>" + resourceusage="${resourceusage}<li>CPU : $(printf %6.2f%% ${cpuusages[i]}) , Virtual Memory : $(printf %8.2fM ${vsusages[i]}) , Memory : $(printf %8.2fM ${rsusages[i]}) <\/li>" done resourceusage="${resourceusage}<\/ul><\/div>" -sed -i "0,/<td><div id='${HOSTNAME}resusage'>.*<\!--resusage-->/s//<td><div id='${HOSTNAME}resusage'>${resourceusage}<\!--resusage-->/" ${serverinfofile} - -if [[ -f "/tmp/nextmonitortime" ]]; then +serverinfochanges="${serverinfochanges} -e \"s/<td id='${SERVICEID}resusage'>.*/<td id='${SERVICEID}resusage'>${resourceusage}/\" " +if [[ ${newlivenessfile} -eq 1 ]]; then + nexttime=0 +elif [[ -f "/tmp/nextmonitortime" ]]; then nexttime=$(cat /tmp/nextmonitortime) else nexttime=0 fi -#if [[ $(date '+%s') -ge ${nexttime} ]] ; then -if [[ $(date '+%s') -ge 0 ]] ; then - message="<a href='javascript:void(0)' onclick='showdetailusage(\"${HOSTNAME}${nowseconds}\")' id='${HOSTNAME}${nowseconds}detaillink'>+<\/a> ${now} : " - message="${message}<span class='summary' id='${HOSTNAME}${nowseconds}'>Total CPU : $(printf %.1f ${totalcpuusage}) , Virutal Memory : $(printf %.2fM ${totalvsusage}) , Memory : $(printf %.2fM ${totalrsusage})<\/span>" +nexttime=0 +if [[ $(date '+%s') -ge ${nexttime} ]] ; then + message="<a href='javascript:void(0)' onclick='showdetailusage(\"${SERVICEID}${nowseconds}\")' id='${SERVICEID}${nowseconds}detaillink'>+</a>" + message="${message}<span class='summary' id='${SERVICEID}${nowseconds}'> ${now} : Processes : ${auth2processes} , Total CPU : $(printf %6.2f%% ${totalcpuusage}) , Virutal Memory : $(printf %8.2fM ${totalvsusage}) , Memory : $(printf %8.2fM ${totalrsusage})" if [[ $status -eq 0 ]]; then - message="${message} : Ping time=$(printf %.2f ${pingtime}) ms , Ping Result = Succeed " + message="${message} : Ping time=$(printf %7.3f ${pingtime}) ms , Ping Result = Succeed " else - message="${message} : Ping time=$(printf %.2f ${pingtime}) ms , Ping Result = Failed " + message="${message} : Ping time=$(printf %7.3f ${pingtime}) ms , Ping Result = Failed " fi - message="${message} : <div style='display:none' id='${HOSTNAME}${nowseconds}detail' class='detail'><ul>" + message="${message}</span> <div style='display:none' id='${SERVICEID}${nowseconds}detail' class='detail'><ul>" for (( i=0; i<${auth2processes}; i++ )); do - message="${message}<li>CPU : $(printf %.2f ${cpuusages[i]}) , Virtual Memory : $(printf %.2fM ${vsusages[i]}) , Memory : $(printf %.2fM ${rsusages[i]}) <\/li>" + message="${message}<li>CPU : $(printf %6.2f%% ${cpuusages[i]}) , Virtual Memory : $(printf %8.2fM ${vsusages[i]}) , Memory : $(printf %8.2fM ${rsusages[i]}) </li>" done if [[ $status -eq 0 ]]; then - message="${message}<div>${pingstatus}<\/div><\/ul><\/div>" + message="${message}</ul><div><pre>$(cat /tmp/auth2_ping.json)</pre></div></div>" else - message="${message}<\/ul><\/div>" + message="${message}</ul></div>" fi - sed -i "0,/<ol>/s//<ol>\n<li>${message}<\/li>/" ${monitoringfile} - nextmonitortime=$(date -d "+${monitorInterval} seconds" "+%s") + echo "<li>${message}</li>" >> ${livenessfile} + nextmonitortime=$(date -d "$(date -d "${now}")+${MONITOR_INTERVAL} seconds" "+%s") echo "${nextmonitortime}" > /tmp/nextmonitortime fi -if [[ $status -eq 0 ]]; then +if [[ "${serverinfochanges}" != "" ]]; then + eval "sed -i ${serverinfochanges} ${serverinfofile}" +fi +if [[ ${status} -eq 0 ]]; then #auth2 is ready to use - sed -i "0,/<td id='${HOSTNAME}readytime'>.*<\!--readytime-->/s//<td id='${HOSTNAME}readytime'>${now}<\!--readytime-->/" ${serverinfofile} + sed -i "s/<td id='${SERVICEID}readytime'>.*/<td id='${SERVICEID}readytime'>${now}<\/td>/" ${serverinfofile} fi -exit $status +exit ${status} diff --git a/poetry.lock b/poetry.lock index dce478c..0ad7331 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,15 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. - -[[package]] -name = "appnope" -version = "0.1.4" -description = "Disable App Nap on macOS >= 10.9" -optional = false -python-versions = ">=3.6" -files = [ - {file = "appnope-0.1.4-py2.py3-none-any.whl", hash = "sha256:502575ee11cd7a28c0205f379b525beefebab9d161b7c964670864014ed7213c"}, - {file = "appnope-0.1.4.tar.gz", hash = "sha256:1de3860566df9caf38f01f86f65e0e13e379af54f9e4bee1e66b48f2efffd1ee"}, -] +# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. [[package]] name = "asgiref" @@ -26,101 +15,108 @@ files = [ tests = ["mypy (>=0.800)", "pytest", "pytest-asyncio"] [[package]] -name = "backcall" -version = "0.2.0" -description = "Specifications for callback functions passed in to an API" +name = "asttokens" +version = "2.4.1" +description = "Annotate AST trees with source code positions" optional = false python-versions = "*" files = [ - {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, - {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, + {file = "asttokens-2.4.1-py2.py3-none-any.whl", hash = "sha256:051ed49c3dcae8913ea7cd08e46a606dba30b79993209636c4875bc1d637bc24"}, + {file = "asttokens-2.4.1.tar.gz", hash = "sha256:b03869718ba9a6eb027e134bfdf69f38a236d681c83c160d510768af11254ba0"}, ] +[package.dependencies] +six = ">=1.12.0" + +[package.extras] +astroid = ["astroid (>=1,<2)", "astroid (>=2,<4)"] +test = ["astroid (>=1,<2)", "astroid (>=2,<4)", "pytest"] + [[package]] name = "certifi" -version = "2024.7.4" +version = "2024.8.30" description = "Python package for providing Mozilla's CA Bundle." optional = false python-versions = ">=3.6" files = [ - {file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, - {file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, + {file = "certifi-2024.8.30-py3-none-any.whl", hash = "sha256:922820b53db7a7257ffbda3f597266d435245903d80737e34f8a45ff3e3230d8"}, + {file = "certifi-2024.8.30.tar.gz", hash = "sha256:bec941d2aa8195e248a60b31ff9f0558284cf01a52591ceda73ea9afffd69fd9"}, ] [[package]] name = "cffi" -version = "1.17.0" +version = "1.17.1" description = "Foreign Function Interface for Python calling C code." optional = false python-versions = ">=3.8" files = [ - {file = "cffi-1.17.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:f9338cc05451f1942d0d8203ec2c346c830f8e86469903d5126c1f0a13a2bcbb"}, - {file = "cffi-1.17.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a0ce71725cacc9ebf839630772b07eeec220cbb5f03be1399e0457a1464f8e1a"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c815270206f983309915a6844fe994b2fa47e5d05c4c4cef267c3b30e34dbe42"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6bdcd415ba87846fd317bee0774e412e8792832e7805938987e4ede1d13046d"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8a98748ed1a1df4ee1d6f927e151ed6c1a09d5ec21684de879c7ea6aa96f58f2"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0a048d4f6630113e54bb4b77e315e1ba32a5a31512c31a273807d0027a7e69ab"}, - {file = "cffi-1.17.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:24aa705a5f5bd3a8bcfa4d123f03413de5d86e497435693b638cbffb7d5d8a1b"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:856bf0924d24e7f93b8aee12a3a1095c34085600aa805693fb7f5d1962393206"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:4304d4416ff032ed50ad6bb87416d802e67139e31c0bde4628f36a47a3164bfa"}, - {file = "cffi-1.17.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:331ad15c39c9fe9186ceaf87203a9ecf5ae0ba2538c9e898e3a6967e8ad3db6f"}, - {file = "cffi-1.17.0-cp310-cp310-win32.whl", hash = "sha256:669b29a9eca6146465cc574659058ed949748f0809a2582d1f1a324eb91054dc"}, - {file = "cffi-1.17.0-cp310-cp310-win_amd64.whl", hash = "sha256:48b389b1fd5144603d61d752afd7167dfd205973a43151ae5045b35793232aa2"}, - {file = "cffi-1.17.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c5d97162c196ce54af6700949ddf9409e9833ef1003b4741c2b39ef46f1d9720"}, - {file = "cffi-1.17.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:5ba5c243f4004c750836f81606a9fcb7841f8874ad8f3bf204ff5e56332b72b9"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb9333f58fc3a2296fb1d54576138d4cf5d496a2cc118422bd77835e6ae0b9cb"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:435a22d00ec7d7ea533db494da8581b05977f9c37338c80bc86314bec2619424"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1df34588123fcc88c872f5acb6f74ae59e9d182a2707097f9e28275ec26a12d"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:df8bb0010fdd0a743b7542589223a2816bdde4d94bb5ad67884348fa2c1c67e8"}, - {file = "cffi-1.17.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a8b5b9712783415695663bd463990e2f00c6750562e6ad1d28e072a611c5f2a6"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ffef8fd58a36fb5f1196919638f73dd3ae0db1a878982b27a9a5a176ede4ba91"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4e67d26532bfd8b7f7c05d5a766d6f437b362c1bf203a3a5ce3593a645e870b8"}, - {file = "cffi-1.17.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:45f7cd36186db767d803b1473b3c659d57a23b5fa491ad83c6d40f2af58e4dbb"}, - {file = "cffi-1.17.0-cp311-cp311-win32.whl", hash = "sha256:a9015f5b8af1bb6837a3fcb0cdf3b874fe3385ff6274e8b7925d81ccaec3c5c9"}, - {file = "cffi-1.17.0-cp311-cp311-win_amd64.whl", hash = "sha256:b50aaac7d05c2c26dfd50c3321199f019ba76bb650e346a6ef3616306eed67b0"}, - {file = "cffi-1.17.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aec510255ce690d240f7cb23d7114f6b351c733a74c279a84def763660a2c3bc"}, - {file = "cffi-1.17.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2770bb0d5e3cc0e31e7318db06efcbcdb7b31bcb1a70086d3177692a02256f59"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:db9a30ec064129d605d0f1aedc93e00894b9334ec74ba9c6bdd08147434b33eb"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a47eef975d2b8b721775a0fa286f50eab535b9d56c70a6e62842134cf7841195"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f3e0992f23bbb0be00a921eae5363329253c3b86287db27092461c887b791e5e"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6107e445faf057c118d5050560695e46d272e5301feffda3c41849641222a828"}, - {file = "cffi-1.17.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb862356ee9391dc5a0b3cbc00f416b48c1b9a52d252d898e5b7696a5f9fe150"}, - {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c1c13185b90bbd3f8b5963cd8ce7ad4ff441924c31e23c975cb150e27c2bf67a"}, - {file = "cffi-1.17.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:17c6d6d3260c7f2d94f657e6872591fe8733872a86ed1345bda872cfc8c74885"}, - {file = "cffi-1.17.0-cp312-cp312-win32.whl", hash = "sha256:c3b8bd3133cd50f6b637bb4322822c94c5ce4bf0d724ed5ae70afce62187c492"}, - {file = "cffi-1.17.0-cp312-cp312-win_amd64.whl", hash = "sha256:dca802c8db0720ce1c49cce1149ff7b06e91ba15fa84b1d59144fef1a1bc7ac2"}, - {file = "cffi-1.17.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6ce01337d23884b21c03869d2f68c5523d43174d4fc405490eb0091057943118"}, - {file = "cffi-1.17.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:cab2eba3830bf4f6d91e2d6718e0e1c14a2f5ad1af68a89d24ace0c6b17cced7"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:14b9cbc8f7ac98a739558eb86fabc283d4d564dafed50216e7f7ee62d0d25377"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b00e7bcd71caa0282cbe3c90966f738e2db91e64092a877c3ff7f19a1628fdcb"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:41f4915e09218744d8bae14759f983e466ab69b178de38066f7579892ff2a555"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e4760a68cab57bfaa628938e9c2971137e05ce48e762a9cb53b76c9b569f1204"}, - {file = "cffi-1.17.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:011aff3524d578a9412c8b3cfaa50f2c0bd78e03eb7af7aa5e0df59b158efb2f"}, - {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:a003ac9edc22d99ae1286b0875c460351f4e101f8c9d9d2576e78d7e048f64e0"}, - {file = "cffi-1.17.0-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ef9528915df81b8f4c7612b19b8628214c65c9b7f74db2e34a646a0a2a0da2d4"}, - {file = "cffi-1.17.0-cp313-cp313-win32.whl", hash = "sha256:70d2aa9fb00cf52034feac4b913181a6e10356019b18ef89bc7c12a283bf5f5a"}, - {file = "cffi-1.17.0-cp313-cp313-win_amd64.whl", hash = "sha256:b7b6ea9e36d32582cda3465f54c4b454f62f23cb083ebc7a94e2ca6ef011c3a7"}, - {file = "cffi-1.17.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:964823b2fc77b55355999ade496c54dde161c621cb1f6eac61dc30ed1b63cd4c"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:516a405f174fd3b88829eabfe4bb296ac602d6a0f68e0d64d5ac9456194a5b7e"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dec6b307ce928e8e112a6bb9921a1cb00a0e14979bf28b98e084a4b8a742bd9b"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4094c7b464cf0a858e75cd14b03509e84789abf7b79f8537e6a72152109c76e"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2404f3de742f47cb62d023f0ba7c5a916c9c653d5b368cc966382ae4e57da401"}, - {file = "cffi-1.17.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3aa9d43b02a0c681f0bfbc12d476d47b2b2b6a3f9287f11ee42989a268a1833c"}, - {file = "cffi-1.17.0-cp38-cp38-win32.whl", hash = "sha256:0bb15e7acf8ab35ca8b24b90af52c8b391690ef5c4aec3d31f38f0d37d2cc499"}, - {file = "cffi-1.17.0-cp38-cp38-win_amd64.whl", hash = "sha256:93a7350f6706b31f457c1457d3a3259ff9071a66f312ae64dc024f049055f72c"}, - {file = "cffi-1.17.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1a2ddbac59dc3716bc79f27906c010406155031a1c801410f1bafff17ea304d2"}, - {file = "cffi-1.17.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:6327b572f5770293fc062a7ec04160e89741e8552bf1c358d1a23eba68166759"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dbc183e7bef690c9abe5ea67b7b60fdbca81aa8da43468287dae7b5c046107d4"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5bdc0f1f610d067c70aa3737ed06e2726fd9d6f7bfee4a351f4c40b6831f4e82"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6d872186c1617d143969defeadac5a904e6e374183e07977eedef9c07c8953bf"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0d46ee4764b88b91f16661a8befc6bfb24806d885e27436fdc292ed7e6f6d058"}, - {file = "cffi-1.17.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f76a90c345796c01d85e6332e81cab6d70de83b829cf1d9762d0a3da59c7932"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:0e60821d312f99d3e1569202518dddf10ae547e799d75aef3bca3a2d9e8ee693"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:eb09b82377233b902d4c3fbeeb7ad731cdab579c6c6fda1f763cd779139e47c3"}, - {file = "cffi-1.17.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:24658baf6224d8f280e827f0a50c46ad819ec8ba380a42448e24459daf809cf4"}, - {file = "cffi-1.17.0-cp39-cp39-win32.whl", hash = "sha256:0fdacad9e0d9fc23e519efd5ea24a70348305e8d7d85ecbb1a5fa66dc834e7fb"}, - {file = "cffi-1.17.0-cp39-cp39-win_amd64.whl", hash = "sha256:7cbc78dc018596315d4e7841c8c3a7ae31cc4d638c9b627f87d52e8abaaf2d29"}, - {file = "cffi-1.17.0.tar.gz", hash = "sha256:f3157624b7558b914cb039fd1af735e5e8049a87c817cc215109ad1c8779df76"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:df8b1c11f177bc2313ec4b2d46baec87a5f3e71fc8b45dab2ee7cae86d9aba14"}, + {file = "cffi-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8f2cdc858323644ab277e9bb925ad72ae0e67f69e804f4898c070998d50b1a67"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:edae79245293e15384b51f88b00613ba9f7198016a5948b5dddf4917d4d26382"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45398b671ac6d70e67da8e4224a065cec6a93541bb7aebe1b198a61b58c7b702"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ad9413ccdeda48c5afdae7e4fa2192157e991ff761e7ab8fdd8926f40b160cc3"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5da5719280082ac6bd9aa7becb3938dc9f9cbd57fac7d2871717b1feb0902ab6"}, + {file = "cffi-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2bb1a08b8008b281856e5971307cc386a8e9c5b625ac297e853d36da6efe9c17"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:045d61c734659cc045141be4bae381a41d89b741f795af1dd018bfb532fd0df8"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:6883e737d7d9e4899a8a695e00ec36bd4e5e4f18fabe0aca0efe0a4b44cdb13e"}, + {file = "cffi-1.17.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:6b8b4a92e1c65048ff98cfe1f735ef8f1ceb72e3d5f0c25fdb12087a23da22be"}, + {file = "cffi-1.17.1-cp310-cp310-win32.whl", hash = "sha256:c9c3d058ebabb74db66e431095118094d06abf53284d9c81f27300d0e0d8bc7c"}, + {file = "cffi-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:0f048dcf80db46f0098ccac01132761580d28e28bc0f78ae0d58048063317e15"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a45e3c6913c5b87b3ff120dcdc03f6131fa0065027d0ed7ee6190736a74cd401"}, + {file = "cffi-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:30c5e0cb5ae493c04c8b42916e52ca38079f1b235c2f8ae5f4527b963c401caf"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f75c7ab1f9e4aca5414ed4d8e5c0e303a34f4421f8a0d47a4d019ceff0ab6af4"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1ed2dd2972641495a3ec98445e09766f077aee98a1c896dcb4ad0d303628e41"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:46bf43160c1a35f7ec506d254e5c890f3c03648a4dbac12d624e4490a7046cd1"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a24ed04c8ffd54b0729c07cee15a81d964e6fee0e3d4d342a27b020d22959dc6"}, + {file = "cffi-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:610faea79c43e44c71e1ec53a554553fa22321b65fae24889706c0a84d4ad86d"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a9b15d491f3ad5d692e11f6b71f7857e7835eb677955c00cc0aefcd0669adaf6"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:de2ea4b5833625383e464549fec1bc395c1bdeeb5f25c4a3a82b5a8c756ec22f"}, + {file = "cffi-1.17.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fc48c783f9c87e60831201f2cce7f3b2e4846bf4d8728eabe54d60700b318a0b"}, + {file = "cffi-1.17.1-cp311-cp311-win32.whl", hash = "sha256:85a950a4ac9c359340d5963966e3e0a94a676bd6245a4b55bc43949eee26a655"}, + {file = "cffi-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:caaf0640ef5f5517f49bc275eca1406b0ffa6aa184892812030f04c2abf589a0"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:805b4371bf7197c329fcb3ead37e710d1bca9da5d583f5073b799d5c5bd1eee4"}, + {file = "cffi-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:733e99bc2df47476e3848417c5a4540522f234dfd4ef3ab7fafdf555b082ec0c"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1257bdabf294dceb59f5e70c64a3e2f462c30c7ad68092d01bbbfb1c16b1ba36"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:da95af8214998d77a98cc14e3a3bd00aa191526343078b530ceb0bd710fb48a5"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d63afe322132c194cf832bfec0dc69a99fb9bb6bbd550f161a49e9e855cc78ff"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f79fc4fc25f1c8698ff97788206bb3c2598949bfe0fef03d299eb1b5356ada99"}, + {file = "cffi-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b62ce867176a75d03a665bad002af8e6d54644fad99a3c70905c543130e39d93"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:386c8bf53c502fff58903061338ce4f4950cbdcb23e2902d86c0f722b786bbe3"}, + {file = "cffi-1.17.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4ceb10419a9adf4460ea14cfd6bc43d08701f0835e979bf821052f1805850fe8"}, + {file = "cffi-1.17.1-cp312-cp312-win32.whl", hash = "sha256:a08d7e755f8ed21095a310a693525137cfe756ce62d066e53f502a83dc550f65"}, + {file = "cffi-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:51392eae71afec0d0c8fb1a53b204dbb3bcabcb3c9b807eedf3e1e6ccf2de903"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f3a2b4222ce6b60e2e8b337bb9596923045681d71e5a082783484d845390938e"}, + {file = "cffi-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0984a4925a435b1da406122d4d7968dd861c1385afe3b45ba82b750f229811e2"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d01b12eeeb4427d3110de311e1774046ad344f5b1a7403101878976ecd7a10f3"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:706510fe141c86a69c8ddc029c7910003a17353970cff3b904ff0686a5927683"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:de55b766c7aa2e2a3092c51e0483d700341182f08e67c63630d5b6f200bb28e5"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c59d6e989d07460165cc5ad3c61f9fd8f1b4796eacbd81cee78957842b834af4"}, + {file = "cffi-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd398dbc6773384a17fe0d3e7eeb8d1a21c2200473ee6806bb5e6a8e62bb73dd"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:3edc8d958eb099c634dace3c7e16560ae474aa3803a5df240542b305d14e14ed"}, + {file = "cffi-1.17.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:72e72408cad3d5419375fc87d289076ee319835bdfa2caad331e377589aebba9"}, + {file = "cffi-1.17.1-cp313-cp313-win32.whl", hash = "sha256:e03eab0a8677fa80d646b5ddece1cbeaf556c313dcfac435ba11f107ba117b5d"}, + {file = "cffi-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:f6a16c31041f09ead72d69f583767292f750d24913dadacf5756b966aacb3f1a"}, + {file = "cffi-1.17.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:636062ea65bd0195bc012fea9321aca499c0504409f413dc88af450b57ffd03b"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c7eac2ef9b63c79431bc4b25f1cd649d7f061a28808cbc6c47b534bd789ef964"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e221cf152cff04059d011ee126477f0d9588303eb57e88923578ace7baad17f9"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:31000ec67d4221a71bd3f67df918b1f88f676f1c3b535a7eb473255fdc0b83fc"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f17be4345073b0a7b8ea599688f692ac3ef23ce28e5df79c04de519dbc4912c"}, + {file = "cffi-1.17.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2b1fac190ae3ebfe37b979cc1ce69c81f4e4fe5746bb401dca63a9062cdaf1"}, + {file = "cffi-1.17.1-cp38-cp38-win32.whl", hash = "sha256:7596d6620d3fa590f677e9ee430df2958d2d6d6de2feeae5b20e82c00b76fbf8"}, + {file = "cffi-1.17.1-cp38-cp38-win_amd64.whl", hash = "sha256:78122be759c3f8a014ce010908ae03364d00a1f81ab5c7f4a7a5120607ea56e1"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b2ab587605f4ba0bf81dc0cb08a41bd1c0a5906bd59243d56bad7668a6fc6c16"}, + {file = "cffi-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:28b16024becceed8c6dfbc75629e27788d8a3f9030691a1dbf9821a128b22c36"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1d599671f396c4723d016dbddb72fe8e0397082b0a77a4fab8028923bec050e8"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ca74b8dbe6e8e8263c0ffd60277de77dcee6c837a3d0881d8c1ead7268c9e576"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f7f5baafcc48261359e14bcd6d9bff6d4b28d9103847c9e136694cb0501aef87"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:98e3969bcff97cae1b2def8ba499ea3d6f31ddfdb7635374834cf89a1a08ecf0"}, + {file = "cffi-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cdf5ce3acdfd1661132f2a9c19cac174758dc2352bfe37d98aa7512c6b7178b3"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:9755e4345d1ec879e3849e62222a18c7174d65a6a92d5b346b1863912168b595"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:f1e22e8c4419538cb197e4dd60acc919d7696e5ef98ee4da4e01d3f8cfa4cc5a"}, + {file = "cffi-1.17.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:c03e868a0b3bc35839ba98e74211ed2b05d2119be4e8a0f224fba9384f1fe02e"}, + {file = "cffi-1.17.1-cp39-cp39-win32.whl", hash = "sha256:e31ae45bc2e29f6b2abd0de1cc3b9d5205aa847cafaecb8af1476a609a2f6eb7"}, + {file = "cffi-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:d016c76bdd850f3c626af19b0542c9677ba156e4ee4fccfdd7848803533ef662"}, + {file = "cffi-1.17.1.tar.gz", hash = "sha256:1c39c6016c32bc48dd54561950ebd6836e1670f2ae46128f67cf49e789c52824"}, ] [package.dependencies] @@ -128,101 +124,116 @@ pycparser = "*" [[package]] name = "charset-normalizer" -version = "3.3.2" +version = "3.4.0" description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." optional = false python-versions = ">=3.7.0" files = [ - {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, - {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, - {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, - {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, - {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, - {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, - {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, - {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:4f9fc98dad6c2eaa32fc3af1417d95b5e3d08aff968df0cd320066def971f9a6"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0de7b687289d3c1b3e8660d0741874abe7888100efe14bd0f9fd7141bcbda92b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5ed2e36c3e9b4f21dd9422f6893dec0abf2cca553af509b10cd630f878d3eb99"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40d3ff7fc90b98c637bda91c89d51264a3dcf210cade3a2c6f838c7268d7a4ca"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1110e22af8ca26b90bd6364fe4c763329b0ebf1ee213ba32b68c73de5752323d"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:86f4e8cca779080f66ff4f191a685ced73d2f72d50216f7112185dc02b90b9b7"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f683ddc7eedd742e2889d2bfb96d69573fde1d92fcb811979cdb7165bb9c7d3"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:27623ba66c183eca01bf9ff833875b459cad267aeeb044477fedac35e19ba907"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f606a1881d2663630ea5b8ce2efe2111740df4b687bd78b34a8131baa007f79b"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:0b309d1747110feb25d7ed6b01afdec269c647d382c857ef4663bbe6ad95a912"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:136815f06a3ae311fae551c3df1f998a1ebd01ddd424aa5603a4336997629e95"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:14215b71a762336254351b00ec720a8e85cada43b987da5a042e4ce3e82bd68e"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:79983512b108e4a164b9c8d34de3992f76d48cadc9554c9e60b43f308988aabe"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win32.whl", hash = "sha256:c94057af19bc953643a33581844649a7fdab902624d2eb739738a30e2b3e60fc"}, + {file = "charset_normalizer-3.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:55f56e2ebd4e3bc50442fbc0888c9d8c94e4e06a933804e2af3e89e2f9c1c749"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:0d99dd8ff461990f12d6e42c7347fd9ab2532fb70e9621ba520f9e8637161d7c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c57516e58fd17d03ebe67e181a4e4e2ccab1168f8c2976c6a334d4f819fe5944"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:6dba5d19c4dfab08e58d5b36304b3f92f3bd5d42c1a3fa37b5ba5cdf6dfcbcee"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf4475b82be41b07cc5e5ff94810e6a01f276e37c2d55571e3fe175e467a1a1c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ce031db0408e487fd2775d745ce30a7cd2923667cf3b69d48d219f1d8f5ddeb6"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8ff4e7cdfdb1ab5698e675ca622e72d58a6fa2a8aa58195de0c0061288e6e3ea"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3710a9751938947e6327ea9f3ea6332a09bf0ba0c09cae9cb1f250bd1f1549bc"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82357d85de703176b5587dbe6ade8ff67f9f69a41c0733cf2425378b49954de5"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:47334db71978b23ebcf3c0f9f5ee98b8d65992b65c9c4f2d34c2eaf5bcaf0594"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:8ce7fd6767a1cc5a92a639b391891bf1c268b03ec7e021c7d6d902285259685c"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:f1a2f519ae173b5b6a2c9d5fa3116ce16e48b3462c8b96dfdded11055e3d6365"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:63bc5c4ae26e4bc6be6469943b8253c0fd4e4186c43ad46e713ea61a0ba49129"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bcb4f8ea87d03bc51ad04add8ceaf9b0f085ac045ab4d74e73bbc2dc033f0236"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win32.whl", hash = "sha256:9ae4ef0b3f6b41bad6366fb0ea4fc1d7ed051528e113a60fa2a65a9abb5b1d99"}, + {file = "charset_normalizer-3.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cee4373f4d3ad28f1ab6290684d8e2ebdb9e7a1b74fdc39e4c211995f77bec27"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:0713f3adb9d03d49d365b70b84775d0a0d18e4ab08d12bc46baa6132ba78aaf6"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:de7376c29d95d6719048c194a9cf1a1b0393fbe8488a22008610b0361d834ecf"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a51b48f42d9358460b78725283f04bddaf44a9358197b889657deba38f329db"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b295729485b06c1a0683af02a9e42d2caa9db04a373dc38a6a58cdd1e8abddf1"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ee803480535c44e7f5ad00788526da7d85525cfefaf8acf8ab9a310000be4b03"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3d59d125ffbd6d552765510e3f31ed75ebac2c7470c7274195b9161a32350284"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8cda06946eac330cbe6598f77bb54e690b4ca93f593dee1568ad22b04f347c15"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:07afec21bbbbf8a5cc3651aa96b980afe2526e7f048fdfb7f1014d84acc8b6d8"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6b40e8d38afe634559e398cc32b1472f376a4099c75fe6299ae607e404c033b2"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:b8dcd239c743aa2f9c22ce674a145e0a25cb1566c495928440a181ca1ccf6719"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:84450ba661fb96e9fd67629b93d2941c871ca86fc38d835d19d4225ff946a631"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:44aeb140295a2f0659e113b31cfe92c9061622cadbc9e2a2f7b8ef6b1e29ef4b"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1db4e7fefefd0f548d73e2e2e041f9df5c59e178b4c72fbac4cc6f535cfb1565"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win32.whl", hash = "sha256:5726cf76c982532c1863fb64d8c6dd0e4c90b6ece9feb06c9f202417a31f7dd7"}, + {file = "charset_normalizer-3.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:b197e7094f232959f8f20541ead1d9862ac5ebea1d58e9849c1bf979255dfac9"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:dd4eda173a9fcccb5f2e2bd2a9f423d180194b1bf17cf59e3269899235b2a114"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e9e3c4c9e1ed40ea53acf11e2a386383c3304212c965773704e4603d589343ed"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92a7e36b000bf022ef3dbb9c46bfe2d52c047d5e3f3343f43204263c5addc250"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54b6a92d009cbe2fb11054ba694bc9e284dad30a26757b1e372a1fdddaf21920"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ffd9493de4c922f2a38c2bf62b831dcec90ac673ed1ca182fe11b4d8e9f2a64"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:35c404d74c2926d0287fbd63ed5d27eb911eb9e4a3bb2c6d294f3cfd4a9e0c23"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4796efc4faf6b53a18e3d46343535caed491776a22af773f366534056c4e1fbc"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e7fdd52961feb4c96507aa649550ec2a0d527c086d284749b2f582f2d40a2e0d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:92db3c28b5b2a273346bebb24857fda45601aef6ae1c011c0a997106581e8a88"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ab973df98fc99ab39080bfb0eb3a925181454d7c3ac8a1e695fddfae696d9e90"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:4b67fdab07fdd3c10bb21edab3cbfe8cf5696f453afce75d815d9d7223fbe88b"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:aa41e526a5d4a9dfcfbab0716c7e8a1b215abd3f3df5a45cf18a12721d31cb5d"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:ffc519621dce0c767e96b9c53f09c5d215578e10b02c285809f76509a3931482"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win32.whl", hash = "sha256:f19c1585933c82098c2a520f8ec1227f20e339e33aca8fa6f956f6691b784e67"}, + {file = "charset_normalizer-3.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:707b82d19e65c9bd28b81dde95249b07bf9f5b90ebe1ef17d9b57473f8a64b7b"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dbe03226baf438ac4fda9e2d0715022fd579cb641c4cf639fa40d53b2fe6f3e2"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd9a8bd8900e65504a305bf8ae6fa9fbc66de94178c420791d0293702fce2df7"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8831399554b92b72af5932cdbbd4ddc55c55f631bb13ff8fe4e6536a06c5c51"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a14969b8691f7998e74663b77b4c36c0337cb1df552da83d5c9004a93afdb574"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcaf7c1524c0542ee2fc82cc8ec337f7a9f7edee2532421ab200d2b920fc97cf"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:425c5f215d0eecee9a56cdb703203dda90423247421bf0d67125add85d0c4455"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_aarch64.whl", hash = "sha256:d5b054862739d276e09928de37c79ddeec42a6e1bfc55863be96a36ba22926f6"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_i686.whl", hash = "sha256:f3e73a4255342d4eb26ef6df01e3962e73aa29baa3124a8e824c5d3364a65748"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_ppc64le.whl", hash = "sha256:2f6c34da58ea9c1a9515621f4d9ac379871a8f21168ba1b5e09d74250de5ad62"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_s390x.whl", hash = "sha256:f09cb5a7bbe1ecae6e87901a2eb23e0256bb524a79ccc53eb0b7629fbe7677c4"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-musllinux_1_2_x86_64.whl", hash = "sha256:0099d79bdfcf5c1f0c2c72f91516702ebf8b0b8ddd8905f97a8aecf49712c621"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win32.whl", hash = "sha256:9c98230f5042f4945f957d006edccc2af1e03ed5e37ce7c373f00a5a4daa6149"}, + {file = "charset_normalizer-3.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:62f60aebecfc7f4b82e3f639a7d1433a20ec32824db2199a11ad4f5e146ef5ee"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:af73657b7a68211996527dbfeffbb0864e043d270580c5aef06dc4b659a4b578"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:cab5d0b79d987c67f3b9e9c53f54a61360422a5a0bc075f43cab5621d530c3b6"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:9289fd5dddcf57bab41d044f1756550f9e7cf0c8e373b8cdf0ce8773dc4bd417"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6b493a043635eb376e50eedf7818f2f322eabbaa974e948bd8bdd29eb7ef2a51"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9fa2566ca27d67c86569e8c85297aaf413ffab85a8960500f12ea34ff98e4c41"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8e538f46104c815be19c975572d74afb53f29650ea2025bbfaef359d2de2f7f"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6fd30dc99682dc2c603c2b315bded2799019cea829f8bf57dc6b61efde6611c8"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2006769bd1640bdf4d5641c69a3d63b71b81445473cac5ded39740a226fa88ab"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:dc15e99b2d8a656f8e666854404f1ba54765871104e50c8e9813af8a7db07f12"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:ab2e5bef076f5a235c3774b4f4028a680432cded7cad37bba0fd90d64b187d19"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:4ec9dd88a5b71abfc74e9df5ebe7921c35cbb3b641181a531ca65cdb5e8e4dea"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:43193c5cda5d612f247172016c4bb71251c784d7a4d9314677186a838ad34858"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:aa693779a8b50cd97570e5a0f343538a8dbd3e496fa5dcb87e29406ad0299654"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win32.whl", hash = "sha256:7706f5850360ac01d80c89bcef1640683cc12ed87f42579dab6c5d3ed6888613"}, + {file = "charset_normalizer-3.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:c3e446d253bd88f6377260d07c895816ebf33ffffd56c1c792b13bff9c3e1ade"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:980b4f289d1d90ca5efcf07958d3eb38ed9c0b7676bf2831a54d4f66f9c27dfa"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f28f891ccd15c514a0981f3b9db9aa23d62fe1a99997512b0491d2ed323d229a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:a8aacce6e2e1edcb6ac625fb0f8c3a9570ccc7bfba1f63419b3769ccf6a00ed0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd7af3717683bea4c87acd8c0d3d5b44d56120b26fd3f8a692bdd2d5260c620a"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5ff2ed8194587faf56555927b3aa10e6fb69d931e33953943bc4f837dfee2242"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e91f541a85298cf35433bf66f3fab2a4a2cff05c127eeca4af174f6d497f0d4b"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:309a7de0a0ff3040acaebb35ec45d18db4b28232f21998851cfa709eeff49d62"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:285e96d9d53422efc0d7a17c60e59f37fbf3dfa942073f666db4ac71e8d726d0"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:5d447056e2ca60382d460a604b6302d8db69476fd2015c81e7c35417cfabe4cd"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:20587d20f557fe189b7947d8e7ec5afa110ccf72a3128d61a2a387c3313f46be"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:130272c698667a982a5d0e626851ceff662565379baf0ff2cc58067b81d4f11d"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:ab22fbd9765e6954bc0bcff24c25ff71dcbfdb185fcdaca49e81bac68fe724d3"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:7782afc9b6b42200f7362858f9e73b1f8316afb276d316336c0ec3bd73312742"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win32.whl", hash = "sha256:2de62e8801ddfff069cd5c504ce3bc9672b23266597d4e4f50eda28846c322f2"}, + {file = "charset_normalizer-3.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:95c3c157765b031331dd4db3c775e58deaee050a3042fcad72cbc4189d7c8dca"}, + {file = "charset_normalizer-3.4.0-py3-none-any.whl", hash = "sha256:fe9f97feb71aa9896b81973a7bbada8c49501dc73e58a10fcef6663af95e5079"}, + {file = "charset_normalizer-3.4.0.tar.gz", hash = "sha256:223217c3d4f82c3ac5e29032b3f1c2eb0fb591b72161f86d93f5719079dae93e"}, ] [[package]] @@ -321,38 +332,38 @@ yaml = ["PyYAML (>=3.10)"] [[package]] name = "cryptography" -version = "43.0.0" +version = "43.0.3" description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." optional = false python-versions = ">=3.7" files = [ - {file = "cryptography-43.0.0-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:64c3f16e2a4fc51c0d06af28441881f98c5d91009b8caaff40cf3548089e9c74"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3dcdedae5c7710b9f97ac6bba7e1052b95c7083c9d0e9df96e02a1932e777895"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3d9a1eca329405219b605fac09ecfc09ac09e595d6def650a437523fcd08dd22"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ea9e57f8ea880eeea38ab5abf9fbe39f923544d7884228ec67d666abd60f5a47"}, - {file = "cryptography-43.0.0-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:9a8d6802e0825767476f62aafed40532bd435e8a5f7d23bd8b4f5fd04cc80ecf"}, - {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cc70b4b581f28d0a254d006f26949245e3657d40d8857066c2ae22a61222ef55"}, - {file = "cryptography-43.0.0-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:4a997df8c1c2aae1e1e5ac49c2e4f610ad037fc5a3aadc7b64e39dea42249431"}, - {file = "cryptography-43.0.0-cp37-abi3-win32.whl", hash = "sha256:6e2b11c55d260d03a8cf29ac9b5e0608d35f08077d8c087be96287f43af3ccdc"}, - {file = "cryptography-43.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:31e44a986ceccec3d0498e16f3d27b2ee5fdf69ce2ab89b52eaad1d2f33d8778"}, - {file = "cryptography-43.0.0-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:7b3f5fe74a5ca32d4d0f302ffe6680fcc5c28f8ef0dc0ae8f40c0f3a1b4fca66"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac1955ce000cb29ab40def14fd1bbfa7af2017cca696ee696925615cafd0dce5"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:299d3da8e00b7e2b54bb02ef58d73cd5f55fb31f33ebbf33bd00d9aa6807df7e"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:ee0c405832ade84d4de74b9029bedb7b31200600fa524d218fc29bfa371e97f5"}, - {file = "cryptography-43.0.0-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cb013933d4c127349b3948aa8aaf2f12c0353ad0eccd715ca789c8a0f671646f"}, - {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fdcb265de28585de5b859ae13e3846a8e805268a823a12a4da2597f1f5afc9f0"}, - {file = "cryptography-43.0.0-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2905ccf93a8a2a416f3ec01b1a7911c3fe4073ef35640e7ee5296754e30b762b"}, - {file = "cryptography-43.0.0-cp39-abi3-win32.whl", hash = "sha256:47ca71115e545954e6c1d207dd13461ab81f4eccfcb1345eac874828b5e3eaaf"}, - {file = "cryptography-43.0.0-cp39-abi3-win_amd64.whl", hash = "sha256:0663585d02f76929792470451a5ba64424acc3cd5227b03921dab0e2f27b1709"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:2c6d112bf61c5ef44042c253e4859b3cbbb50df2f78fa8fae6747a7814484a70"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:844b6d608374e7d08f4f6e6f9f7b951f9256db41421917dfb2d003dde4cd6b66"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:51956cf8730665e2bdf8ddb8da0056f699c1a5715648c1b0144670c1ba00b48f"}, - {file = "cryptography-43.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:aae4d918f6b180a8ab8bf6511a419473d107df4dbb4225c7b48c5c9602c38c7f"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:232ce02943a579095a339ac4b390fbbe97f5b5d5d107f8a08260ea2768be8cc2"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:5bcb8a5620008a8034d39bce21dc3e23735dfdb6a33a06974739bfa04f853947"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:08a24a7070b2b6804c1940ff0f910ff728932a9d0e80e7814234269f9d46d069"}, - {file = "cryptography-43.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:e9c5266c432a1e23738d178e51c2c7a5e2ddf790f248be939448c0ba2021f9d1"}, - {file = "cryptography-43.0.0.tar.gz", hash = "sha256:b88075ada2d51aa9f18283532c9f60e72170041bba88d7f37e49cbb10275299e"}, + {file = "cryptography-43.0.3-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:bf7a1932ac4176486eab36a19ed4c0492da5d97123f1406cf15e41b05e787d2e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:63efa177ff54aec6e1c0aefaa1a241232dcd37413835a9b674b6e3f0ae2bfd3e"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e1ce50266f4f70bf41a2c6dc4358afadae90e2a1e5342d3c08883df1675374f"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:443c4a81bb10daed9a8f334365fe52542771f25aedaf889fd323a853ce7377d6"}, + {file = "cryptography-43.0.3-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:74f57f24754fe349223792466a709f8e0c093205ff0dca557af51072ff47ab18"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:9762ea51a8fc2a88b70cf2995e5675b38d93bf36bd67d91721c309df184f49bd"}, + {file = "cryptography-43.0.3-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:81ef806b1fef6b06dcebad789f988d3b37ccaee225695cf3e07648eee0fc6b73"}, + {file = "cryptography-43.0.3-cp37-abi3-win32.whl", hash = "sha256:cbeb489927bd7af4aa98d4b261af9a5bc025bd87f0e3547e11584be9e9427be2"}, + {file = "cryptography-43.0.3-cp37-abi3-win_amd64.whl", hash = "sha256:f46304d6f0c6ab8e52770addfa2fc41e6629495548862279641972b6215451cd"}, + {file = "cryptography-43.0.3-cp39-abi3-macosx_10_9_universal2.whl", hash = "sha256:8ac43ae87929a5982f5948ceda07001ee5e83227fd69cf55b109144938d96984"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:846da004a5804145a5f441b8530b4bf35afbf7da70f82409f151695b127213d5"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0f996e7268af62598f2fc1204afa98a3b5712313a55c4c9d434aef49cadc91d4"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:f7b178f11ed3664fd0e995a47ed2b5ff0a12d893e41dd0494f406d1cf555cab7"}, + {file = "cryptography-43.0.3-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:c2e6fc39c4ab499049df3bdf567f768a723a5e8464816e8f009f121a5a9f4405"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:e1be4655c7ef6e1bbe6b5d0403526601323420bcf414598955968c9ef3eb7d16"}, + {file = "cryptography-43.0.3-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:df6b6c6d742395dd77a23ea3728ab62f98379eff8fb61be2744d4679ab678f73"}, + {file = "cryptography-43.0.3-cp39-abi3-win32.whl", hash = "sha256:d56e96520b1020449bbace2b78b603442e7e378a9b3bd68de65c782db1507995"}, + {file = "cryptography-43.0.3-cp39-abi3-win_amd64.whl", hash = "sha256:0c580952eef9bf68c4747774cde7ec1d85a6e61de97281f2dba83c7d2c806362"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d03b5621a135bffecad2c73e9f4deb1a0f977b9a8ffe6f8e002bf6c9d07b918c"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:a2a431ee15799d6db9fe80c82b055bae5a752bef645bba795e8e52687c69efe3"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:281c945d0e28c92ca5e5930664c1cefd85efe80e5c0d2bc58dd63383fda29f83"}, + {file = "cryptography-43.0.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:f18c716be16bc1fea8e95def49edf46b82fccaa88587a45f8dc0ff6ab5d8e0a7"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:4a02ded6cd4f0a5562a8887df8b3bd14e822a90f97ac5e544c162899bc467664"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:53a583b6637ab4c4e3591a15bc9db855b8d9dee9a669b550f311480acab6eb08"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1ec0bcf7e17c0c5669d881b1cd38c4972fade441b27bda1051665faaa89bdcaa"}, + {file = "cryptography-43.0.3-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:2ce6fae5bdad59577b44e4dfed356944fbf1d925269114c28be377692643b4ff"}, + {file = "cryptography-43.0.3.tar.gz", hash = "sha256:315b9001266a492a6ff443b61238f956b214dbec9910a081ba5b6646a055a805"}, ] [package.dependencies] @@ -365,7 +376,7 @@ nox = ["nox"] pep8test = ["check-sdist", "click", "mypy", "ruff"] sdist = ["build"] ssh = ["bcrypt (>=3.1.5)"] -test = ["certifi", "cryptography-vectors (==43.0.0)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test = ["certifi", "cryptography-vectors (==43.0.3)", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] test-randomorder = ["pytest-randomly"] [[package]] @@ -420,13 +431,13 @@ static3 = "*" [[package]] name = "django" -version = "4.2.15" +version = "4.2.16" description = "A high-level Python web framework that encourages rapid development and clean, pragmatic design." optional = false python-versions = ">=3.8" files = [ - {file = "Django-4.2.15-py3-none-any.whl", hash = "sha256:61ee4a130efb8c451ef3467c67ca99fdce400fedd768634efc86a68c18d80d30"}, - {file = "Django-4.2.15.tar.gz", hash = "sha256:c77f926b81129493961e19c0e02188f8d07c112a1162df69bfab178ae447f94a"}, + {file = "Django-4.2.16-py3-none-any.whl", hash = "sha256:1ddc333a16fc139fd253035a1606bb24261951bbc3a6ca256717fa06cc41a898"}, + {file = "Django-4.2.16.tar.gz", hash = "sha256:6f1616c2786c408ce86ab7e10f792b8f15742f7b7b7460243929cb371e7f1dad"}, ] [package.dependencies] @@ -487,6 +498,20 @@ files = [ {file = "docopt-0.6.2.tar.gz", hash = "sha256:49b3a825280bd66b3aa83585ef59c4a8c82f2c8a522dbe754a8bc8d08c85c491"}, ] +[[package]] +name = "executing" +version = "2.1.0" +description = "Get the currently executing AST node of a frame, and other information" +optional = false +python-versions = ">=3.8" +files = [ + {file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"}, + {file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"}, +] + +[package.extras] +tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"] + [[package]] name = "gunicorn" version = "22.0.0" @@ -510,15 +535,18 @@ tornado = ["tornado (>=0.2)"] [[package]] name = "idna" -version = "3.7" +version = "3.10" description = "Internationalized Domain Names in Applications (IDNA)" optional = false -python-versions = ">=3.5" +python-versions = ">=3.6" files = [ - {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, - {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, ] +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + [[package]] name = "ipdb" version = "0.13.13" @@ -536,39 +564,39 @@ ipython = {version = ">=7.31.1", markers = "python_version >= \"3.11\""} [[package]] name = "ipython" -version = "7.34.0" +version = "8.28.0" description = "IPython: Productive Interactive Computing" optional = false -python-versions = ">=3.7" +python-versions = ">=3.10" files = [ - {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, - {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, + {file = "ipython-8.28.0-py3-none-any.whl", hash = "sha256:530ef1e7bb693724d3cdc37287c80b07ad9b25986c007a53aa1857272dac3f35"}, + {file = "ipython-8.28.0.tar.gz", hash = "sha256:0d0d15ca1e01faeb868ef56bc7ee5a0de5bd66885735682e8a322ae289a13d1a"}, ] [package.dependencies] -appnope = {version = "*", markers = "sys_platform == \"darwin\""} -backcall = "*" colorama = {version = "*", markers = "sys_platform == \"win32\""} decorator = "*" jedi = ">=0.16" matplotlib-inline = "*" -pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} -pickleshare = "*" -prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" -pygments = "*" -setuptools = ">=18.5" -traitlets = ">=4.2" +pexpect = {version = ">4.3", markers = "sys_platform != \"win32\" and sys_platform != \"emscripten\""} +prompt-toolkit = ">=3.0.41,<3.1.0" +pygments = ">=2.4.0" +stack-data = "*" +traitlets = ">=5.13.0" [package.extras] -all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] -doc = ["Sphinx (>=1.3)"] +all = ["ipython[black,doc,kernel,matplotlib,nbconvert,nbformat,notebook,parallel,qtconsole]", "ipython[test,test-extra]"] +black = ["black"] +doc = ["docrepr", "exceptiongroup", "intersphinx-registry", "ipykernel", "ipython[test]", "matplotlib", "setuptools (>=18.5)", "sphinx (>=1.3)", "sphinx-rtd-theme", "sphinxcontrib-jquery", "tomli", "typing-extensions"] kernel = ["ipykernel"] +matplotlib = ["matplotlib"] nbconvert = ["nbconvert"] nbformat = ["nbformat"] notebook = ["ipywidgets", "notebook"] parallel = ["ipyparallel"] qtconsole = ["qtconsole"] -test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] +test = ["packaging", "pickleshare", "pytest", "pytest-asyncio (<0.22)", "testpath"] +test-extra = ["curio", "ipython[test]", "matplotlib (!=3.2.0)", "nbformat", "numpy (>=1.23)", "pandas", "trio"] [[package]] name = "jedi" @@ -675,17 +703,6 @@ files = [ [package.dependencies] ptyprocess = ">=0.5" -[[package]] -name = "pickleshare" -version = "0.7.5" -description = "Tiny 'shelve'-like database with concurrency support" -optional = false -python-versions = "*" -files = [ - {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, - {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, -] - [[package]] name = "pillow" version = "10.3.0" @@ -774,13 +791,13 @@ xmp = ["defusedxml"] [[package]] name = "prompt-toolkit" -version = "3.0.47" +version = "3.0.48" description = "Library for building powerful interactive command lines in Python" optional = false python-versions = ">=3.7.0" files = [ - {file = "prompt_toolkit-3.0.47-py3-none-any.whl", hash = "sha256:0d7bfa67001d5e39d02c224b663abc33687405033a8c422d0d675a5a13361d10"}, - {file = "prompt_toolkit-3.0.47.tar.gz", hash = "sha256:1e1b29cb58080b1e69f207c893a1a7bf16d127a5c30c9d17a25a5d77792e5360"}, + {file = "prompt_toolkit-3.0.48-py3-none-any.whl", hash = "sha256:f49a827f90062e411f1ce1f854f2aedb3c23353244f8108b89283587397ac10e"}, + {file = "prompt_toolkit-3.0.48.tar.gz", hash = "sha256:d6623ab0477a80df74e646bdbc93621143f5caf104206aa29294d53de1a03d90"}, ] [package.dependencies] @@ -788,27 +805,28 @@ wcwidth = "*" [[package]] name = "psutil" -version = "5.9.8" +version = "6.0.0" description = "Cross-platform lib for process and system monitoring in Python." optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" files = [ - {file = "psutil-5.9.8-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:26bd09967ae00920df88e0352a91cff1a78f8d69b3ecabbfe733610c0af486c8"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:05806de88103b25903dff19bb6692bd2e714ccf9e668d050d144012055cbca73"}, - {file = "psutil-5.9.8-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:611052c4bc70432ec770d5d54f64206aa7203a101ec273a0cd82418c86503bb7"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:50187900d73c1381ba1454cf40308c2bf6f34268518b3f36a9b663ca87e65e36"}, - {file = "psutil-5.9.8-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02615ed8c5ea222323408ceba16c60e99c3f91639b07da6373fb7e6539abc56d"}, - {file = "psutil-5.9.8-cp27-none-win32.whl", hash = "sha256:36f435891adb138ed3c9e58c6af3e2e6ca9ac2f365efe1f9cfef2794e6c93b4e"}, - {file = "psutil-5.9.8-cp27-none-win_amd64.whl", hash = "sha256:bd1184ceb3f87651a67b2708d4c3338e9b10c5df903f2e3776b62303b26cb631"}, - {file = "psutil-5.9.8-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:aee678c8720623dc456fa20659af736241f575d79429a0e5e9cf88ae0605cc81"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8cb6403ce6d8e047495a701dc7c5bd788add903f8986d523e3e20b98b733e421"}, - {file = "psutil-5.9.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d06016f7f8625a1825ba3732081d77c94589dca78b7a3fc072194851e88461a4"}, - {file = "psutil-5.9.8-cp36-cp36m-win32.whl", hash = "sha256:7d79560ad97af658a0f6adfef8b834b53f64746d45b403f225b85c5c2c140eee"}, - {file = "psutil-5.9.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27cc40c3493bb10de1be4b3f07cae4c010ce715290a5be22b98493509c6299e2"}, - {file = "psutil-5.9.8-cp37-abi3-win32.whl", hash = "sha256:bc56c2a1b0d15aa3eaa5a60c9f3f8e3e565303b465dbf57a1b730e7a2b9844e0"}, - {file = "psutil-5.9.8-cp37-abi3-win_amd64.whl", hash = "sha256:8db4c1b57507eef143a15a6884ca10f7c73876cdf5d51e713151c1236a0e68cf"}, - {file = "psutil-5.9.8-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:d16bbddf0693323b8c6123dd804100241da461e41d6e332fb0ba6058f630f8c8"}, - {file = "psutil-5.9.8.tar.gz", hash = "sha256:6be126e3225486dff286a8fb9a06246a5253f4c7c53b475ea5f5ac934e64194c"}, + {file = "psutil-6.0.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a021da3e881cd935e64a3d0a20983bda0bb4cf80e4f74fa9bfcb1bc5785360c6"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:1287c2b95f1c0a364d23bc6f2ea2365a8d4d9b726a3be7294296ff7ba97c17f0"}, + {file = "psutil-6.0.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:a9a3dbfb4de4f18174528d87cc352d1f788b7496991cca33c6996f40c9e3c92c"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:6ec7588fb3ddaec7344a825afe298db83fe01bfaaab39155fa84cf1c0d6b13c3"}, + {file = "psutil-6.0.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1e7c870afcb7d91fdea2b37c24aeb08f98b6d67257a5cb0a8bc3ac68d0f1a68c"}, + {file = "psutil-6.0.0-cp27-none-win32.whl", hash = "sha256:02b69001f44cc73c1c5279d02b30a817e339ceb258ad75997325e0e6169d8b35"}, + {file = "psutil-6.0.0-cp27-none-win_amd64.whl", hash = "sha256:21f1fb635deccd510f69f485b87433460a603919b45e2a324ad65b0cc74f8fb1"}, + {file = "psutil-6.0.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:c588a7e9b1173b6e866756dde596fd4cad94f9399daf99ad8c3258b3cb2b47a0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ed2440ada7ef7d0d608f20ad89a04ec47d2d3ab7190896cd62ca5fc4fe08bf0"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fd9a97c8e94059b0ef54a7d4baf13b405011176c3b6ff257c247cae0d560ecd"}, + {file = "psutil-6.0.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e2e8d0054fc88153ca0544f5c4d554d42e33df2e009c4ff42284ac9ebdef4132"}, + {file = "psutil-6.0.0-cp36-cp36m-win32.whl", hash = "sha256:fc8c9510cde0146432bbdb433322861ee8c3efbf8589865c8bf8d21cb30c4d14"}, + {file = "psutil-6.0.0-cp36-cp36m-win_amd64.whl", hash = "sha256:34859b8d8f423b86e4385ff3665d3f4d94be3cdf48221fbe476e883514fdb71c"}, + {file = "psutil-6.0.0-cp37-abi3-win32.whl", hash = "sha256:a495580d6bae27291324fe60cea0b5a7c23fa36a7cd35035a16d93bdcf076b9d"}, + {file = "psutil-6.0.0-cp37-abi3-win_amd64.whl", hash = "sha256:33ea5e1c975250a720b3a6609c490db40dae5d83a4eb315170c4fe0d8b1f34b3"}, + {file = "psutil-6.0.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:ffe7fc9b6b36beadc8c322f84e1caff51e8703b88eee1da46d1e3a6ae11b4fd0"}, + {file = "psutil-6.0.0.tar.gz", hash = "sha256:8faae4f310b6d969fa26ca0545338b21f73c6b15db7c4a8d934a5482faa818f2"}, ] [package.extras] @@ -848,6 +866,20 @@ files = [ {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, ] +[[package]] +name = "pure-eval" +version = "0.2.3" +description = "Safely evaluate AST nodes without side effects" +optional = false +python-versions = "*" +files = [ + {file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"}, + {file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"}, +] + +[package.extras] +tests = ["pytest"] + [[package]] name = "pycparser" version = "2.22" @@ -875,19 +907,19 @@ windows-terminal = ["colorama (>=0.4.6)"] [[package]] name = "pyjwt" -version = "2.8.0" +version = "2.9.0" description = "JSON Web Token implementation in Python" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "PyJWT-2.8.0-py3-none-any.whl", hash = "sha256:59127c392cc44c2da5bb3192169a91f429924e17aff6534d70fdc02ab3e04320"}, - {file = "PyJWT-2.8.0.tar.gz", hash = "sha256:57e28d156e3d5c10088e0c68abb90bfac3df82b40a71bd0daa20c65ccd5c23de"}, + {file = "PyJWT-2.9.0-py3-none-any.whl", hash = "sha256:3b02fb0f44517787776cf48f2ae25d8e14f300e6d7545a4315cee571a415e850"}, + {file = "pyjwt-2.9.0.tar.gz", hash = "sha256:7e1e5b56cc735432a7369cbfa0efe50fa113ebecdc04ae6922deba8b84582d0c"}, ] [package.extras] crypto = ["cryptography (>=3.4.0)"] -dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] -docs = ["sphinx (>=4.5.0,<5.0.0)", "sphinx-rtd-theme", "zope.interface"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] [[package]] @@ -1089,21 +1121,16 @@ starlite = ["starlite (>=1.48)"] tornado = ["tornado (>=6)"] [[package]] -name = "setuptools" -version = "72.1.0" -description = "Easily download, build, install, upgrade, and uninstall Python packages" +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" optional = false -python-versions = ">=3.8" +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" files = [ - {file = "setuptools-72.1.0-py3-none-any.whl", hash = "sha256:5a03e1860cf56bb6ef48ce186b0e557fdba433237481a9a625176c2831be15d1"}, - {file = "setuptools-72.1.0.tar.gz", hash = "sha256:8d243eff56d095e5817f796ede6ae32941278f542e0f941867cc05ae52b162ec"}, + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, ] -[package.extras] -core = ["importlib-metadata (>=6)", "importlib-resources (>=5.10.2)", "jaraco.text (>=3.7)", "more-itertools (>=8.8)", "ordered-set (>=3.1.1)", "packaging (>=24)", "platformdirs (>=2.6.2)", "tomli (>=2.0.1)", "wheel (>=0.43.0)"] -doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "pyproject-hooks (!=1.1)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] -test = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.14)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "jaraco.test", "mypy (==1.11.*)", "packaging (>=23.2)", "pip (>=19.1)", "pyproject-hooks (!=1.1)", "pytest (>=6,!=8.1.*)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (<0.4)", "pytest-ruff (>=0.2.1)", "pytest-ruff (>=0.3.2)", "pytest-subprocess", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] - [[package]] name = "social-auth-app-django" version = "5.4.1" @@ -1160,6 +1187,25 @@ files = [ dev = ["build", "hatch"] doc = ["sphinx"] +[[package]] +name = "stack-data" +version = "0.6.3" +description = "Extract data from python stack frames and tracebacks for informative displays" +optional = false +python-versions = "*" +files = [ + {file = "stack_data-0.6.3-py3-none-any.whl", hash = "sha256:d5558e0c25a4cb0853cddad3d77da9891a08cb85dd9f9f91b9f8cd66e511e695"}, + {file = "stack_data-0.6.3.tar.gz", hash = "sha256:836a778de4fec4dcd1dcd89ed8abff8a221f58308462e1c4aa2a3cf30148f0b9"}, +] + +[package.dependencies] +asttokens = ">=2.1.0" +executing = ">=1.2.0" +pure-eval = "*" + +[package.extras] +tests = ["cython", "littleutils", "pygments", "pytest", "typeguard"] + [[package]] name = "static3" version = "0.7.0" @@ -1202,24 +1248,24 @@ files = [ [[package]] name = "tzdata" -version = "2024.1" +version = "2024.2" description = "Provider of IANA time zone data" optional = false python-versions = ">=2" files = [ - {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, - {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, + {file = "tzdata-2024.2-py2.py3-none-any.whl", hash = "sha256:a48093786cdcde33cad18c2555e8532f34422074448fbc874186f0abd79565cd"}, + {file = "tzdata-2024.2.tar.gz", hash = "sha256:7d85cc416e9382e69095b7bdf4afd9e3880418a2413feec7069d533d6b4e31cc"}, ] [[package]] name = "urllib3" -version = "2.2.2" +version = "2.2.3" description = "HTTP library with thread-safe connection pooling, file post, and more." optional = false python-versions = ">=3.8" files = [ - {file = "urllib3-2.2.2-py3-none-any.whl", hash = "sha256:a448b2f64d686155468037e1ace9f2d2199776e17f0a46610480d311f73e3472"}, - {file = "urllib3-2.2.2.tar.gz", hash = "sha256:dd505485549a7a552833da5e6063639d0d177c04f23bc3864e41e5dc5f612168"}, + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, ] [package.extras] @@ -1242,4 +1288,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "~3.12" -content-hash = "2a909c0a788fae71161c7311d68d97e80ee3abd09c8f0d9fe36c811d033c0e1c" +content-hash = "a5a67f8d731dd2ea5da4fcd7d8f5ba8d066403fde7fd8188bd53f90bfe5423ff" diff --git a/pyproject.toml b/pyproject.toml index 6a57a57..02a1859 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ license = "Apache-2.0" [tool.poetry.dependencies] python = "~3.12" -Django = "4.2.15" +Django = "4.2.16" django-ipware = "7.0.1" social-auth-app-django = "5.4.1" social-auth-core = "4.5.4" @@ -21,13 +21,13 @@ python-memcached = "1.62" qrcode = "7.4.2" pyotp = "2.9.0" Pillow = "10.3.0" -PyJWT = "2.8.0" -psutil = "5.9.8" +PyJWT = "2.9.0" +psutil = "6.0.0" redis = "5.0.4" sentry-sdk = {version = "2.16.0", extras = ["django"]} [tool.poetry.dev-dependencies] -ipython = "^7.31.1" +ipython = "^8.28.0" coverage = "^5.3" coveralls = "^2.1.2" mock = "^4.0.2" diff --git a/testcluster b/testcluster index 9ec6747..510992f 100755 --- a/testcluster +++ b/testcluster @@ -14,14 +14,14 @@ if [[ "$mode" == "release" ]] then echo "Running in release mode" echo "Backup the source code via renaming authome to authome.bak" - mv -v authome authome.bak + mv authome authome.bak if [[ "$?" != "0" ]] then echo "Failed to rename authome to authome.bak" exit 1 fi echo "Get the test code via copying authome.bak to authome" - cp -rvf authome.bak authome + cp -rf authome.bak authome if [[ "$?" != "0" ]] then echo "Failed to copy authome.bak to authome" @@ -53,5 +53,5 @@ then echo "remove the test code" rm -rf authome echo "Recovery the source code vi renameing authome.bak to authome" - mv -v authome.bak authome + mv authome.bak authome fi diff --git a/testsessiontimeout b/testsessiontimeout new file mode 100755 index 0000000..7092542 --- /dev/null +++ b/testsessiontimeout @@ -0,0 +1,57 @@ +#!/bin/bash +# please start the standlone redis and rediscluster first before run this unitest +#source venv/bin/activate && python manage.py test authome --keepdb +if [[ "$1" == "" ]] +then + mode="debug" +elif [[ "$1" == "release" ]] +then + mode="release" +else + mode="debug" +fi +if [[ "$mode" == "release" ]] +then + echo "Running in release mode" + echo "Backup the source code via renaming authome to authome.bak" + mv authome authome.bak + if [[ "$?" != "0" ]] + then + echo "Failed to rename authome to authome.bak" + exit 1 + fi + echo "Get the test code via copying authome.bak to authome" + cp -rf authome.bak authome + if [[ "$?" != "0" ]] + then + echo "Failed to copy authome.bak to authome" + exit 1 + fi + cd authome + echo "prepare the test code via comment out the debug.log, Debug.log and performance related code" + find ./ -type f -iname '*.py' -exec sed -i 's/logger\.debug/#logger.debug/g' "{}" +; + find ./ -type f -iname '*.py' -exec sed -i 's/from \. import performance/#from . import performance/g' "{}" +; + find ./ -type f -iname '*.py' -exec sed -i 's/from \.\. import performance/#from .. import performance/g' "{}" +; + find ./ -type f -iname '*.py' -exec sed -i 's/performance\.start_processingstep/#performance.start_processingstep/g' "{}" +; + find ./ -type f -iname '*.py' -exec sed -i 's/performance\.end_processingstep/#performance.end_processingstep/g' "{}" +; + + find ./ -type f -iname '*.py' -exec sed -i 's/from \.models import DebugLog/#from .models import DebugLog/g' "{}" +; + find ./ -type f -iname '*.py' -exec sed -i 's/from \.\.models import DebugLog/#from ..models import DebugLog/g' "{}" +; + find ./ -type f -iname '*.py' -exec sed -i 's/DebugLog\.log/#DebugLog.log/g' "{}" +; + find ./ -type f -iname '*.py' -exec sed -i 's/DebugLog\.attach_request/#DebugLog.attach_request/g' "{}" +; + + cd .. +else + echo "Running in debug mode" +fi +echo "Running unit test" +TEST_RUNNER=authome.testrunners.NoDatabaseTestRunner +export TEST_RUNNER +export IGNORE_LOADING_ERROR=True ; poetry run python manage.py test authome --keepdb --pattern="testsessiontimeout.py" +if [[ "$mode" == "release" ]] +then + echo "remove the test code" + rm -rf authome + echo "Recovery the source code vi renameing authome.bak to authome" + mv authome.bak authome +fi