From 954092890ee13e75a5d6e5e08b5a3b2ae36da59e Mon Sep 17 00:00:00 2001 From: fanhe Date: Tue, 26 Apr 2022 14:05:35 +0800 Subject: [PATCH 1/4] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20scim=20=E5=8D=8F?= =?UTF-8?q?=E8=AE=AE=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arkid/urls.py | 5 +- .../in_memory_user_provider.py | 18 +-- scim_server/protocol/query_response_base.py | 101 ++++++------ scim_server/schemas/address.py | 99 +++++++++++- scim_server/schemas/address_base.py | 99 ------------ scim_server/schemas/authentication_scheme.py | 17 ++ scim_server/schemas/bulk_requests_feature.py | 54 ++++--- scim_server/schemas/core2_enterprise_user.py | 50 +++++- .../schemas/core2_enterprise_user_base.py | 46 ------ scim_server/schemas/core2_group.py | 41 ++++- scim_server/schemas/core2_group_base.py | 61 ++++--- scim_server/schemas/core2_metadata.py | 12 +- .../schemas/core2_service_configuration.py | 56 ++++--- scim_server/schemas/core2_user_base.py | 74 +++++---- .../schemas/electronic_mail_address.py | 12 +- .../schemas/electronic_mail_address_base.py | 9 -- ...=> extension_attribute_enterprise_user.py} | 15 +- ...xtension_attribute_enterprise_user_base.py | 94 ----------- scim_server/schemas/feature.py | 15 +- scim_server/schemas/feature_base.py | 13 -- scim_server/schemas/filter_feature.py | 7 + scim_server/schemas/group_base.py | 49 ------ scim_server/schemas/instant_messaging.py | 18 ++- scim_server/schemas/instant_messaging_base.py | 13 -- scim_server/schemas/manager.py | 8 +- scim_server/schemas/member.py | 53 +++++- scim_server/schemas/member_base.py | 48 ------ scim_server/schemas/name.py | 11 +- scim_server/schemas/phone_number.py | 15 +- scim_server/schemas/phone_number_base.py | 11 -- scim_server/schemas/photo.py | 10 ++ scim_server/schemas/resource.py | 10 +- scim_server/schemas/role.py | 44 ++++- scim_server/schemas/role_base.py | 45 ------ scim_server/schemas/schematized.py | 10 +- .../schemas/service_configuration_base.py | 153 ++++++++++-------- scim_server/schemas/typed_item.py | 6 +- scim_server/schemas/typed_value.py | 4 +- scim_server/schemas/user_base.py | 31 ---- scim_server/schemas/user_groups.py | 14 ++ scim_server/service/provider_base.py | 14 +- scim_server/urls.py | 28 ++-- scim_server/utils.py | 6 +- scim_server/views/view_template.py | 7 +- 44 files changed, 729 insertions(+), 777 deletions(-) delete mode 100644 scim_server/schemas/address_base.py create mode 100644 scim_server/schemas/authentication_scheme.py delete mode 100644 scim_server/schemas/core2_enterprise_user_base.py delete mode 100644 scim_server/schemas/electronic_mail_address_base.py rename scim_server/schemas/{extension_attribute_enterprise_user2.py => extension_attribute_enterprise_user.py} (73%) delete mode 100644 scim_server/schemas/extension_attribute_enterprise_user_base.py delete mode 100644 scim_server/schemas/feature_base.py create mode 100644 scim_server/schemas/filter_feature.py delete mode 100644 scim_server/schemas/group_base.py delete mode 100644 scim_server/schemas/instant_messaging_base.py delete mode 100644 scim_server/schemas/member_base.py delete mode 100644 scim_server/schemas/phone_number_base.py create mode 100644 scim_server/schemas/photo.py delete mode 100644 scim_server/schemas/role_base.py delete mode 100644 scim_server/schemas/user_base.py create mode 100644 scim_server/schemas/user_groups.py diff --git a/arkid/urls.py b/arkid/urls.py index 00ff3a0ab..6b2b577f3 100644 --- a/arkid/urls.py +++ b/arkid/urls.py @@ -20,6 +20,8 @@ from arkid.login import view as login_view from arkid.core import urls as core_urls from arkid.redoc import view as redoc_view +from scim_server import urls as scim_urls + urlpatterns = [ path("admin/", admin.site.urls), @@ -27,7 +29,8 @@ path("api/v1/login", login_view.LoginEnter.as_view()), path("api/v1/login_process", login_view.LoginProcess.as_view()), path("api/v1/redoc", redoc_view.Redoc.as_view()), - path("api/v1/openapi_redoc.json", redoc_view.RedocOpenAPI.as_view()) + path("api/v1/openapi_redoc.json", redoc_view.RedocOpenAPI.as_view()), ] urlpatterns += core_urls.urlpatterns +urlpatterns += scim_urls.urlpatterns diff --git a/scim_server/in_memory_provider/in_memory_user_provider.py b/scim_server/in_memory_provider/in_memory_user_provider.py index e999249fa..326b63a1c 100644 --- a/scim_server/in_memory_provider/in_memory_user_provider.py +++ b/scim_server/in_memory_provider/in_memory_user_provider.py @@ -19,26 +19,26 @@ def __init__(self): self.storage = Instance def create_async2(self, resource, correlation_identifier): - if resource.identifier is not None: + if resource.id is not None: raise BadRequestException() - if not resource.user_name: + if not resource.userName: raise BadRequestException() existing_users = self.storage.users.values() for item in existing_users: - if item.user_name == resource.user_name: + if item.userName == resource.userName: raise ConflictException() resource_identifier = uuid.uuid4().hex - resource.identifier = resource_identifier + resource.id = resource_identifier self.storage.users[resource_identifier] = resource - return resource + return resource.dict() def delete_async2(self, resource_identifier, correlation_identifier): if not resource_identifier.identifier: raise BadRequestException() - identifier = resource_identifier.identifier + identifier = resource_identifier.id if identifier in self.storage.users: del self.storage.users[identifier] @@ -48,7 +48,7 @@ def query_async2(self, parameters, correlation_identifier): raise ArgumentNullException('parameters') if not correlation_identifier: raise ArgumentNullException('correlation_identifier') - if parameters.alternate_filters is None: + if parameters.alternate_filters is None: raise ArgumentException('Invalid parameters') if not parameters.schema_identifier: @@ -56,7 +56,7 @@ def query_async2(self, parameters, correlation_identifier): if not parameters.alternate_filters: all_users = self.storage.users.values() - return all_users + return list(all_users) query_filter = parameters.alternate_filters[0] if not query_filter.attribute_path: @@ -102,7 +102,7 @@ def replace_async2(self, resource, correlation_identifier): for item in existing_users: if item.user_name == resource.user_name: raise ConflictException() - if resource.identifier not in self.storage.users: + if resource.id not in self.storage.users: raise NotFoundException() self.storage.users[resource.identifier] = resource diff --git a/scim_server/protocol/query_response_base.py b/scim_server/protocol/query_response_base.py index 812180e4a..5f1900381 100644 --- a/scim_server/protocol/query_response_base.py +++ b/scim_server/protocol/query_response_base.py @@ -3,9 +3,14 @@ from scim_server.exceptions import ArgumentNullException from scim_server.protocol.protocol_schema_identifiers import ProtocolSchemaIdentifiers from scim_server.protocol.protocol_attribute_names import ProtocolAttributeNames - +from typing import List, Any class QueryResponseBase(Schematized): + resources:List[Any] = [] + items_per_page:int = 0 + start_index: int = 0 + total_results: int = 0 + def __init__(self, resources): super().__init__() if resources is None: @@ -13,61 +18,61 @@ def __init__(self, resources): self.resources = resources self.add_schema(ProtocolSchemaIdentifiers.Version2ListResponse) - @property - def items_per_page(self): - if not hasattr(self, '_items_per_page'): - return None - return self._items_per_page + # @property + # def items_per_page(self): + # if not hasattr(self, '_items_per_page'): + # return None + # return self._items_per_page - @items_per_page.setter - def items_per_page(self, value): - self._items_per_page = value + # @items_per_page.setter + # def items_per_page(self, value): + # self._items_per_page = value - @property - def resources(self): - if not hasattr(self, '_resources'): - return None - return self._resources + # @property + # def resources(self): + # if not hasattr(self, '_resources'): + # return None + # return self._resources - @resources.setter - def resources(self, value): - if value is None: - raise ArgumentNullException('invalid value') - self._resources = value + # @resources.setter + # def resources(self, value): + # if value is None: + # raise ArgumentNullException('invalid value') + # self._resources = value - @property - def start_index(self): - if not hasattr(self, '_start_index'): - return None - return self._start_index + # @property + # def start_index(self): + # if not hasattr(self, '_start_index'): + # return None + # return self._start_index - @start_index.setter - def start_index(self, value): - self._start_index = value + # @start_index.setter + # def start_index(self, value): + # self._start_index = value - @property - def total_results(self): - if not hasattr(self, '_total_results'): - return None - return self._total_results + # @property + # def total_results(self): + # if not hasattr(self, '_total_results'): + # return None + # return self._total_results - @total_results.setter - def total_results(self, value): - self._total_results = value + # @total_results.setter + # def total_results(self, value): + # self._total_results = value - def to_dict(self): - d = super().to_dict() - if self.total_results is not None: - d[ProtocolAttributeNames.TotalResults] = self.total_results + # def to_dict(self): + # d = super().to_dict() + # if self.total_results is not None: + # d[ProtocolAttributeNames.TotalResults] = self.total_results - if self.items_per_page is not None: - d[ProtocolAttributeNames.ItemsPerPage] = self.items_per_page + # if self.items_per_page is not None: + # d[ProtocolAttributeNames.ItemsPerPage] = self.items_per_page - if self.start_index is not None: - d[ProtocolAttributeNames.StartIndex] = self.start_index + # if self.start_index is not None: + # d[ProtocolAttributeNames.StartIndex] = self.start_index - if self.resources is not None: - d[ProtocolAttributeNames.Resources] = [ - item.to_dict() for item in self.resources - ] - return d + # if self.resources is not None: + # d[ProtocolAttributeNames.Resources] = [ + # item.to_dict() for item in self.resources + # ] + # return d diff --git a/scim_server/schemas/address.py b/scim_server/schemas/address.py index 95f708fc2..d135580a0 100644 --- a/scim_server/schemas/address.py +++ b/scim_server/schemas/address.py @@ -1,7 +1,100 @@ #!/usr/bin/env python3 +from enum import Enum +from typing import Optional +from scim_server.schemas.typed_item import TypedItem +from scim_server.schemas.attribute_names import AttributeNames +class TypeEnum(str, Enum): + home = "home" + other = 'other' + work = 'work' -from scim_server.schemas.address_base import AddressBase +class Address(TypedItem): + type: TypeEnum + formatted: Optional[str] = None + country: Optional[str] = None + locality: Optional[str] = None + postCode: Optional[str] = None + region: Optional[str] = None + streetAddress: Optional[str] = None + # @property + # def country(self): + # return self._country -class Address(AddressBase): - pass + # @country.setter + # def country(self, value): + # self._country = value + + # @property + # def formatted(self): + # return self._formatted + + # @formatted.setter + # def formatted(self, value): + # self._formatted = value + + # @property + # def locality(self): + # return self._locality + + # @locality.setter + # def locality(self, value): + # self._locality = value + + # @property + # def postal_code(self): + # return self._postal_code + + # @postal_code.setter + # def postal_code(self, value): + # self._postal_code = value + + # @property + # def region(self): + # return self._region + + # @region.setter + # def region(self, value): + # self._region = value + + # @property + # def street_address(self): + # return self._street_address + + # @street_address.setter + # def street_address(self, value): + # self._street_address = value + + # @classmethod + # def from_dict(cls, d): + # obj = super().from_dict(d) + # if AttributeNames.Country in d: + # obj.country = d.get(AttributeNames.Country) + # if AttributeNames.Formatted in d: + # obj.formatted = d.get(AttributeNames.Formatted) + # if AttributeNames.Locality in d: + # obj.locality = d.get(AttributeNames.Locality) + # if AttributeNames.PostalCode in d: + # obj.postal_code = d.get(AttributeNames.PostalCode) + # if AttributeNames.Region in d: + # obj.region = d.get(AttributeNames.Region) + # if AttributeNames.StreetAddress in d: + # obj.street_address = d.get(AttributeNames.StreetAddress) + + # return obj + + # def to_dict(self): + # d = super().to_dict() + # if self.country is not None: + # d[AttributeNames.Country] = self.country + # if self.formatted is not None: + # d[AttributeNames.Formatted] = self.formatted + # if self.locality is not None: + # d[AttributeNames.Locality] = self.locality + # if self.postal_code is not None: + # d[AttributeNames.PostalCode] = self.postal_code + # if self.region is not None: + # d[AttributeNames.Region] = self.region + # if self.street_address is not None: + # d[AttributeNames.StreetAddress] = self.street_address + # return d diff --git a/scim_server/schemas/address_base.py b/scim_server/schemas/address_base.py deleted file mode 100644 index a3575ff28..000000000 --- a/scim_server/schemas/address_base.py +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env python3 -from scim_server.schemas.typed_item import TypedItem -from scim_server.schemas.attribute_names import AttributeNames - - -class AddressBase(TypedItem): - Home = 'home' - Other = 'other' - Untyped = 'untyped' - Work = 'work' - - country: str - formatted: str - locality: str - post_code: str - region: str - street_address: str - - # @property - # def country(self): - # return self._country - - # @country.setter - # def country(self, value): - # self._country = value - - # @property - # def formatted(self): - # return self._formatted - - # @formatted.setter - # def formatted(self, value): - # self._formatted = value - - # @property - # def locality(self): - # return self._locality - - # @locality.setter - # def locality(self, value): - # self._locality = value - - # @property - # def postal_code(self): - # return self._postal_code - - # @postal_code.setter - # def postal_code(self, value): - # self._postal_code = value - - # @property - # def region(self): - # return self._region - - # @region.setter - # def region(self, value): - # self._region = value - - # @property - # def street_address(self): - # return self._street_address - - # @street_address.setter - # def street_address(self, value): - # self._street_address = value - - # @classmethod - # def from_dict(cls, d): - # obj = super().from_dict(d) - # if AttributeNames.Country in d: - # obj.country = d.get(AttributeNames.Country) - # if AttributeNames.Formatted in d: - # obj.formatted = d.get(AttributeNames.Formatted) - # if AttributeNames.Locality in d: - # obj.locality = d.get(AttributeNames.Locality) - # if AttributeNames.PostalCode in d: - # obj.postal_code = d.get(AttributeNames.PostalCode) - # if AttributeNames.Region in d: - # obj.region = d.get(AttributeNames.Region) - # if AttributeNames.StreetAddress in d: - # obj.street_address = d.get(AttributeNames.StreetAddress) - - # return obj - - # def to_dict(self): - # d = super().to_dict() - # if self.country is not None: - # d[AttributeNames.Country] = self.country - # if self.formatted is not None: - # d[AttributeNames.Formatted] = self.formatted - # if self.locality is not None: - # d[AttributeNames.Locality] = self.locality - # if self.postal_code is not None: - # d[AttributeNames.PostalCode] = self.postal_code - # if self.region is not None: - # d[AttributeNames.Region] = self.region - # if self.street_address is not None: - # d[AttributeNames.StreetAddress] = self.street_address - # return d diff --git a/scim_server/schemas/authentication_scheme.py b/scim_server/schemas/authentication_scheme.py new file mode 100644 index 000000000..6348fe4c7 --- /dev/null +++ b/scim_server/schemas/authentication_scheme.py @@ -0,0 +1,17 @@ +from pydantic import BaseModel, HttpUrl +from enum import Enum +from typing import Optional + +class AuthTypeEnum(str, Enum): + oauth = "oauth" + oauth2 = "oauth2" + oauthbearertoken = "oauthbearertoken" + httpbasic = "httpbasic" + httpdigest = "httpdigest" + +class AuthenticationScheme(BaseModel): + type: AuthTypeEnum + name: str + description: str + specUri: Optional[HttpUrl] + documentationUri: Optional[HttpUrl] \ No newline at end of file diff --git a/scim_server/schemas/bulk_requests_feature.py b/scim_server/schemas/bulk_requests_feature.py index 282ef048d..dd5279c64 100644 --- a/scim_server/schemas/bulk_requests_feature.py +++ b/scim_server/schemas/bulk_requests_feature.py @@ -1,34 +1,38 @@ #!/usr/bin/env python3 -from scim_server.schemas.feature_base import FeatureBase +from scim_server.schemas.feature import Feature +from typing import Optional -class BulkRequestsFeature(FeatureBase): - @classmethod - def create_unsupported_feature(cls): - result = BulkRequestsFeature() - result.supported = False - return result +class BulkRequestsFeature(Feature): + maxOperations: int + maxPayloadSize: int - @property - def concurrent_operations(self): - return self._concurrent_operations + # @classmethod + # def create_unsupported_feature(cls): + # result = BulkRequestsFeature() + # result.supported = False + # return result - @concurrent_operations.setter - def concurrent_operation(self, value): - self._concurrent_operations = value + # @property + # def concurrent_operations(self): + # return self._concurrent_operations - @property - def maximum_operations(self): - return self._maximum_operations + # @concurrent_operations.setter + # def concurrent_operation(self, value): + # self._concurrent_operations = value - @maximum_operations.setter - def maximum_operations(self, value): - self._maximum_operations = value + # @property + # def maximum_operations(self): + # return self._maximum_operations - @property - def maximum_payload_size(self): - return self._maximum_payload_size + # @maximum_operations.setter + # def maximum_operations(self, value): + # self._maximum_operations = value - @maximum_payload_size.setter - def maximum_payload_size(self, value): - self._maximum_payload_size = value + # @property + # def maximum_payload_size(self): + # return self._maximum_payload_size + + # @maximum_payload_size.setter + # def maximum_payload_size(self, value): + # self._maximum_payload_size = value diff --git a/scim_server/schemas/core2_enterprise_user.py b/scim_server/schemas/core2_enterprise_user.py index 446d3bc8e..3df100fe5 100644 --- a/scim_server/schemas/core2_enterprise_user.py +++ b/scim_server/schemas/core2_enterprise_user.py @@ -1,6 +1,50 @@ #!/usr/bin/env python3 -from scim_server.schemas.core2_enterprise_user_base import Core2EnterpriseUserBase +from scim_server.schemas.core2_user_base import Core2UserBase +from scim_server.schemas.schema_identifiers import SchemaIdentifiers +from scim_server.schemas.extension_attribute_enterprise_user import ( + ExtensionAttributeEnterpriseUser, +) +from scim_server.schemas.attribute_names import AttributeNames +from typing import Optional +from pydantic import Field -class Core2EnterpriseUser(Core2EnterpriseUserBase): - pass +class Core2EnterpriseUser(Core2UserBase): + + enterprise_extension: Optional[ExtensionAttributeEnterpriseUser] = Field( + None, alias=AttributeNames.ExtensionEnterpriseUser2 + ) + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.add_schema(SchemaIdentifiers.Core2EnterpriseUser) + self.enterprise_extension = ExtensionAttributeEnterpriseUser() + + # @property + # def enterprise_extension(self): + # if not hasattr(self, '_enterprise_extension'): + # return None + # return self._enterprise_extension + + # @enterprise_extension.setter + # def enterprise_extension(self, value): + # self._enterprise_extension = value + + # @classmethod + # def from_dict(cls, d): + # obj = super().from_dict(d) + # if AttributeNames.ExtensionEnterpriseUser2 in d: + # extension = ExtensionAttributeEnterpriseUser2.from_dict( + # d.get(AttributeNames.ExtensionEnterpriseUser2) + # ) + # obj.enterprise_extension = extension + # return obj + + # def to_dict(self): + # d = super().to_dict() + # if self.enterprise_extension is not None: + # d[ + # AttributeNames.ExtensionEnterpriseUser2 + # ] = self.enterprise_extension.to_dict() + + # return d diff --git a/scim_server/schemas/core2_enterprise_user_base.py b/scim_server/schemas/core2_enterprise_user_base.py deleted file mode 100644 index 818390577..000000000 --- a/scim_server/schemas/core2_enterprise_user_base.py +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env python3 -from scim_server.schemas.core2_user_base import Core2UserBase -from scim_server.schemas.schema_identifiers import SchemaIdentifiers -from scim_server.schemas.extension_attribute_enterprise_user2 import ( - ExtensionAttributeEnterpriseUser2, -) -from scim_server.schemas.attribute_names import AttributeNames - - -class Core2EnterpriseUserBase(Core2UserBase): - - enterprise_extension: ExtensionAttributeEnterpriseUser2 - - # def __init__(self): - # super().__init__() - # self.add_schema(SchemaIdentifiers.Core2EnterpriseUser) - # self._enterprise_extension = ExtensionAttributeEnterpriseUser2() - - # @property - # def enterprise_extension(self): - # if not hasattr(self, '_enterprise_extension'): - # return None - # return self._enterprise_extension - - # @enterprise_extension.setter - # def enterprise_extension(self, value): - # self._enterprise_extension = value - - # @classmethod - # def from_dict(cls, d): - # obj = super().from_dict(d) - # if AttributeNames.ExtensionEnterpriseUser2 in d: - # extension = ExtensionAttributeEnterpriseUser2.from_dict( - # d.get(AttributeNames.ExtensionEnterpriseUser2) - # ) - # obj.enterprise_extension = extension - # return obj - - # def to_dict(self): - # d = super().to_dict() - # if self.enterprise_extension is not None: - # d[ - # AttributeNames.ExtensionEnterpriseUser2 - # ] = self.enterprise_extension.to_dict() - - # return d diff --git a/scim_server/schemas/core2_group.py b/scim_server/schemas/core2_group.py index f17e81e46..266910bac 100644 --- a/scim_server/schemas/core2_group.py +++ b/scim_server/schemas/core2_group.py @@ -1,6 +1,41 @@ #!/usr/bin/env python3 -from scim_server.schemas.core2_group_base import Core2GroupBase +from scim_server.schemas.core2_group_base import GroupBase +from scim_server.schemas.core2_metadata import Core2Metadata +from scim_server.schemas.types import Types +from scim_server.schemas.schema_identifiers import SchemaIdentifiers +from typing import Optional -class Core2Group(Core2GroupBase): - pass +class Core2Group(GroupBase): + meta: Optional[Core2Metadata] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.add_schema(SchemaIdentifiers.Core2Group) + self.meta = Core2Metadata(resourceType=Types.Group) + # self._custom_extension = {} + + # @property + # def custom_extension(self): + # if not hasattr(self, '_custom_extension'): + # return None + # return self._custom_extension + + # def add_custom_attribute(self, key, value): + # if ( + # key + # and key.startswith(SchemaIdentifiers.PrefixExtension) + # and type(value) == dict + # ): + # self._custom_extension[key] = value + + # def to_dict(self): + # result = super().to_dict() + # for key, value in self.custom_extension.items(): + # result[key] = value + # return result + + # # TODO custom extension + # @classmethod + # def from_dict(cls, d): + # return super().from_dict(d) diff --git a/scim_server/schemas/core2_group_base.py b/scim_server/schemas/core2_group_base.py index dc78b3e6c..f9517739d 100644 --- a/scim_server/schemas/core2_group_base.py +++ b/scim_server/schemas/core2_group_base.py @@ -1,40 +1,49 @@ #!/usr/bin/env python3 -from scim_server.schemas.group_base import GroupBase -from scim_server.schemas.core2_metadata import Core2Metadata -from scim_server.schemas.types import Types -from scim_server.schemas.schema_identifiers import SchemaIdentifiers +from scim_server.schemas.resource import Resource +from scim_server.schemas.attribute_names import AttributeNames +from scim_server.schemas.member import Member +from typing import List -class Core2GroupBase(GroupBase): - pass - # def __init__(self): - # super().__init__() - # self.add_schema(SchemaIdentifiers.Core2Group) - # self.metadata = Core2Metadata() - # self.metadata.resouce_type = Types.Group - # self._custom_extension = {} +class GroupBase(Resource): + display_name: str + members: List[Member] + # @property + # def display_name(self): + # if not hasattr(self, '_display_name'): + # return None + # return self._display_name + + # @display_name.setter + # def display_name(self, value): + # self._display_name = value # @property - # def custom_extension(self): - # if not hasattr(self, '_custom_extension'): + # def members(self): + # if not hasattr(self, '_members'): # return None - # return self._custom_extension + # return self._members - # def add_custom_attribute(self, key, value): - # if ( - # key - # and key.startswith(SchemaIdentifiers.PrefixExtension) - # and type(value) == dict - # ): - # self._custom_extension[key] = value + # @members.setter + # def members(self, value): + # self._members = value # def to_dict(self): # result = super().to_dict() - # for key, value in self.custom_extension.items(): - # result[key] = value + # if self.display_name is not None: + # result[AttributeNames.DisplayName] = self.display_name + + # if self.members is not None: + # result[AttributeNames.Members] = [item.to_dict() for item in self.members] # return result - # # TODO custom extension # @classmethod # def from_dict(cls, d): - # return super().from_dict(d) + # obj = super().from_dict(d) + # if AttributeNames.DisplayName in d: + # obj.display_name = d.get(AttributeNames.DisplayName) + # if AttributeNames.Members in d: + # obj.members = [ + # Member.from_dict(item) for item in d.get(AttributeNames.Members) + # ] + # return obj diff --git a/scim_server/schemas/core2_metadata.py b/scim_server/schemas/core2_metadata.py index 1b19ec789..3d418aabe 100644 --- a/scim_server/schemas/core2_metadata.py +++ b/scim_server/schemas/core2_metadata.py @@ -1,9 +1,15 @@ #!/usr/bin/env python3 -from scim_server.schemas.attribute_names import AttributeNames +from pydantic import BaseModel, HttpUrl +from datetime import datetime +from typing import Optional -class Core2Metadata: - resource_type: str +class Core2Metadata(BaseModel): + resourceType: str + created: Optional[datetime] + lastModified: Optional[datetime] + location: Optional[HttpUrl] + version: Optional[str] # @property # def resource_type(self): diff --git a/scim_server/schemas/core2_service_configuration.py b/scim_server/schemas/core2_service_configuration.py index d0ee53254..ece401ac3 100644 --- a/scim_server/schemas/core2_service_configuration.py +++ b/scim_server/schemas/core2_service_configuration.py @@ -1,37 +1,45 @@ #!/usr/bin/env python3 +from curses import meta from scim_server.schemas.service_configuration_base import ServiceConfigurationBase from scim_server.schemas.schema_identifiers import SchemaIdentifiers from scim_server.schemas.core2_metadata import Core2Metadata from scim_server.schemas.types import Types from scim_server.schemas.feature import Feature +from typing import Optional class Core2ServiceConfiguration(ServiceConfigurationBase): - def __init__( - self, - bulk_requests_support, - supports_entity_tags, - supports_filtering, - supports_password_change, - supports_patching, - supports_sorting, - ): - super().__init__() + meta: Optional[Core2Metadata] + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.meta = Core2Metadata(resourceType=Types.ServiceProviderConfiguration) self.add_schema(SchemaIdentifiers.Core2ServiceConfiguration) - self.metadata = Core2Metadata() - self.metadata.resource_type = Types.ServiceProviderConfiguration - self.bulk_requests = bulk_requests_support - self.entity_tags = Feature(supports_entity_tags) - self.filtering = Feature(supports_filtering) - self.password_change = Feature(supports_password_change) - self.patching = Feature(supports_patching) - self.sorting = Feature(supports_sorting) + # def __init__( + # self, + # bulk_requests_support, + # supports_entity_tags, + # supports_filtering, + # supports_password_change, + # supports_patching, + # supports_sorting, + # ): + # super().__init__() + # self.add_schema(SchemaIdentifiers.Core2ServiceConfiguration) + # self.metadata = Core2Metadata() + # self.metadata.resource_type = Types.ServiceProviderConfiguration - @property - def metadata(self): - return self._metadata + # self.bulk_requests = bulk_requests_support + # self.entity_tags = Feature(supports_entity_tags) + # self.filtering = Feature(supports_filtering) + # self.password_change = Feature(supports_password_change) + # self.patching = Feature(supports_patching) + # self.sorting = Feature(supports_sorting) - @metadata.setter - def metadata(self, value): - self._metadata = value + # @property + # def metadata(self): + # return self._metadata + + # @metadata.setter + # def metadata(self, value): + # self._metadata = value diff --git a/scim_server/schemas/core2_user_base.py b/scim_server/schemas/core2_user_base.py index 6e6ad2fe9..bc29b9d94 100644 --- a/scim_server/schemas/core2_user_base.py +++ b/scim_server/schemas/core2_user_base.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 -from scim_server.schemas.user_base import UserBase from scim_server.schemas.schema_identifiers import SchemaIdentifiers from scim_server.schemas.core2_metadata import Core2Metadata from scim_server.schemas.types import Types @@ -11,32 +10,53 @@ from scim_server.schemas.name import Name from scim_server.schemas.phone_number import PhoneNumber from scim_server.schemas.role import Role -from typing import List - - -class Core2UserBase(UserBase): - metadata: Core2Metadata - active: bool - address: List[Address] - display_name: str - electronic_mail_addresses: ElectroicMailAddress - instant_messagings: InstantMessaging - locale: str - metadate: dict - name: str - nickname: str - phone_numbers: List[PhoneNumber] - preferred_language: str - roles: List[Role] - timezone: str - title: str - user_type: str - # def __init__(self): - # super().__init__() - # self.add_schema(SchemaIdentifiers.Core2User) - # self.metadata = Core2Metadata() - # self.metadata.resource_type = Types.User - # self._custom_extension = {} +from scim_server.schemas.photo import Photo +from scim_server.schemas.user_groups import UserGroups +from scim_server.schemas.resource import Resource +from typing import List, Optional +from enum import Enum + + +class UserTypeEnum(str, Enum): + Contractor = "Contractor" + Employee = 'Employee' + Intern = 'Temp' + Temp = 'Temp' + External = 'External' + Unknown = 'Unknown' + + +class Core2UserBase(Resource): + # meta: Core2Metadata + userName: str + name: Optional[Name] + displayName: Optional[str] + nickName: Optional[str] + profileUrl: Optional[str] + title: Optional[str] + userType: Optional[UserTypeEnum] + preferredLanguage: Optional[str] + locale: Optional[str] + timezone: Optional[str] + active: Optional[bool] + password: Optional[bool] + ######################## multi-valued attributes ############ + emails: Optional[List[ElectroicMailAddress]] + phone_numbers: Optional[List[PhoneNumber]] + ims: Optional[List[InstantMessaging]] + photos: Optional[List[Photo]] + address: Optional[List[Address]] + groups: Optional[List[UserGroups]] + # TODO entitlements, + roles: Optional[List[Role]] + # TODO x509Certificates + meta: Optional[Core2Metadata] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.add_schema(SchemaIdentifiers.Core2User) + self.meta = Core2Metadata(resourceType=Types.User) + # self._custom_extension = {} # @property # def active(self): diff --git a/scim_server/schemas/electronic_mail_address.py b/scim_server/schemas/electronic_mail_address.py index d858b076e..40ebbb60b 100644 --- a/scim_server/schemas/electronic_mail_address.py +++ b/scim_server/schemas/electronic_mail_address.py @@ -1,6 +1,10 @@ #!/usr/bin/env python3 -from scim_server.schemas.electronic_mail_address_base import ElectroicMailAddressBase +from enum import Enum +from .typed_value import TypedValue - -class ElectroicMailAddress(ElectroicMailAddressBase): - pass +class TypeEnum(str, Enum): + home = "home" + other = 'other' + work = 'work' +class ElectroicMailAddress(TypedValue): + type: TypeEnum diff --git a/scim_server/schemas/electronic_mail_address_base.py b/scim_server/schemas/electronic_mail_address_base.py deleted file mode 100644 index afcfd59ed..000000000 --- a/scim_server/schemas/electronic_mail_address_base.py +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env python3 - -from .typed_value import TypedValue - - -class ElectroicMailAddressBase(TypedValue): - Home = 'home' - Other = 'other' - Work = 'work' diff --git a/scim_server/schemas/extension_attribute_enterprise_user2.py b/scim_server/schemas/extension_attribute_enterprise_user.py similarity index 73% rename from scim_server/schemas/extension_attribute_enterprise_user2.py rename to scim_server/schemas/extension_attribute_enterprise_user.py index 4e94ee38d..cf99acaf2 100644 --- a/scim_server/schemas/extension_attribute_enterprise_user2.py +++ b/scim_server/schemas/extension_attribute_enterprise_user.py @@ -1,14 +1,17 @@ #!/usr/bin/env python3 -from scim_server.schemas.extension_attribute_enterprise_user_base import ( - ExtensionAttributeEnterpriseUserBase, -) from scim_server.schemas.attribute_names import AttributeNames from scim_server.schemas.manager import Manager +from typing import Optional +from pydantic import BaseModel - -class ExtensionAttributeEnterpriseUser2(ExtensionAttributeEnterpriseUserBase): - manager: Manager +class ExtensionAttributeEnterpriseUser(BaseModel): + employeeNumber: Optional[str] + costCenter: Optional[str] + organization: Optional[str] + division: Optional[str] + department: Optional[str] + manager: Optional[Manager] # @classmethod # def from_dict(cls, d): # obj = super().from_dict(d) diff --git a/scim_server/schemas/extension_attribute_enterprise_user_base.py b/scim_server/schemas/extension_attribute_enterprise_user_base.py deleted file mode 100644 index 6f18b5272..000000000 --- a/scim_server/schemas/extension_attribute_enterprise_user_base.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 -from scim_server.schemas.attribute_names import AttributeNames -from pydantic import BaseModel - - -class ExtensionAttributeEnterpriseUserBase(BaseModel): - cost_center: str - department: str - division: str - employee_number: str - organization: str - - # @property - # def cost_center(self): - # if not hasattr(self, '_cost_center'): - # return None - # return self._cost_center - - # @cost_center.setter - # def cost_center(self, value): - # self._cost_center = value - - # @property - # def department(self): - # if not hasattr(self, '_department'): - # return None - # return self._department - - # @department.setter - # def department(self, value): - # self._department = value - - # @property - # def division(self): - # if not hasattr(self, '_division'): - # return None - # return self._division - - # @division.setter - # def division(self, value): - # self._division = value - - # @property - # def employee_number(self): - # if not hasattr(self, '_employee_number'): - # return None - # return self._employee_number - - # @employee_number.setter - # def employee_number(self, value): - # self._employee_number = value - - # @property - # def organization(self): - # if not hasattr(self, '_organization'): - # return None - # return self._organization - - # @organization.setter - # def organization(self, value): - # self._organization = value - - # @classmethod - # def from_dict(cls, d): - # obj = cls() - # if not d: - # return obj - # buffer = {} - # for item in [ - # AttributeNames.CostCenter, - # AttributeNames.Department, - # AttributeNames.Division, - # AttributeNames.EmployeeNumber, - # AttributeNames.Organization, - # ]: - # if item in d: - # buffer[item] = d.get(item) - # for key, value in buffer.items(): - # setattr(obj, key, value) - # return obj - - # def to_dict(self): - # d = {} - # if self.cost_center: - # d[AttributeNames.CostCenter] = self.cost_center - # if self.department: - # d[AttributeNames.Department] = self.department - # if self.division: - # d[AttributeNames.Division] = self.division - # if self.employee_number: - # d[AttributeNames.EmployeeNumber] = self.employee_number - # if self.organization: - # d[AttributeNames.Organization] = self.organization - # return d diff --git a/scim_server/schemas/feature.py b/scim_server/schemas/feature.py index d3ed92ceb..80e37fa53 100644 --- a/scim_server/schemas/feature.py +++ b/scim_server/schemas/feature.py @@ -1,8 +1,13 @@ #!/usr/bin/env python3 -from scim_server.schemas.feature_base import FeatureBase +from pydantic import BaseModel -class Feature(FeatureBase): - # def __init__(self, supported): - # self.supported = supported - pass +class Feature(BaseModel): + supported: bool + # @property + # def supported(self): + # return self._supported + + # @supported.setter + # def supported(self, value): + # self._supported = value diff --git a/scim_server/schemas/feature_base.py b/scim_server/schemas/feature_base.py deleted file mode 100644 index 1c9b56bf8..000000000 --- a/scim_server/schemas/feature_base.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 -from pydantic import BaseModel - - -class FeatureBase(BaseModel): - supported: bool - # @property - # def supported(self): - # return self._supported - - # @supported.setter - # def supported(self, value): - # self._supported = value diff --git a/scim_server/schemas/filter_feature.py b/scim_server/schemas/filter_feature.py new file mode 100644 index 000000000..5dffa39db --- /dev/null +++ b/scim_server/schemas/filter_feature.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 +from scim_server.schemas.feature import Feature +from typing import Optional + + +class FilterFeature(Feature): + maxResults: int \ No newline at end of file diff --git a/scim_server/schemas/group_base.py b/scim_server/schemas/group_base.py deleted file mode 100644 index f9517739d..000000000 --- a/scim_server/schemas/group_base.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env python3 -from scim_server.schemas.resource import Resource -from scim_server.schemas.attribute_names import AttributeNames -from scim_server.schemas.member import Member -from typing import List - - -class GroupBase(Resource): - display_name: str - members: List[Member] - # @property - # def display_name(self): - # if not hasattr(self, '_display_name'): - # return None - # return self._display_name - - # @display_name.setter - # def display_name(self, value): - # self._display_name = value - - # @property - # def members(self): - # if not hasattr(self, '_members'): - # return None - # return self._members - - # @members.setter - # def members(self, value): - # self._members = value - - # def to_dict(self): - # result = super().to_dict() - # if self.display_name is not None: - # result[AttributeNames.DisplayName] = self.display_name - - # if self.members is not None: - # result[AttributeNames.Members] = [item.to_dict() for item in self.members] - # return result - - # @classmethod - # def from_dict(cls, d): - # obj = super().from_dict(d) - # if AttributeNames.DisplayName in d: - # obj.display_name = d.get(AttributeNames.DisplayName) - # if AttributeNames.Members in d: - # obj.members = [ - # Member.from_dict(item) for item in d.get(AttributeNames.Members) - # ] - # return obj diff --git a/scim_server/schemas/instant_messaging.py b/scim_server/schemas/instant_messaging.py index ae85bc322..49f283d23 100644 --- a/scim_server/schemas/instant_messaging.py +++ b/scim_server/schemas/instant_messaging.py @@ -1,6 +1,16 @@ #!/usr/bin/env python3 -from scim_server.schemas.instant_messaging_base import InstantMessagingBase +from scim_server.schemas.typed_value import TypedValue +from enum import Enum +from typing import Optional +class TypeEnum(str, Enum): + aim = "aim" + gtalk = 'gtalk' + icq = 'icq' + msn = 'msn' + qq = 'qq' + skype = 'skype' + xmpp = 'xmpp' + yahoo = 'yahoo' - -class InstantMessaging(InstantMessagingBase): - pass +class InstantMessaging(TypedValue): + type: TypeEnum diff --git a/scim_server/schemas/instant_messaging_base.py b/scim_server/schemas/instant_messaging_base.py deleted file mode 100644 index 6ffeee24a..000000000 --- a/scim_server/schemas/instant_messaging_base.py +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env python3 -from scim_server.schemas.typed_value import TypedValue - - -class InstantMessagingBase(TypedValue): - AIM = 'aim' - Gtalk = 'gtalk' - Icq = 'icq' - Msn = 'msn' - Qq = 'qq' - Skype = 'skype' - Xmpp = 'xmpp' - Yahoo = 'yahoo' diff --git a/scim_server/schemas/manager.py b/scim_server/schemas/manager.py index 022eb1dd0..1d44fb9aa 100644 --- a/scim_server/schemas/manager.py +++ b/scim_server/schemas/manager.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 from scim_server.schemas.attribute_names import AttributeNames -from pydantic import BaseModel +from pydantic import BaseModel, HttpUrl, Field +from typing import Optional class Manager(BaseModel): - value: str - display_name: str + value: Optional[str] + ref: Optional[HttpUrl] = Field(None, alias="$ref") + displayName: Optional[str] # @classmethod # def from_dict(cls, d): # obj = cls() diff --git a/scim_server/schemas/member.py b/scim_server/schemas/member.py index 8224ea047..6610c9dfd 100644 --- a/scim_server/schemas/member.py +++ b/scim_server/schemas/member.py @@ -1,6 +1,53 @@ #!/usr/bin/env python3 -from scim_server.schemas.member_base import MemberBase +from scim_server.schemas.attribute_names import AttributeNames +from pydantic import BaseModel, HttpUrl, Field +from typing import List, Optional +from enum import Enum +class TypeItem(str, Enum): + User = "User" + Group = "Group" -class Member(MemberBase): - pass +class Member(BaseModel): + + value: Optional[str] + ref: Optional[HttpUrl] = Field(None, alias="$ref") + type: Optional[TypeItem] + + # @property + # def display(self): + # if not hasattr(self, '_display'): + # return None + # return self._display + + # @display.setter + # def display(self, value): + # self._display = value + + # @property + # def value(self): + # if not hasattr(self, '_value'): + # return None + # return self._value + + # @value.setter + # def value(self, value): + # self._value = value + + # def to_dict(self): + # result = {} + # if self.display is not None: + # result[AttributeNames.Display] = self.display + + # if self.value is not None: + # result[AttributeNames.Value] = self.value + # return result + + # @classmethod + # def from_dict(cls, d): + # obj = cls() + # if AttributeNames.Display in d: + # obj.display = d.get(AttributeNames.Display) + # if AttributeNames.Value in d: + # obj.value = d.get(AttributeNames.Value) + # return obj diff --git a/scim_server/schemas/member_base.py b/scim_server/schemas/member_base.py deleted file mode 100644 index 4498475b8..000000000 --- a/scim_server/schemas/member_base.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -from scim_server.schemas.attribute_names import AttributeNames -from pydantic import BaseModel -from typing import List - - -class MemberBase(BaseModel): - - display: str - value: List[str] - - # @property - # def display(self): - # if not hasattr(self, '_display'): - # return None - # return self._display - - # @display.setter - # def display(self, value): - # self._display = value - - # @property - # def value(self): - # if not hasattr(self, '_value'): - # return None - # return self._value - - # @value.setter - # def value(self, value): - # self._value = value - - # def to_dict(self): - # result = {} - # if self.display is not None: - # result[AttributeNames.Display] = self.display - - # if self.value is not None: - # result[AttributeNames.Value] = self.value - # return result - - # @classmethod - # def from_dict(cls, d): - # obj = cls() - # if AttributeNames.Display in d: - # obj.display = d.get(AttributeNames.Display) - # if AttributeNames.Value in d: - # obj.value = d.get(AttributeNames.Value) - # return obj diff --git a/scim_server/schemas/name.py b/scim_server/schemas/name.py index ee87228b3..db00b53d1 100644 --- a/scim_server/schemas/name.py +++ b/scim_server/schemas/name.py @@ -1,14 +1,15 @@ #!/usr/bin/env python3 from scim_server.schemas.attribute_names import AttributeNames from pydantic import BaseModel +from typing import Optional class Name(BaseModel): - formatted: str - familiy_name: str - given_name: str - honorific_prefix: str - honorific_suffix: str + formatted: Optional[str] = None + familiyName: Optional[str] = None + givenName: Optional[str] = None + honorificPrefix: Optional[str] = None + honorificSuffix: Optional[str] = None # @property # def formatted(self): diff --git a/scim_server/schemas/phone_number.py b/scim_server/schemas/phone_number.py index 2c8a3dd2a..d52c40689 100644 --- a/scim_server/schemas/phone_number.py +++ b/scim_server/schemas/phone_number.py @@ -1,6 +1,13 @@ #!/usr/bin/env python3 -from scim_server.schemas.phone_number_base import PhoneNumberBase +from scim_server.schemas.typed_value import TypedValue +from enum import Enum +class TypeEnum(str, Enum): + work = 'work' + home = 'home' + mobile = 'mobile' + fax = 'fax' + pager = 'pager' + other = 'other' - -class PhoneNumber(PhoneNumberBase): - pass +class PhoneNumber(TypedValue): + type: TypeEnum diff --git a/scim_server/schemas/phone_number_base.py b/scim_server/schemas/phone_number_base.py deleted file mode 100644 index abe0a467d..000000000 --- a/scim_server/schemas/phone_number_base.py +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env python3 -from scim_server.schemas.typed_value import TypedValue - - -class PhoneNumberBase(TypedValue): - Fax = 'fax' - Home = 'home' - Mobile = 'Mobile' - Other = 'other' - Pager = 'pager' - Work = 'work' diff --git a/scim_server/schemas/photo.py b/scim_server/schemas/photo.py new file mode 100644 index 000000000..9af2831fc --- /dev/null +++ b/scim_server/schemas/photo.py @@ -0,0 +1,10 @@ +#!/usr/bin/env python3 +from scim_server.schemas.typed_value import TypedValue +from enum import Enum + +class TypeEnum(str, Enum): + photo = 'photo' + thumbnail = 'thumbnail' + +class Photo(TypedValue): + type: TypeEnum diff --git a/scim_server/schemas/resource.py b/scim_server/schemas/resource.py index cdab9e08a..bebfcc622 100644 --- a/scim_server/schemas/resource.py +++ b/scim_server/schemas/resource.py @@ -1,14 +1,14 @@ #!/usr/bin/env python3 from scim_server.schemas.schematized import Schematized from scim_server.schemas.attribute_names import AttributeNames - +from typing import Optional class Resource(Schematized): - external_identifier: str - identifier = str + id:Optional[str] = None + externalId: Optional[str] = None - def __init__(self): - super().__init__() + # def __init__(self): + # super().__init__() # @property # def external_identifier(self): diff --git a/scim_server/schemas/role.py b/scim_server/schemas/role.py index a6f93ceaf..cc3e5699b 100644 --- a/scim_server/schemas/role.py +++ b/scim_server/schemas/role.py @@ -1,6 +1,44 @@ #!/usr/bin/env python3 -from scim_server.schemas.role_base import RoleBase +from scim_server.schemas.typed_value import TypedValue +from scim_server.schemas.attribute_names import AttributeNames +from typing import Optional +class Role(TypedValue): + display: Optional[str] + # @property + # def display(self): + # if not hasattr(self, '_display'): + # return None + # return self._display -class Role(RoleBase): - pass + # @display.setter + # def display(self, value): + # self._display = value + + # @property + # def value(self): + # if not hasattr(self, '_value'): + # return None + # return self._value + + # @value.setter + # def value(self, value): + # self._value = value + + # @classmethod + # def from_dict(cls, d): + # obj = super().from_dict(d) + # if AttributeNames.Value in d: + # obj.value = d.get(AttributeNames.Value) + # if AttributeNames.Display in d: + # obj.display = d.get(AttributeNames.Display) + + # return obj + + # def to_dict(self): + # d = super().to_dict() + # if self.value is not None: + # d[AttributeNames.Value] = self.value + # if self.display is not None: + # d[AttributeNames.Display] = self.display + # return d diff --git a/scim_server/schemas/role_base.py b/scim_server/schemas/role_base.py deleted file mode 100644 index 847d0ee76..000000000 --- a/scim_server/schemas/role_base.py +++ /dev/null @@ -1,45 +0,0 @@ -#!/usr/bin/env python3 -from scim_server.schemas.typed_item import TypedItem -from scim_server.schemas.attribute_names import AttributeNames - - -class RoleBase(TypedItem): - display: str - value: str - # @property - # def display(self): - # if not hasattr(self, '_display'): - # return None - # return self._display - - # @display.setter - # def display(self, value): - # self._display = value - - # @property - # def value(self): - # if not hasattr(self, '_value'): - # return None - # return self._value - - # @value.setter - # def value(self, value): - # self._value = value - - # @classmethod - # def from_dict(cls, d): - # obj = super().from_dict(d) - # if AttributeNames.Value in d: - # obj.value = d.get(AttributeNames.Value) - # if AttributeNames.Display in d: - # obj.display = d.get(AttributeNames.Display) - - # return obj - - # def to_dict(self): - # d = super().to_dict() - # if self.value is not None: - # d[AttributeNames.Value] = self.value - # if self.display is not None: - # d[AttributeNames.Display] = self.display - # return d diff --git a/scim_server/schemas/schematized.py b/scim_server/schemas/schematized.py index 1efbe48aa..3632788bf 100644 --- a/scim_server/schemas/schematized.py +++ b/scim_server/schemas/schematized.py @@ -7,14 +7,14 @@ class Schematized(BaseModel): - schemas: List[str] + schemas: List[str] = [] - def __init__(self): - self._schemas = [] + # def __init__(self): + # self._schemas = [] def add_schema(self, schema_identifier): - if schema_identifier not in self._schemas: - self._schemas.append(schema_identifier) + if schema_identifier not in self.schemas: + self.schemas.append(schema_identifier) def is_schema(self, schema): if not schema: diff --git a/scim_server/schemas/service_configuration_base.py b/scim_server/schemas/service_configuration_base.py index 8190e5e50..014c022ed 100644 --- a/scim_server/schemas/service_configuration_base.py +++ b/scim_server/schemas/service_configuration_base.py @@ -1,76 +1,87 @@ #!/usr/bin/env python3 from scim_server.schemas.schematized import Schematized +from scim_server.schemas.bulk_requests_feature import BulkRequestsFeature +from scim_server.schemas.filter_feature import FilterFeature from scim_server.exceptions import ArgumentNullException, ArgumentException - +from scim_server.schemas.feature import Feature +from scim_server.schemas.authentication_scheme import AuthenticationScheme +from typing import List, Optional +from pydantic import HttpUrl class ServiceConfigurationBase(Schematized): - def __init__(self): - super().__init__() - self._authentication_schemes = [] - - def add_authentication_scheme(self, authentication_scheme): - if not authentication_scheme: - raise ArgumentNullException('authentication_scheme') - if not authentication_scheme.name: - raise ArgumentException('Unnamed Authentication Scheme') - self._authentication_schemes.append(authentication_scheme) - - @property - def authentication_schemes(self): - return self._authentication_schemes - - @property - def bulk_requests(self): - return self._bulk_requests - - @bulk_requests.setter - def bulk_requests(self, value): - self._bulk_requests = value - - @property - def documentation_resource(self): - return self._documentation_resource - - @documentation_resource.setter - def documentation_resource(self, value): - self._documentation_resource = value - - @property - def entity_tags(self): - return self._entity_tags - - @entity_tags.setter - def entity_tags(self, value): - self._entity_tags = value - - @property - def filtering(self): - return self._filtering - - @filtering.setter - def filtering(self, value): - self._filtering = value - - @property - def password_change(self): - return self._password_change - - @password_change.setter - def password_change(self, value): - self._password_change = value - - @property - def patching(self): - return self._patching - - @patching.setter - def patching(self, value): - self._patching = value - - @property - def sorting(self): - return self._sorting - - @sorting.setter - def sorting(self, value): - self._sorting = value + authenticationSchemes: List[AuthenticationScheme] + # def __init__(self): + # super().__init__() + # self._authentication_schemes = [] + documentation_resource: Optional[HttpUrl] + patch: Feature + bulk: BulkRequestsFeature + filter: FilterFeature + changePassword: Feature + sort: Feature + etag: Feature + + def add_authentication_scheme(self, authentication_scheme:AuthenticationScheme) -> None: + if not self.authenticationSchemes: + self.authenticationSchemes = [] + self.authenticationSchemes.append(authentication_scheme) + + # @property + # def authentication_schemes(self): + # return self._authentication_schemes + + # @property + # def bulk_requests(self): + # return self._bulk_requests + + # @bulk_requests.setter + # def bulk_requests(self, value): + # self._bulk_requests = value + + # @property + # def documentation_resource(self): + # return self._documentation_resource + + # @documentation_resource.setter + # def documentation_resource(self, value): + # self._documentation_resource = value + + # @property + # def entity_tags(self): + # return self._entity_tags + + # @entity_tags.setter + # def entity_tags(self, value): + # self._entity_tags = value + + # @property + # def filtering(self): + # return self._filtering + + # @filtering.setter + # def filtering(self, value): + # self._filtering = value + + # @property + # def password_change(self): + # return self._password_change + + # @password_change.setter + # def password_change(self, value): + # self._password_change = value + + # @property + # def patching(self): + # return self._patching + + # @patching.setter + # def patching(self, value): + # self._patching = value + + # @property + # def sorting(self): + # return self._sorting + + # @sorting.setter + # def sorting(self, value): + # self._sorting = value diff --git a/scim_server/schemas/typed_item.py b/scim_server/schemas/typed_item.py index d321603ce..12711cde6 100644 --- a/scim_server/schemas/typed_item.py +++ b/scim_server/schemas/typed_item.py @@ -1,11 +1,11 @@ #!/usr/bin/env python3 from scim_server.schemas.attribute_names import AttributeNames from pydantic import BaseModel - +from typing import Optional class TypedItem(BaseModel): - item_type: str - primary: bool + type: Optional[str] + primary: Optional[bool] # @property # def item_type(self): # if not hasattr(self, '_item_type'): diff --git a/scim_server/schemas/typed_value.py b/scim_server/schemas/typed_value.py index 29268239f..3f34edb0b 100644 --- a/scim_server/schemas/typed_value.py +++ b/scim_server/schemas/typed_value.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 from scim_server.schemas.typed_item import TypedItem from scim_server.schemas.attribute_names import AttributeNames - +from typing import Optional class TypedValue(TypedItem): - value: str + value: Optional[str] # @property # def value(self): # if not hasattr(self, '_value'): diff --git a/scim_server/schemas/user_base.py b/scim_server/schemas/user_base.py deleted file mode 100644 index 289cfd4f1..000000000 --- a/scim_server/schemas/user_base.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env python3 -from scim_server.schemas.resource import Resource -from scim_server.schemas.attribute_names import AttributeNames - - -class UserBase(Resource): - # def __init__(self): - # super().__init__() - username: str - # @property - # def user_name(self): - # if not hasattr(self, '_user_name'): - # return None - # return self._user_name - - # @user_name.setter - # def user_name(self, value): - # self._user_name = value - - # def to_dict(self): - # result = super().to_dict() - # if self.user_name is not None: - # result[AttributeNames.UserName] = self.user_name - # return result - - # @classmethod - # def from_dict(cls, d): - # obj = super().from_dict(d) - # if AttributeNames.UserName in d: - # obj.user_name = d.get(AttributeNames.UserName) - # return obj diff --git a/scim_server/schemas/user_groups.py b/scim_server/schemas/user_groups.py new file mode 100644 index 000000000..2d294c75e --- /dev/null +++ b/scim_server/schemas/user_groups.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 +from scim_server.schemas.typed_value import TypedValue +from enum import Enum +from typing import Optional +from pydantic import Field + +class TypeEnum(str, Enum): + direct = 'direct' + indirect = 'indirect' + +class UserGroups(TypedValue): + type: Optional[TypeEnum] + ref: Optional[str] = Field(None, alias="$ref") + display: Optional[str] \ No newline at end of file diff --git a/scim_server/service/provider_base.py b/scim_server/service/provider_base.py index 4cb4f58a6..f19fa265b 100644 --- a/scim_server/service/provider_base.py +++ b/scim_server/service/provider_base.py @@ -6,11 +6,17 @@ class ProviderBase: - BulkFeatureSupport = BulkRequestsFeature.create_unsupported_feature() TypeSchema = [] - ServiceConfiguration = Core2ServiceConfiguration( - BulkFeatureSupport, False, True, False, True, False - ) + config_data = { + "authenticationSchemes": [{"type":"oauth2", "name": "OAuth2", "description": "OAuth2"}], + "bulk": {"supported": False, "maxOperations":0, "maxPayloadSize":0}, + "etag": {"supported": False}, + "filter": {"supported": True, "maxResults": 200}, + "changePassword": {"supported": False}, + "patch": {"supported": True}, + "sort": {"supported": False}, + } + ServiceConfiguration = Core2ServiceConfiguration(**config_data) Types = [] @property diff --git a/scim_server/urls.py b/scim_server/urls.py index b17e2f2ae..88dcad378 100644 --- a/scim_server/urls.py +++ b/scim_server/urls.py @@ -5,8 +5,8 @@ except ImportError: from django.conf.urls import url as re_path -from scim_server.mssql_provider.users_view import UsersView as MssqlUsersView -from scim_server.mssql_provider.groups_view import GroupsView as MssqlGroupsView +# from scim_server.mssql_provider.users_view import UsersView as MssqlUsersView +# from scim_server.mssql_provider.groups_view import GroupsView as MssqlGroupsView from scim_server.in_memory_provider.users_view import InMemoryUsersView from scim_server.in_memory_provider.groups_view import InMemoryGroupsView @@ -18,13 +18,13 @@ # re_path(r'^.search$', views.SearchView.as_view(implemented=False), name='search'), # re_path(r'^Users/.search$', views.UserSearchView.as_view(), name='users-search'), re_path( - r'^memory/Users(?:/(?P[^/]+))?$', + r'^memory/scim/Users(?:/(?P[^/]+))?$', InMemoryUsersView.as_view(), name='memory_users', ), # re_path(r'^Groups/.search$', views.GroupSearchView.as_view(), name='groups-search'), re_path( - r'^memory/Groups(?:/(?P[^/]+))?$', + r'^memory/scim/Groups(?:/(?P[^/]+))?$', InMemoryGroupsView.as_view(), name='memory_groups', ), @@ -43,14 +43,14 @@ # r'^Schemas(?:/(?P[^/]+))?$', views.SchemasView.as_view(), name='schemas' # ), # re_path(r'^Bulk$', views.SCIMView.as_view(implemented=False), name='bulk'), - re_path( - r'^mssql/Users(?:/(?P[^/]+))?$', - MssqlUsersView.as_view(), - name='mssql_users', - ), - re_path( - r'^mssql/Groups(?:/(?P[^/]+))?$', - MssqlGroupsView.as_view(), - name='mssql_groups', - ), + # re_path( + # r'^mssql/Users(?:/(?P[^/]+))?$', + # MssqlUsersView.as_view(), + # name='mssql_users', + # ), + # re_path( + # r'^mssql/Groups(?:/(?P[^/]+))?$', + # MssqlGroupsView.as_view(), + # name='mssql_groups', + # ), ] diff --git a/scim_server/utils.py b/scim_server/utils.py index 813d3a6d6..16c6ca438 100644 --- a/scim_server/utils.py +++ b/scim_server/utils.py @@ -9,8 +9,8 @@ from scim_server.schemas.phone_number import PhoneNumber from scim_server.schemas.role import Role from scim_server.schemas.schema_identifiers import SchemaIdentifiers -from scim_server.schemas.extension_attribute_enterprise_user2 import ( - ExtensionAttributeEnterpriseUser2, +from scim_server.schemas.extension_attribute_enterprise_user import ( + ExtensionAttributeEnterpriseUser, ) from scim_server.schemas.manager import Manager @@ -23,7 +23,7 @@ def compose_enterprise_extension(user, scim_path, value): if user.enterprise_extension: extension = user.enterprise_extension else: - extension = ExtensionAttributeEnterpriseUser2() + extension = ExtensionAttributeEnterpriseUser() user.enterprise_extension = extension if scim_path.attribute_path == 'manager': compose_manager(user, scim_path, value) diff --git a/scim_server/views/view_template.py b/scim_server/views/view_template.py index 869dac82d..dc99b75d7 100644 --- a/scim_server/views/view_template.py +++ b/scim_server/views/view_template.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 +from cmath import log import json import logging from django.views.generic import View @@ -55,7 +56,7 @@ def get(self, request, *args, **kwargs): resource_query.pagination_parameters, correlation_identifier, ) - d = result.to_dict() + d = result.dict() return JsonResponse(d) def delete(self, request, *args, **kwargs): @@ -70,13 +71,13 @@ def post(self, request, *args, **kwargs): body = request.body try: d = json.loads(body) - resource = self.model_cls.from_dict(d) + resource = self.model_cls(**d) except Exception as e: + logger.exception(e) raise BadRequestException() correlation_identifier = try_get_request_identifier() result = self.adapter_provider.create(request, resource, correlation_identifier) - d = result.to_dict() return JsonResponse(d, status=201) def put(self, request, *args, **kwargs): From 903b97fe56b8f8638cdfd419b6672569bc2045b1 Mon Sep 17 00:00:00 2001 From: fanhe Date: Fri, 29 Apr 2022 22:16:29 +0800 Subject: [PATCH 2/4] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E5=92=8C=E7=BB=84=E7=BB=87=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/v1/views/__init__.py | 2 +- api/v1/views/scim_sync.py | 190 ++++++++++++--- arkid/core/extension/__init__.py | 13 +- arkid/core/extension/scim_sync.py | 196 ++++++++++++++++ .../migrations/0003_auto_20220429_0847.py | 23 ++ arkid/core/models.py | 2 + arkid/core/tasks.py | 30 +++ .../0004_tenantextensionconfig_type.py | 18 ++ arkid/extension/models.py | 1 + arkid/extension/utils.py | 8 +- arkid/settings.py | 16 +- .../com_longgui_scim_sync_arkid/__init__.py | 219 ++++++++++++++++++ scim_server/protocol/query_response_base.py | 19 +- scim_server/schemas/core2_group_base.py | 4 +- scim_server/schemas/core2_user_base.py | 6 +- scim_server/schemas/user_groups.py | 6 +- scim_server/service/provider_base.py | 114 +++++++-- scim_server/urls.py | 4 +- scim_server/utils.py | 46 ++-- tasks/celery.py | 24 ++ 20 files changed, 833 insertions(+), 108 deletions(-) create mode 100644 arkid/core/extension/scim_sync.py create mode 100644 arkid/core/migrations/0003_auto_20220429_0847.py create mode 100644 arkid/core/tasks.py create mode 100644 arkid/extension/migrations/0004_tenantextensionconfig_type.py create mode 100644 extension_root/com_longgui_scim_sync_arkid/__init__.py create mode 100644 tasks/celery.py diff --git a/api/v1/views/__init__.py b/api/v1/views/__init__.py index afef54b14..946198897 100644 --- a/api/v1/views/__init__.py +++ b/api/v1/views/__init__.py @@ -15,5 +15,5 @@ third_auth, permission_sync, scim_sync - send_sms, + # send_sms, ) diff --git a/api/v1/views/scim_sync.py b/api/v1/views/scim_sync.py index b4a3141cb..136cbf58c 100644 --- a/api/v1/views/scim_sync.py +++ b/api/v1/views/scim_sync.py @@ -1,39 +1,167 @@ from arkid.core.api import api +import json +from ninja import Schema +from ninja import ModelSchema from arkid.core.translation import gettext_default as _ +from arkid.core.extension.scim_sync import ScimSyncExtension +from arkid.extension.utils import import_extension +from arkid.extension.models import TenantExtensionConfig, Extension +from django_celery_beat.models import PeriodicTask, CrontabSchedule +from arkid.common.logger import logger +from typing import List +from ninja.pagination import paginate +from django.shortcuts import get_object_or_404 +from uuid import UUID +from arkid.core.error import ErrorCode -@api.get("/tenant/{tenant_id}/scim_syncs/", tags=[_("用户数据同步配置")]) +class ScimSyncListSchemaOut(ModelSchema): + class Config: + model = TenantExtensionConfig + model_fields = ['id', 'name', 'type', 'config'] + + mode: str + + @staticmethod + def resolve_mode(obj): + return obj.config.get("mode") + + +def update_or_create_periodic_task(extension_config): + crontab = extension_config.config.get('crontab') + if crontab: + try: + crontab = crontab.split(' ') + crontab.extend(['*'] * (5 - len(crontab))) + + # create CrontabSchedule + schedule, _ = CrontabSchedule.objects.get_or_create( + minute=crontab[0], + hour=crontab[1], + day_of_week=crontab[2], + day_of_month=crontab[3], + month_of_year=crontab[4], + ) + + # create PeriodicTask + PeriodicTask.objects.update_or_create( + name=extension_config.id, + defaults={ + 'crontab': schedule, + 'task': 'arkid.core.tasks.sync', + 'args': json.dumps([extension_config.id.hex]), + 'kwargs': json.dumps(extension_config.config), + }, + ) + except Exception as e: + logger.exception('add celery task failed %s' % e) + + +def delete_periodic_task(extension_config): + try: + # fake delete triggers post_save signal + PeriodicTask.objects.filter(name=extension_config.id).delete() + except Exception as e: + logger.exception('delete celery task failed %s' % e) + + +ScimSyncConfigSchemaIn = ScimSyncExtension.create_composite_config_schema( + 'ScimSyncConfigSchemaIn' +) + + +class ScimSyncConfigSchemaOut(Schema): + id: UUID + type: str + name: str + config: dict + + +ScimSyncSchemaOut = ScimSyncExtension.create_composite_config_schema( + 'ScimSyncSchemaOut' +) + + +@api.get( + "/tenant/{tenant_id}/scim_syncs/", + response=List[ScimSyncListSchemaOut], + tags=[_("用户数据同步配置")], + auth=None, +) +@paginate def get_scim_syncs(request, tenant_id: str): - """ 用户数据同步配置列表,TODO - """ - return [] + """用户数据同步配置列表,TODO""" + configs = TenantExtensionConfig.valid_objects.filter( + tenant_id=tenant_id, type="scim_sync" + ) + return configs + -@api.get("/tenant/{tenant_id}/scim_syncs/{id}/", tags=[_("用户数据同步配置")]) +@api.get( + "/tenant/{tenant_id}/scim_syncs/{id}/", + response=ScimSyncConfigSchemaOut, + tags=[_("用户数据同步配置")], + auth=None, +) def get_scim_sync(request, tenant_id: str, id: str): - """ 获取用户数据同步配置,TODO - """ - return {} - -@api.post("/tenant/{tenant_id}/scim_syncs/", tags=[_("用户数据同步配置")]) -def create_scim_sync(request, tenant_id: str): - """ 创建用户数据同步配置,TODO - """ - return {} - -@api.put("/tenant/{tenant_id}/scim_syncs/{id}/", tags=[_("用户数据同步配置")]) -def update_scim_sync(request, tenant_id: str, id: str): - """ 编辑用户数据同步配置,TODO - """ - return {} - -@api.delete("/tenant/{tenant_id}/scim_syncs/{id}/", tags=[_("用户数据同步配置")]) + """获取用户数据同步配置,TODO""" + config = get_object_or_404(TenantExtensionConfig, id=id, tenant=request.tenant) + return config + + +@api.post( + "/tenant/{tenant_id}/scim_syncs/", + tags=[_("用户数据同步配置")], + response=ScimSyncConfigSchemaOut, + auth=None, +) +def create_scim_sync(request, tenant_id: str, data: ScimSyncConfigSchemaIn): + """创建用户数据同步配置,TODO""" + + tenant = request.tenant + package = data.package + name = data.name + type = data.type + config = data.config + extension = Extension.active_objects.get(package=package) + extension = import_extension(extension.ext_dir) + extension_config = extension.create_tenant_config( + tenant, config.dict(), name=name, type=type + ) + if config.mode == "client": + update_or_create_periodic_task(extension_config) + return extension_config + + +@api.put( + "/tenant/{tenant_id}/scim_syncs/{id}/", + tags=[_("用户数据同步配置")], + response=ScimSyncConfigSchemaOut, + auth=None, +) +def update_scim_sync(request, tenant_id: str, id: str, data: ScimSyncConfigSchemaIn): + """编辑用户数据同步配置,TODO""" + extension_config = get_object_or_404( + TenantExtensionConfig, id=id, tenant=request.tenant + ) + extension_config.package = data.package + extension_config.name = data.name + extension_config.type = data.type + extension_config.config = data.config + + if data.config.mode == "client": + update_or_create_periodic_task(extension_config) + + return extension_config + + +@api.delete("/tenant/{tenant_id}/scim_syncs/{id}/", tags=[_("用户数据同步配置")], auth=None) def delete_scim_sync(request, tenant_id: str, id: str): - """ 删除用户数据同步配置,TODO - """ - return {} - -@api.get("/tenant/{tenant_id}/scim_syncs/{id}/sync/", tags=[_("用户数据同步配置")]) -def scim_sync(request, tenant_id: str, id: str): - """ 同步权限数据,TODO - """ - return {} + """删除用户数据同步配置,TODO""" + extension_config = get_object_or_404( + TenantExtensionConfig, id=id, tenant=request.tenant + ) + extension_config.delete() + if extension_config.config["mode"] == "client": + delete_periodic_task(extension_config) + return {'error': ErrorCode.OK.value} diff --git a/arkid/core/extension/__init__.py b/arkid/core/extension/__init__.py index 5e136e533..1a4ef9ef3 100644 --- a/arkid/core/extension/__init__.py +++ b/arkid/core/extension/__init__.py @@ -435,16 +435,15 @@ def get_tenant_configs(self, tenant): def get_config_by_id(self, id): return TenantExtensionConfig.valid_objects.get(id=id) - def update_tenant_config(self, id, config, name): - return TenantExtensionConfig.valid_objects.filter(id=id).update(config=config, name=name) + def update_tenant_config(self, id, config, name, type): + return TenantExtensionConfig.valid_objects.filter(id=id).update(config=config, name=name,type=type) - def create_tenant_config(self, tenant, config, name): + def create_tenant_config(self, tenant, config, name, type): ext = ExtensionModel.valid_objects.filter(package=self.package).first() - return TenantExtensionConfig.objects.create(tenant=tenant, extension=ext, config=config, name=name) + return TenantExtensionConfig.objects.create(tenant=tenant, extension=ext, config=config, name=name, type=type) - def delete_tenant_config(self, tenant, config): - ext = ExtensionModel.valid_objects.filter(package=self.package).first() - return TenantExtensionConfig.objects.delete(tenant=tenant, extension=ext, config=config) + def delete_tenant_config(self, id): + return TenantExtensionConfig.objects.delete(id=id) ################################################################################ diff --git a/arkid/core/extension/scim_sync.py b/arkid/core/extension/scim_sync.py new file mode 100644 index 000000000..3a7af0c08 --- /dev/null +++ b/arkid/core/extension/scim_sync.py @@ -0,0 +1,196 @@ +import requests +from types import SimpleNamespace +from typing import Literal, Union +from ninja import Schema +from pydantic import Field +from abc import abstractmethod +from arkid.core.extension import Extension +from arkid.core.translation import gettext_default as _ +from arkid.core import event as core_event +from arkid.extension.models import TenantExtensionConfig +from arkid.core.extension import RootSchema, create_extension_schema +from pydantic import UUID4 +from celery import shared_task +from arkid.common.logger import logger +from django.urls import reverse +from arkid.config import get_app_config +from scim_server.urls import urlpatterns as scim_server_urls +from django.urls import re_path +from scim_server.views.users_view import UsersViewTemplate +from scim_server.views.groups_view import GroupsViewTemplate +from scim_server.service.provider_base import ProviderBase +from scim_server.exceptions import NotImplementedException + + +class ScimSyncExtension(Extension, ProviderBase): + TYPE = "scim_sync" + + composite_schema_map = {} + created_composite_schema_list = [] + composite_key = 'type' + composite_model = TenantExtensionConfig + + @property + def type(self): + return ScimSyncExtension.TYPE + + def load(self): + class UsersView(UsersViewTemplate): + @property + def provider(this): + return self + + class GroupsView(GroupsViewTemplate): + @property + def provider(this): + return self + + scim_server_urls = [ + re_path( + rf'^scim/{self.name}/(?P[\w-]+)/Users(?:/(?P[^/]+))?$', + UsersView.as_view(), + name=f'{self.name}_scim_users', + ), + # re_path(r'^Groups/.search$', views.GroupSearchView.as_view(), name='groups-search'), + re_path( + rf'^scim/{self.name}/(?P[\w-]+)/Groups(?:/(?P[^/]+))?$', + GroupsView.as_view(), + name=f'{self.name}_scim_groups', + ), + ] + self.register_routers(scim_server_urls, True) + super().load() + + def register_scim_sync_schema(self, sync_type, client_schema, server_schema): + schema = create_extension_schema( + self.package, + fields=[ + ( + "__root__", + Union[(client_schema, server_schema)], + Field(discriminator="mode"), + ) + ], + base_schema=RootSchema, + ) + self.register_config_schema(schema, self.package + '_' + sync_type) + self.register_composite_config_schema(schema, sync_type, exclude=['extension']) + + def sync(self, config): + logger.info( + f"============= Sync Start With Config: {config}/{config.config} ================" + ) + groups, users = self.get_groups_users(config) + if not groups or not users: + return + self.sync_groups(groups, config) + self.sync_users(users, config) + + def get_data(self, url): + logger.info(f"Getting data from {url}") + r = requests.get(url) + if r.status_code == 200: + return r.json() + return {} + + def get_groups_users(self, config): + sync_server_id = config.config["sync_server_id"] + server_config = TenantExtensionConfig.active_objects.filter( + id=sync_server_id + ).first() + if not server_config: + logger.error(f"No scim sync server config found: {sync_server_id}") + return None, None + group_url = server_config.config["group_url"] + user_url = server_config.config["user_url"] + groups = self.get_data(group_url).get("Resources") + users = self.get_data(user_url).get("Resources") + return groups, users + + @abstractmethod + def sync_groups(self, groups, config): + pass + + @abstractmethod + def sync_users(self, users, config): + pass + + def get_current_config(self, event): + config_id = event.request.POST.get('config_id') + return self.get_config_by_id(config_id) + + def create_tenant_config(self, tenant, config, name, type): + config_created = super().create_tenant_config( + tenant, config, name=name, type=type + ) + if config["mode"] == "server": + server_host = get_app_config().get_host() + package = self.package.replace('.', '_') + user_url = server_host + reverse( + f'{package}:{self.name}_scim_users', args=[tenant.id, config_created.id] + ) + group_url = server_host + reverse( + f'{package}:{self.name}_scim_groups', + args=[tenant.id, config_created.id], + ) + config["group_url"] = group_url + config["user_url"] = user_url + config_created.config = config + config_created.save() + return config_created + + def create_user(self, request, resource, correlation_identifier): + raise NotImplementedException() + + def create_group(self, request, resource, correlation_identifier): + raise NotImplementedException() + + def delete_user(self, request, resource_identifier, correlation_identifier): + raise NotImplementedException() + + def delete_group(self, request, resource_identifier, correlation_identifier): + raise NotImplementedException() + + def replace_user(self, request, resource, correlation_identifier): + raise NotImplementedException() + + def replace_group(self, request, resource, correlation_identifier): + raise NotImplementedException() + + def retrieve_user(self, request, parameters, correlation_identifier): + raise NotImplementedException() + + def retrieve_group(self, request, parameters, correlation_identifier): + raise NotImplementedException() + + def update_user(self, request, patch, correlation_identifier): + raise NotImplementedException() + + def update_group(self, request, patch, correlation_identifier): + raise NotImplementedException() + + def query_users(self, request, parameters, correlation_identifier): + pass + + def query_groups(self, request, parameters, correlation_identifier): + pass + + +class BaseScimSyncClientSchema(Schema): + # name: str = Field(default='', title=_('Name', '配置名称')) + crontab: str = Field(default='0 1 * * *', title=_('Crontab', '定时运行时间')) + max_retries: int = Field(default=3, title=_('Max Retries', '重试次数')) + retry_delay: int = Field(default=60, title=_('Retry Delay', '重试间隔(单位秒)')) + sync_server_name: str = Field(default="", title=_('Sync Server Name', '同步服务的名字')) + sync_server_id: str = Field( + default="", title=_('Sync Server ID', '同步服务的ID'), hidden=True + ) + attr_map: dict = Field(default={}, title=_('Attribute Map', '同步映射关系')) + mode: Literal["client"] + + +class BaseScimSyncServerSchema(Schema): + # name: str = Field(title=_('配置名称')) + mode: Literal["server"] + user_url: str = Field(default="", title=_('User Url', '获取用户URL')) + group_url: str = Field(default="", title=_('Group Url', '获取组URL')) diff --git a/arkid/core/migrations/0003_auto_20220429_0847.py b/arkid/core/migrations/0003_auto_20220429_0847.py new file mode 100644 index 000000000..cc641d25d --- /dev/null +++ b/arkid/core/migrations/0003_auto_20220429_0847.py @@ -0,0 +1,23 @@ +# Generated by Django 3.2.13 on 2022-04-29 08:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0002_initial'), + ] + + operations = [ + migrations.AddField( + model_name='user', + name='scim_external_id', + field=models.CharField(blank=True, max_length=128, null=True), + ), + migrations.AddField( + model_name='usergroup', + name='scim_external_id', + field=models.CharField(blank=True, max_length=128, null=True), + ), + ] diff --git a/arkid/core/models.py b/arkid/core/models.py index f33b51b5a..bd158aaef 100644 --- a/arkid/core/models.py +++ b/arkid/core/models.py @@ -48,6 +48,7 @@ class Meta(object): tenant = models.ForeignKey( 'Tenant', blank=False, on_delete=models.PROTECT ) + scim_external_id = models.CharField(max_length=128, blank=True, null=True) # tenants = models.ManyToManyField( # 'Tenant', @@ -82,6 +83,7 @@ class Meta(object): verbose_name=_('User List','用户列表') ) + scim_external_id = models.CharField(max_length=128, blank=True, null=True) def __str__(self) -> str: return f'{self.name}' diff --git a/arkid/core/tasks.py b/arkid/core/tasks.py new file mode 100644 index 000000000..514873edd --- /dev/null +++ b/arkid/core/tasks.py @@ -0,0 +1,30 @@ +from arkid.extension.utils import import_extension +from arkid.extension.models import TenantExtensionConfig, Extension +from types import SimpleNamespace +from celery import shared_task +from arkid.common.logger import logger +import importlib + + +@shared_task(bind=True) +def sync(self, config_id, *args, **kwargs): + try: + logger.info("=== arkid.core.tasks.sync start...===") + logger.info(f"config_id: {config_id}") + logger.info(f"kwargs: {kwargs}") + extension_config = TenantExtensionConfig.active_objects.get(id=config_id) + extension = extension_config.extension + ext_dir = extension.ext_dir + logger.info(f"Importing {ext_dir}") + ext_name = str(ext_dir).replace('/','.') + ext = importlib.import_module(ext_name) + if ext and hasattr(ext, 'extension'): + ext.extension.sync(extension_config) + logger.info("=== arkid.core.tasks.sync end...===") + else: + logger.error(f'{ext_name} import fail') + return None + except Exception as exc: + max_retries = kwargs.get('max_retries', 3) + countdown = kwargs.get('retry_delay', 5 * 60) + raise self.retry(exc=exc, max_retries=max_retries, countdown=countdown) diff --git a/arkid/extension/migrations/0004_tenantextensionconfig_type.py b/arkid/extension/migrations/0004_tenantextensionconfig_type.py new file mode 100644 index 000000000..5fc6b5e8f --- /dev/null +++ b/arkid/extension/migrations/0004_tenantextensionconfig_type.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.4 on 2022-04-28 14:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('extension', '0003_alter_tenantextension_options_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='tenantextensionconfig', + name='type', + field=models.CharField(default='', max_length=128, verbose_name='类型'), + ), + ] diff --git a/arkid/extension/models.py b/arkid/extension/models.py index cc0856725..7c84d79fe 100644 --- a/arkid/extension/models.py +++ b/arkid/extension/models.py @@ -44,3 +44,4 @@ class Meta(object): extension = models.ForeignKey('Extension', blank=False, on_delete=models.PROTECT, verbose_name=_('插件')) config = models.JSONField(blank=True, default=dict, verbose_name=_('Runtime Config','运行时配置')) name = models.CharField(max_length=128, default='', verbose_name=_('名称')) + type = models.CharField(max_length=128, default='', verbose_name=_('类型')) diff --git a/arkid/extension/utils.py b/arkid/extension/utils.py index e1c38d9cb..f8f6318ed 100644 --- a/arkid/extension/utils.py +++ b/arkid/extension/utils.py @@ -91,12 +91,14 @@ def delete_extension_folder(extension) -> None: def import_extension(ext_dir: str) -> any: + logger.info(f"Importing {ext_dir}") ext_name = str(ext_dir).replace('/','.') - from pip._internal import main - requirements = str(ext_dir)+'/'+'requirements.txt' - main(['install', '-r', requirements]) + # from pip._internal import main + # requirements = str(ext_dir)+'/'+'requirements.txt' + # main(['install', '-r', requirements]) ext = importlib.import_module(ext_name) + logger.info(f"Imported {ext}") if ext and hasattr(ext, 'extension'): ext.extension.ext_dir = ext_dir logger.info(f'{ext_name} import success') diff --git a/arkid/settings.py b/arkid/settings.py index a344ba9eb..404e86fe1 100644 --- a/arkid/settings.py +++ b/arkid/settings.py @@ -41,6 +41,7 @@ 'oauth2_provider', 'arkid.core', 'arkid.extension', + 'django_celery_beat', ] MIDDLEWARE = [ @@ -150,4 +151,17 @@ LOCALE_PATHS = [ # '/home/guancy/longgui/arkid/extension_root/com_longgui_international_en_us/locale' -] \ No newline at end of file +] + +# Celery settings +CELERY_BROKER = 'redis://localhost:6379' +CELERY_BROKER_URL = 'redis://localhost:6379' +CELERY_TIMEZONE = 'Asia/Shanghai' +CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' + +#: Only add pickle to this list if your broker is secured +#: from unwanted access (see userguide/security.html) +# CELERY_ACCEPT_CONTENT = ['json'] +CELERY_RESULT_BACKEND = 'db+sqlite:///results.sqlite' +# CELERY_TASK_SERIALIZER = 'json' +CELERY_BROKER_TRANSPORT_OPTIONS = {'max_retries': 0} diff --git a/extension_root/com_longgui_scim_sync_arkid/__init__.py b/extension_root/com_longgui_scim_sync_arkid/__init__.py new file mode 100644 index 000000000..c001f82b0 --- /dev/null +++ b/extension_root/com_longgui_scim_sync_arkid/__init__.py @@ -0,0 +1,219 @@ +from email.headerregistry import Group +from email.mime import base +from ninja import Field +from typing import Optional +from types import SimpleNamespace +from arkid.core import event +from arkid.core.extension import Extension, create_extension_schema +from arkid.core.event import SEND_SMS +from arkid.core.translation import gettext_default as _ +from arkid.core.extension.scim_sync import ( + BaseScimSyncClientSchema, + BaseScimSyncServerSchema, + ScimSyncExtension, +) +from arkid.common.logger import logger +from arkid.core.models import UserGroup, User, Tenant +from scim_server.schemas.core2_enterprise_user import Core2EnterpriseUser +from scim_server.schemas.core2_group import Core2Group +from scim_server.protocol.path import Path +from scim_server.utils import ( + compose_core2_user, + compose_enterprise_extension, + compose_core2_group, + compose_core2_group_member, +) +from scim_server.schemas.schema_identifiers import SchemaIdentifiers +from scim_server.schemas.member import Member +from scim_server.schemas.user_groups import UserGroup as ScimUserGroup +from django.db.utils import IntegrityError + +package = 'com.longgui.scim_sync.arkid' + + +ClientConfig = create_extension_schema( + "ClientConfig", + package, + fields=[ + # ('sign_name', str, Field(title=_("Sign Name", "短信签名名称"))), + # ('template_code', str, Field(title=_("Template Code", "短信模板CODE"))), + # ('sms_up_extend_code', Optional[str], Field(title=_("Sms Up Extend Code", "上行短信扩展码"))), + # ('out_id', Optional[str], Field(title=_("Out ID", "外部流水扩展字段"))), + ], + base_schema=BaseScimSyncClientSchema, +) + +ServerConfig = create_extension_schema( + "ServerConfig", + package, + fields=[ + # ('sign_name', str, Field(title=_("Sign Name", "短信签名名称"))), + # ('template_code', str, Field(title=_("Template Code", "短信模板CODE"))), + # ('sms_up_extend_code', Optional[str], Field(title=_("Sms Up Extend Code", "上行短信扩展码"))), + # ('out_id', Optional[str], Field(title=_("Out ID", "外部流水扩展字段"))), + ], + base_schema=BaseScimSyncServerSchema, +) + + +class ScimSyncArkIDExtension(ScimSyncExtension): + def load(self): + self.register_scim_sync_schema(self.type, ClientConfig, ServerConfig) + super().load() + + def _get_arkid_user_attrs(self, user): + return { + "username": user.get("userName", ""), + "is_active": user.get("active", True), + } + + def _get_arkid_user(self, scim_user, tenant): + scim_external_id = scim_user["id"] + username = scim_user["userName"] + arkid_user_attrs = self._get_arkid_user_attrs(scim_user) + user_lookup = { + "scim_external_id": scim_external_id, + "tenant": tenant, + "username": username, + } + arkid_user, _ = User.objects.update_or_create( + defaults=arkid_user_attrs, **user_lookup + ) + # 更新arkid_user所属的group + arkid_user.user_set.clear() + for scim_group in scim_user.get("groups", []): + scim_group_id = scim_group.get("value") + arkid_group = self.scim_arkid_group_map.get(scim_group_id) + if arkid_group: + arkid_user.user_set.add(arkid_group) + arkid_user.save() + return arkid_user + + def _get_arkid_group(self, group, scim_arkid_map, tenant): + scim_external_id = group["id"] if "id" in group else group["value"] + if scim_external_id not in scim_arkid_map: + group_lookup = {"scim_external_id": scim_external_id, "tenant": tenant} + arkid_group, _ = UserGroup.objects.update_or_create(**group_lookup) + scim_arkid_map[scim_external_id] = arkid_group + return arkid_group + else: + return scim_arkid_map[scim_external_id] + + def _sync_group_attr(self, arkid_group, scim_group): + arkid_group.name = scim_group.get("displayName") + arkid_group.save() + + def sync_groups(self, groups, config): + logger.info("###### update&create groups ######") + tenant = config.tenant + self.scim_arkid_group_map = {} + for group in groups: + parent_group = self._get_arkid_group( + group, self.scim_arkid_group_map, tenant + ) + self._sync_group_attr(parent_group, group) + for member in group.get("members", []): + sub_group = self._get_arkid_group( + member, self.scim_arkid_group_map, tenant + ) + sub_group.parent = parent_group + + logger.info("###### delete groups ######") + groups_need_delete = ( + UserGroup.objects.filter(tenant=config.tenant) + .exclude(scim_external_id=None) + .exclude(scim_external_id__in=self.scim_arkid_group_map.keys()) + ) + logger.info(f"******* groups to be deleted: {groups_need_delete} ********") + groups_need_delete.delete() + + def sync_users(self, users, config): + logger.info("###### update&create users ######") + tenant = config.tenant + scim_user_ids = [] + for user in users: + scim_user_ids.append(user["id"]) + try: + arkid_user = self._get_arkid_user(user, tenant) + except IntegrityError as e: + logger.error(e) + logger.error(f"sync user failed: {user}") + + logger.info("###### delete users ######") + users_need_delete = ( + User.objects.filter(tenant=tenant) + .exclude(scim_external_id=None) + .exclude(scim_external_id__in=scim_user_ids) + ) + logger.info(f"***** users to be deleted: {users_need_delete} ******") + users_need_delete.delete() + + def _get_scim_user(self, arkid_user): + attr_map = {"id": "id", "username": "userName", "is_active": "active"} + scim_user = Core2EnterpriseUser(userName='', groups=[]) + for arkid_attr, scim_attr in attr_map.items(): + value = getattr(arkid_user, arkid_attr) + scim_path = Path.create(scim_attr) + if ( + scim_path.schema_identifier + and scim_path.schema_identifier == SchemaIdentifiers.Core2EnterpriseUser + ): + compose_enterprise_extension(scim_user, scim_path, value) + else: + compose_core2_user(scim_user, scim_path, value) + + # 生成用户所在的组 + parent_groups = arkid_user.user_set.all() + for grp in parent_groups: + scim_user.groups.append(ScimUserGroup(value=grp.id.hex, display=grp.name)) + return scim_user + + def _get_scim_group(self, arkid_group): + members = UserGroup.valid_objects.filter(parent=arkid_group) + attr_map = {"id": "id", "name": "displayName"} + scim_group = Core2Group(displayName='') + for arkid_attr, scim_attr in attr_map.items(): + value = getattr(arkid_group, arkid_attr) + scim_path = Path.create(scim_attr) + compose_core2_group(scim_group, scim_path, value) + for item in members: + member = Member(value=item.id.hex) + scim_group.members.append(member) + return scim_group + + def _get_all_scim_users(self, tenant): + scim_users = [] + arkid_users = User.valid_objects.filter(tenant=tenant) + for arkid_user in arkid_users: + scim_user = self._get_scim_user(arkid_user) + scim_users.append(scim_user) + return scim_users + + def _get_all_scim_groups(self, tenant): + scim_groups = [] + arkid_groups = UserGroup.valid_objects.filter(tenant=tenant) + for arkid_group in arkid_groups: + scim_group = self._get_scim_group(arkid_group) + scim_groups.append(scim_group) + return scim_groups + + def query_users(self, request, parameters, correlation_identifier): + if not parameters.alternate_filters: + all_users = self._get_all_scim_users(request.tenant) + return all_users + + def query_groups(self, request, parameters, correlation_identifier): + if not parameters.alternate_filters: + groups = self._get_all_scim_groups(request.tenant) + return groups + + +extension = ScimSyncArkIDExtension( + package=package, + description='ArkID 同步', + version='1.0', + labels='scim-sync-arkid', + homepage='https://www.longguikeji.com', + logo='', + author='hanbin@jinji-inc.com', +) diff --git a/scim_server/protocol/query_response_base.py b/scim_server/protocol/query_response_base.py index 5f1900381..6c8dca059 100644 --- a/scim_server/protocol/query_response_base.py +++ b/scim_server/protocol/query_response_base.py @@ -5,17 +5,18 @@ from scim_server.protocol.protocol_attribute_names import ProtocolAttributeNames from typing import List, Any + class QueryResponseBase(Schematized): - resources:List[Any] = [] - items_per_page:int = 0 - start_index: int = 0 - total_results: int = 0 + Resources: List[Any] = [] + itemsPerPage: int = 0 + startIndex: int = 0 + totalResults: int = 0 - def __init__(self, resources): - super().__init__() - if resources is None: - raise ArgumentNullException('resources') - self.resources = resources + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # if resources is None: + # raise ArgumentNullException('resources') + # self.Resources = resources self.add_schema(ProtocolSchemaIdentifiers.Version2ListResponse) # @property diff --git a/scim_server/schemas/core2_group_base.py b/scim_server/schemas/core2_group_base.py index f9517739d..00ea05ac6 100644 --- a/scim_server/schemas/core2_group_base.py +++ b/scim_server/schemas/core2_group_base.py @@ -6,8 +6,8 @@ class GroupBase(Resource): - display_name: str - members: List[Member] + displayName: str + members: List[Member] = [] # @property # def display_name(self): # if not hasattr(self, '_display_name'): diff --git a/scim_server/schemas/core2_user_base.py b/scim_server/schemas/core2_user_base.py index bc29b9d94..f18631ad9 100644 --- a/scim_server/schemas/core2_user_base.py +++ b/scim_server/schemas/core2_user_base.py @@ -11,7 +11,7 @@ from scim_server.schemas.phone_number import PhoneNumber from scim_server.schemas.role import Role from scim_server.schemas.photo import Photo -from scim_server.schemas.user_groups import UserGroups +from scim_server.schemas.user_groups import UserGroup from scim_server.schemas.resource import Resource from typing import List, Optional from enum import Enum @@ -42,11 +42,11 @@ class Core2UserBase(Resource): password: Optional[bool] ######################## multi-valued attributes ############ emails: Optional[List[ElectroicMailAddress]] - phone_numbers: Optional[List[PhoneNumber]] + phoneNumbers: Optional[List[PhoneNumber]] ims: Optional[List[InstantMessaging]] photos: Optional[List[Photo]] address: Optional[List[Address]] - groups: Optional[List[UserGroups]] + groups: Optional[List[UserGroup]] # TODO entitlements, roles: Optional[List[Role]] # TODO x509Certificates diff --git a/scim_server/schemas/user_groups.py b/scim_server/schemas/user_groups.py index 2d294c75e..6f8a93790 100644 --- a/scim_server/schemas/user_groups.py +++ b/scim_server/schemas/user_groups.py @@ -4,11 +4,13 @@ from typing import Optional from pydantic import Field + class TypeEnum(str, Enum): direct = 'direct' indirect = 'indirect' -class UserGroups(TypedValue): + +class UserGroup(TypedValue): type: Optional[TypeEnum] ref: Optional[str] = Field(None, alias="$ref") - display: Optional[str] \ No newline at end of file + display: Optional[str] diff --git a/scim_server/service/provider_base.py b/scim_server/service/provider_base.py index f19fa265b..f098afeb8 100644 --- a/scim_server/service/provider_base.py +++ b/scim_server/service/provider_base.py @@ -3,13 +3,17 @@ from scim_server.schemas.core2_service_configuration import Core2ServiceConfiguration from scim_server.exceptions import ArgumentNullException, ArgumentException from scim_server.service.query_response import QueryResponse +from scim_server.schemas.core2_enterprise_user import Core2EnterpriseUser +from scim_server.schemas.core2_group import Core2Group class ProviderBase: TypeSchema = [] config_data = { - "authenticationSchemes": [{"type":"oauth2", "name": "OAuth2", "description": "OAuth2"}], - "bulk": {"supported": False, "maxOperations":0, "maxPayloadSize":0}, + "authenticationSchemes": [ + {"type": "oauth2", "name": "OAuth2", "description": "OAuth2"} + ], + "bulk": {"supported": False, "maxOperations": 0, "maxPayloadSize": 0}, "etag": {"supported": False}, "filter": {"supported": True, "maxResults": 200}, "changePassword": {"supported": False}, @@ -51,7 +55,10 @@ def schema(self): def user_deserialization_behavior(self): return None - def create_async2(self, resource, correlation_identifier): + def create_user(self, request, resource, correlation_identifier): + pass + + def create_group(self, request, resource, correlation_identifier): pass def create_async(self, request): @@ -61,10 +68,19 @@ def create_async(self, request): raise ArgumentException('Invalid Request') if not request.correlation_identifier: raise ArgumentException('Invalid Request') - result = self.create_async2(request.payload, request.correlation_identifier) - return result - def delete_async2(self, resource_identifier, correlation_identifier): + resource = request.payload + correlation_identifier = request.correlation_identifier + if isinstance(resource, Core2EnterpriseUser): + return self.create_user(request.request, resource, correlation_identifier) + + if isinstance(resource, Core2Group): + return self.create_group(request.request, resource, correlation_identifier) + + def delete_user(self, request, resource_identifier, correlation_identifier): + pass + + def delete_group(self, request, resource_identifier, correlation_identifier): pass def delete_async(self, request): @@ -74,23 +90,29 @@ def delete_async(self, request): raise ArgumentException('Invalid Request') if not request.correlation_identifier: raise ArgumentException('Invalid Request') - result = self.delete_async2(request.payload, request.correlation_identifier) - return result + + resource = request.payload + correlation_identifier = request.correlation_identifier + if isinstance(resource, Core2EnterpriseUser): + return self.delete_user(request.request, resource, correlation_identifier) + + if isinstance(resource, Core2Group): + return self.delete_group(request.request, resource, correlation_identifier) def paginate_query_async(self, request): if not request: raise ArgumentNullException('request') resources = self.query_async(request) - result = QueryResponse(resources) - result.total_results = result.items_per_page = len(resources) - result.start_index = 1 if resources else None + result = QueryResponse(Resources=resources) + result.totalResults = result.itemsPerPage = len(resources) + result.startIndex = 1 if resources else None return result - def process_async(self): + def query_users(self, request, parameters, correlation_identifier): pass - def query_async2(self, parameters, correlation_identifier): + def query_groups(self, request, parameters, correlation_identifier): pass def query_async(self, request): @@ -100,10 +122,21 @@ def query_async(self, request): raise ArgumentException('Invalid Request') if not request.correlation_identifier: raise ArgumentException('Invalid Request') - result = self.query_async2(request.payload, request.correlation_identifier) - return result - def replace_async2(self, resource, correlation_identifier): + parameters = request.payload + correlation_identifier = request.correlation_identifier + if parameters.path == 'Users': + return self.query_users(request.request, parameters, correlation_identifier) + + if parameters.path == 'Groups': + return self.query_groups( + request.request, parameters, correlation_identifier + ) + + def replace_user(self, request, resource, correlation_identifier): + pass + + def replace_group(self, request, resource, correlation_identifier): pass def replace_async(self, request): @@ -113,23 +146,48 @@ def replace_async(self, request): raise ArgumentException('Invalid Request') if not request.correlation_identifier: raise ArgumentException('Invalid Request') - result = self.replace_async2(request.payload, request.correlation_identifier) - return result - def retrieve_async2(self, parameters, correlation_identifier): + resource = request.payload + correlation_identifier = request.correlation_identifier + if isinstance(resource, Core2EnterpriseUser): + return self.replace_user(request.request, resource, correlation_identifier) + + if isinstance(resource, Core2Group): + return self.replace_group(request.request, resource, correlation_identifier) + + def retrieve_user(self, request, parameters, correlation_identifier): pass - def retrieve_async(self, request): + def retrieve_group(self, request, parameters, correlation_identifier): + pass + + def retrieve_async( + self, + request, + ): if not request: raise ArgumentNullException('request') if not request.payload: raise ArgumentException('Invalid Request') if not request.correlation_identifier: raise ArgumentException('Invalid Request') - result = self.retrieve_async2(request.payload, request.correlation_identifier) - return result - def update_async2(self, patch, correlation_identifier): + parameters = request.payload + correlation_identifier = request.correlation_identifier + if parameters.path == 'Users': + return self.retrieve_user( + request.request, parameters, correlation_identifier + ) + + if parameters.path == 'Users': + return self.retrieve_group( + request.request, parameters, correlation_identifier + ) + + def update_user(self, request, patch, correlation_identifier): + pass + + def update_group(self, request, patch, correlation_identifier): pass def update_async(self, request): @@ -139,5 +197,11 @@ def update_async(self, request): raise ArgumentException('Invalid Request') if not request.correlation_identifier: raise ArgumentException('Invalid Request') - result = self.update_async2(request.payload, request.correlation_identifier) - return result + + resource = request.payload + correlation_identifier = request.correlation_identifier + if isinstance(resource, Core2EnterpriseUser): + return self.update_user(request.request, resource, correlation_identifier) + + if isinstance(resource, Core2Group): + return self.update_group(request.request, resource, correlation_identifier) diff --git a/scim_server/urls.py b/scim_server/urls.py index 88dcad378..7413f8df8 100644 --- a/scim_server/urls.py +++ b/scim_server/urls.py @@ -18,13 +18,13 @@ # re_path(r'^.search$', views.SearchView.as_view(implemented=False), name='search'), # re_path(r'^Users/.search$', views.UserSearchView.as_view(), name='users-search'), re_path( - r'^memory/scim/Users(?:/(?P[^/]+))?$', + r'^scim/Users(?:/(?P[^/]+))?$', InMemoryUsersView.as_view(), name='memory_users', ), # re_path(r'^Groups/.search$', views.GroupSearchView.as_view(), name='groups-search'), re_path( - r'^memory/scim/Groups(?:/(?P[^/]+))?$', + r'^scim/Groups(?:/(?P[^/]+))?$', InMemoryGroupsView.as_view(), name='memory_groups', ), diff --git a/scim_server/utils.py b/scim_server/utils.py index 16c6ca438..fb29d8e60 100644 --- a/scim_server/utils.py +++ b/scim_server/utils.py @@ -28,9 +28,9 @@ def compose_enterprise_extension(user, scim_path, value): if scim_path.attribute_path == 'manager': compose_manager(user, scim_path, value) elif scim_path.attribute_path == 'employeeNumber': - extension.employee_number = value + extension.employeeNumber = value elif scim_path.attribute_path == 'costCenter': - extension.cost_center = value + extension.costCenter = value elif scim_path.attribute_path == 'organization': extension.organization = value elif scim_path.attribute_path == 'division': @@ -46,36 +46,36 @@ def compose_manager(user, scim_path, value): if not extension.manager: extension.manager = Manager() if scim_path.value_path.attribute_path == 'displayName': - extension.manager.display_name = value + extension.manager.displayName = value elif scim_path.value_path.attribute_path == 'value': extension.manager.value = value def compose_core2_user(user, scim_path, value): if scim_path.attribute_path == 'id': - user.identifier = value + user.id = value elif scim_path.attribute_path == 'active': user.active = bool(value) elif scim_path.attribute_path == 'addresses': compose_addresses(user, scim_path, value) elif scim_path.attribute_path == 'displayName': - user.display_name = value + user.displayName = value elif scim_path.attribute_path == 'emails': compose_emails(user, scim_path, value) elif scim_path.attribute_path == 'externalId': - user.external_identifier = value + user.externalId = value elif scim_path.attribute_path == 'name': compose_name(user, scim_path, value) elif scim_path.attribute_path == 'phoneNumbers': compose_phone_numbers(user, scim_path, value) elif scim_path.attribute_path == 'preferredLanguage': - user.preferred_language = value + user.preferredLanguage = value elif scim_path.attribute_path == 'roles': compose_roles(user, scim_path, value) elif scim_path.attribute_path == 'title': user.title = value elif scim_path.attribute_path == 'userName': - user.user_name = value + user.userName = value def compose_roles(user, scim_path, value): @@ -87,7 +87,7 @@ def compose_roles(user, scim_path, value): if sub_attribute.attribute_path != 'type': return role = Role() - role.item_type = sub_attribute.comparison_value + role.type = sub_attribute.comparison_value role.value = value if user.roles: user.roles.append(role) @@ -106,7 +106,7 @@ def compose_phone_numbers(user, scim_path, value): if sub_attribute.comparison_value not in ['fax', 'work', 'mobile']: return phone = PhoneNumber() - phone.item_type = sub_attribute.comparison_value + phone.type = sub_attribute.comparison_value phone.value = value if user.phone_numbers: user.phone_numbers.append(phone) @@ -124,9 +124,9 @@ def compose_name(user, scim_path, value): name = Name() user.name = name if scim_path.value_path.attribute_path == 'familyName': - name.family_name = value + name.familyName = value elif scim_path.value_path.attribute_path == 'givenName': - name.given_name = value + name.givenName = value elif scim_path.value_path.attribute_path == 'formatted': name.formatted = value @@ -140,7 +140,7 @@ def compose_emails(user, scim_path, value): if sub_attribute.attribute_path != 'type': return email = ElectroicMailAddress() - email.item_type = sub_attribute.comparison_value + email.type = sub_attribute.comparison_value email.value = value if user.electronic_mail_addresses: user.electronic_mail_addresses.append(email) @@ -157,17 +157,17 @@ def compose_addresses(user, scim_path, value): if sub_attribute.attribute_path != 'type': return address = Address() - address.item_type = sub_attribute.comparison_value + address.type = sub_attribute.comparison_value if scim_path.value_path.attribute_path == AttributeNames.Country: address.country = value if scim_path.value_path.attribute_path == AttributeNames.Locality: - address.Locality = value + address.locality = value if scim_path.value_path.attribute_path == AttributeNames.PostalCode: - address.postal_code = value + address.postalCode = value if scim_path.value_path.attribute_path == AttributeNames.Region: address.region = value if scim_path.value_path.attribute_path == AttributeNames.StreetAddress: - address.street_address = value + address.streetAddress = value if scim_path.value_path.attribute_path == AttributeNames.Formatted: address.formatted = value if user.addresses: @@ -178,13 +178,15 @@ def compose_addresses(user, scim_path, value): def compose_core2_group(group, scim_path, value): if scim_path.attribute_path == 'id': - group.identifier = value + group.id = value elif scim_path.attribute_path == 'displayName': - group.display_name = value + group.displayName = value def compose_core2_group_member(member, scim_path, value): - if scim_path.attribute_path == 'id': + if scim_path.attribute_path == 'value': member.value = value - elif scim_path.attribute_path == 'displayName': - member.display = value + elif scim_path.attribute_path == 'type': + member.type = value + elif scim_path.attribute_path == 'ref': + member.ref = value diff --git a/tasks/celery.py b/tasks/celery.py new file mode 100644 index 000000000..6358c558f --- /dev/null +++ b/tasks/celery.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +import os + +from celery import Celery + +# set the default Django settings module for the 'celery' program. +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'arkid.settings') + +app = Celery('arkid') + +# Using a string here means the worker doesn't have to serialize +# the configuration object to child processes. +# - namespace='CELERY' means all celery-related configuration keys +# should have a `CELERY_` prefix. +app.config_from_object('django.conf:settings', namespace='CELERY') + +# Load task modules from all registered Django app configs. +app.autodiscover_tasks() + + +@app.task(bind=True) +def debug_task(self): + print(f'Request: {self.request!r}') From 541d052e3b29528255eb2555468d963e4f98a802 Mon Sep 17 00:00:00 2001 From: fanhe Date: Sun, 1 May 2022 09:30:27 +0800 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=F0=9F=90=9B=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=BA=93=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile | 2 + Pipfile.lock | 540 ++++++++++++++++++++++++++++++++++++------- requirements-dev.txt | 54 +++-- requirements.txt | 48 +++- 4 files changed, 529 insertions(+), 115 deletions(-) diff --git a/Pipfile b/Pipfile index 63763ed87..520484b99 100644 --- a/Pipfile +++ b/Pipfile @@ -22,6 +22,8 @@ mkdocstrings = "*" mkdocs = "*" mkdocs-material = "*" pyjwt = "*" +django-celery-beat = "*" +alibabacloud-dysmsapi20170525 = "*" [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index 275e87ce4..9ae728db1 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "507b95b335e8c86a1ba3a39f06e3ca0861f33f5f54706a10ee0e8bfd96767edb" + "sha256": "f44dca3e46cb58345011f2cec1ab1d1e4cd45cbd45f2bd8142358bc3cb372135" }, "pipfile-spec": 6, "requires": { @@ -16,6 +16,153 @@ ] }, "default": { + "aiohttp": { + "hashes": [ + "sha256:01d7bdb774a9acc838e6b8f1d114f45303841b89b95984cbb7d80ea41172a9e3", + "sha256:03a6d5349c9ee8f79ab3ff3694d6ce1cfc3ced1c9d36200cb8f08ba06bd3b782", + "sha256:04d48b8ce6ab3cf2097b1855e1505181bdd05586ca275f2505514a6e274e8e75", + "sha256:0770e2806a30e744b4e21c9d73b7bee18a1cfa3c47991ee2e5a65b887c49d5cf", + "sha256:07b05cd3305e8a73112103c834e91cd27ce5b4bd07850c4b4dbd1877d3f45be7", + "sha256:086f92daf51a032d062ec5f58af5ca6a44d082c35299c96376a41cbb33034675", + "sha256:099ebd2c37ac74cce10a3527d2b49af80243e2a4fa39e7bce41617fbc35fa3c1", + "sha256:0c7ebbbde809ff4e970824b2b6cb7e4222be6b95a296e46c03cf050878fc1785", + "sha256:102e487eeb82afac440581e5d7f8f44560b36cf0bdd11abc51a46c1cd88914d4", + "sha256:11691cf4dc5b94236ccc609b70fec991234e7ef8d4c02dd0c9668d1e486f5abf", + "sha256:11a67c0d562e07067c4e86bffc1553f2cf5b664d6111c894671b2b8712f3aba5", + "sha256:12de6add4038df8f72fac606dff775791a60f113a725c960f2bab01d8b8e6b15", + "sha256:13487abd2f761d4be7c8ff9080de2671e53fff69711d46de703c310c4c9317ca", + "sha256:15b09b06dae900777833fe7fc4b4aa426556ce95847a3e8d7548e2d19e34edb8", + "sha256:1c182cb873bc91b411e184dab7a2b664d4fea2743df0e4d57402f7f3fa644bac", + "sha256:1ed0b6477896559f17b9eaeb6d38e07f7f9ffe40b9f0f9627ae8b9926ae260a8", + "sha256:28d490af82bc6b7ce53ff31337a18a10498303fe66f701ab65ef27e143c3b0ef", + "sha256:2e5d962cf7e1d426aa0e528a7e198658cdc8aa4fe87f781d039ad75dcd52c516", + "sha256:2ed076098b171573161eb146afcb9129b5ff63308960aeca4b676d9d3c35e700", + "sha256:2f2f69dca064926e79997f45b2f34e202b320fd3782f17a91941f7eb85502ee2", + "sha256:31560d268ff62143e92423ef183680b9829b1b482c011713ae941997921eebc8", + "sha256:31d1e1c0dbf19ebccbfd62eff461518dcb1e307b195e93bba60c965a4dcf1ba0", + "sha256:37951ad2f4a6df6506750a23f7cbabad24c73c65f23f72e95897bb2cecbae676", + "sha256:3af642b43ce56c24d063325dd2cf20ee012d2b9ba4c3c008755a301aaea720ad", + "sha256:44db35a9e15d6fe5c40d74952e803b1d96e964f683b5a78c3cc64eb177878155", + "sha256:473d93d4450880fe278696549f2e7aed8cd23708c3c1997981464475f32137db", + "sha256:477c3ea0ba410b2b56b7efb072c36fa91b1e6fc331761798fa3f28bb224830dd", + "sha256:4a4a4e30bf1edcad13fb0804300557aedd07a92cabc74382fdd0ba6ca2661091", + "sha256:4aed991a28ea3ce320dc8ce655875e1e00a11bdd29fe9444dd4f88c30d558602", + "sha256:51467000f3647d519272392f484126aa716f747859794ac9924a7aafa86cd411", + "sha256:55c3d1072704d27401c92339144d199d9de7b52627f724a949fc7d5fc56d8b93", + "sha256:589c72667a5febd36f1315aa6e5f56dd4aa4862df295cb51c769d16142ddd7cd", + "sha256:5bfde62d1d2641a1f5173b8c8c2d96ceb4854f54a44c23102e2ccc7e02f003ec", + "sha256:5c23b1ad869653bc818e972b7a3a79852d0e494e9ab7e1a701a3decc49c20d51", + "sha256:61bfc23df345d8c9716d03717c2ed5e27374e0fe6f659ea64edcd27b4b044cf7", + "sha256:6ae828d3a003f03ae31915c31fa684b9890ea44c9c989056fea96e3d12a9fa17", + "sha256:6c7cefb4b0640703eb1069835c02486669312bf2f12b48a748e0a7756d0de33d", + "sha256:6d69f36d445c45cda7b3b26afef2fc34ef5ac0cdc75584a87ef307ee3c8c6d00", + "sha256:6f0d5f33feb5f69ddd57a4a4bd3d56c719a141080b445cbf18f238973c5c9923", + "sha256:6f8b01295e26c68b3a1b90efb7a89029110d3a4139270b24fda961893216c440", + "sha256:713ac174a629d39b7c6a3aa757b337599798da4c1157114a314e4e391cd28e32", + "sha256:718626a174e7e467f0558954f94af117b7d4695d48eb980146016afa4b580b2e", + "sha256:7187a76598bdb895af0adbd2fb7474d7f6025d170bc0a1130242da817ce9e7d1", + "sha256:71927042ed6365a09a98a6377501af5c9f0a4d38083652bcd2281a06a5976724", + "sha256:7d08744e9bae2ca9c382581f7dce1273fe3c9bae94ff572c3626e8da5b193c6a", + "sha256:7dadf3c307b31e0e61689cbf9e06be7a867c563d5a63ce9dca578f956609abf8", + "sha256:81e3d8c34c623ca4e36c46524a3530e99c0bc95ed068fd6e9b55cb721d408fb2", + "sha256:844a9b460871ee0a0b0b68a64890dae9c415e513db0f4a7e3cab41a0f2fedf33", + "sha256:8b7ef7cbd4fec9a1e811a5de813311ed4f7ac7d93e0fda233c9b3e1428f7dd7b", + "sha256:97ef77eb6b044134c0b3a96e16abcb05ecce892965a2124c566af0fd60f717e2", + "sha256:99b5eeae8e019e7aad8af8bb314fb908dd2e028b3cdaad87ec05095394cce632", + "sha256:a25fa703a527158aaf10dafd956f7d42ac6d30ec80e9a70846253dd13e2f067b", + "sha256:a2f635ce61a89c5732537a7896b6319a8fcfa23ba09bec36e1b1ac0ab31270d2", + "sha256:a79004bb58748f31ae1cbe9fa891054baaa46fb106c2dc7af9f8e3304dc30316", + "sha256:a996d01ca39b8dfe77440f3cd600825d05841088fd6bc0144cc6c2ec14cc5f74", + "sha256:b0e20cddbd676ab8a64c774fefa0ad787cc506afd844de95da56060348021e96", + "sha256:b6613280ccedf24354406caf785db748bebbddcf31408b20c0b48cb86af76866", + "sha256:b9d00268fcb9f66fbcc7cd9fe423741d90c75ee029a1d15c09b22d23253c0a44", + "sha256:bb01ba6b0d3f6c68b89fce7305080145d4877ad3acaed424bae4d4ee75faa950", + "sha256:c2aef4703f1f2ddc6df17519885dbfa3514929149d3ff900b73f45998f2532fa", + "sha256:c34dc4958b232ef6188c4318cb7b2c2d80521c9a56c52449f8f93ab7bc2a8a1c", + "sha256:c3630c3ef435c0a7c549ba170a0633a56e92629aeed0e707fec832dee313fb7a", + "sha256:c3d6a4d0619e09dcd61021debf7059955c2004fa29f48788a3dfaf9c9901a7cd", + "sha256:d15367ce87c8e9e09b0f989bfd72dc641bcd04ba091c68cd305312d00962addd", + "sha256:d2f9b69293c33aaa53d923032fe227feac867f81682f002ce33ffae978f0a9a9", + "sha256:e999f2d0e12eea01caeecb17b653f3713d758f6dcc770417cf29ef08d3931421", + "sha256:ea302f34477fda3f85560a06d9ebdc7fa41e82420e892fc50b577e35fc6a50b2", + "sha256:eaba923151d9deea315be1f3e2b31cc39a6d1d2f682f942905951f4e40200922", + "sha256:ef9612483cb35171d51d9173647eed5d0069eaa2ee812793a75373447d487aa4", + "sha256:f5315a2eb0239185af1bddb1abf472d877fede3cc8d143c6cddad37678293237", + "sha256:fa0ffcace9b3aa34d205d8130f7873fcfefcb6a4dd3dd705b0dab69af6712642", + "sha256:fc5471e1a54de15ef71c1bc6ebe80d4dc681ea600e68bfd1cbce40427f0b7578" + ], + "markers": "python_version >= '3.6'", + "version": "==3.8.1" + }, + "aiosignal": { + "hashes": [ + "sha256:26e62109036cd181df6e6ad646f91f0dcfd05fe16d0cb924138ff2ab75d64e3a", + "sha256:78ed67db6c7b7ced4f98e495e572106d5c432a93e1ddd1bf475e1dc05f5b7df2" + ], + "markers": "python_version >= '3.6'", + "version": "==1.2.0" + }, + "alibabacloud-credentials": { + "hashes": [ + "sha256:b4424b26065d2156e9ca00e8ec79dac5317c7d7df95bca6acefcdbd9bc5651c4" + ], + "markers": "python_version >= '3.6'", + "version": "==0.2.0" + }, + "alibabacloud-dysmsapi20170525": { + "hashes": [ + "sha256:03c7ca5c3829c11442b3c845494355d531afc67966fc80cb54cae767b12f8c8c", + "sha256:0988c5a9a444f9b87c2c3afacac9d19338edb64ae77eea28683f7cac1d97b417" + ], + "index": "pypi", + "version": "==2.0.9" + }, + "alibabacloud-endpoint-util": { + "hashes": [ + "sha256:8c0efb76fdcc3af4ca716ef24bbce770201a3f83f98c0afcf81655f684b9c7d2" + ], + "version": "==0.0.3" + }, + "alibabacloud-gateway-spi": { + "hashes": [ + "sha256:1b259855708afc3c04d8711d8530c63f7645e1edc0cf97e2fd15461b08e11c30" + ], + "markers": "python_version >= '3.6'", + "version": "==0.0.1" + }, + "alibabacloud-openapi-util": { + "hashes": [ + "sha256:def82164e698ec66c26d9dfede8d4c56018627cf760c58759e52eb5e20e8dfac" + ], + "version": "==0.1.6" + }, + "alibabacloud-tea": { + "hashes": [ + "sha256:81cd3c7585e3e8707c24587b227ca44ab1b9ec405a545d1d2300340cda5263a2" + ], + "markers": "python_version >= '3.6'", + "version": "==0.2.9" + }, + "alibabacloud-tea-openapi": { + "hashes": [ + "sha256:4dd0bf124b8d697c32b47208e14e61e1e41f7ce6cbc0d9baa4964bb9590f6609" + ], + "markers": "python_version >= '3.6'", + "version": "==0.3.3" + }, + "alibabacloud-tea-util": { + "hashes": [ + "sha256:a4a2ea0a40e537e767b6749111bda1350f982df5e84a51936bb4482dd709fa16" + ], + "markers": "python_version >= '3.6'", + "version": "==0.3.5" + }, + "alibabacloud-tea-xml": { + "hashes": [ + "sha256:f0135e8148fd7d9c1f029db161863f37f144f837c280cba16c2edeb2f9c549d8" + ], + "version": "==0.0.2" + }, "amqp": { "hashes": [ "sha256:2c1b13fecc0893e946c65cbd5f36427861cffa4ea2201d8f6fca22e2a373b5e2", @@ -26,11 +173,11 @@ }, "asgiref": { "hashes": [ - "sha256:2f8abc20f7248433085eda803936d98992f1343ddb022065779f37c5da0181d0", - "sha256:88d59c13d634dcffe0510be048210188edd79aeccb6a6c9028cdad6f31d730a9" + "sha256:45a429524fba18aba9d512498b19d220c4d628e75b40cf5c627524dbaebc5cc1", + "sha256:fddeea3c53fa99d0cdb613c3941cc6e52d822491fc2753fba25768fb5bf4e865" ], "markers": "python_version >= '3.7'", - "version": "==3.5.0" + "version": "==3.5.1" }, "astunparse": { "hashes": [ @@ -48,27 +195,13 @@ "markers": "python_version >= '3.6'", "version": "==4.0.2" }, - "backports.zoneinfo": { - "hashes": [ - "sha256:17746bd546106fa389c51dbea67c8b7c8f0d14b5526a579ca6ccf5ed72c526cf", - "sha256:1b13e654a55cd45672cb54ed12148cd33628f672548f373963b0bff67b217328", - "sha256:1c5742112073a563c81f786e77514969acb58649bcdf6cdf0b4ed31a348d4546", - "sha256:4a0f800587060bf8880f954dbef70de6c11bbe59c673c3d818921f042f9954a6", - "sha256:5c144945a7752ca544b4b78c8c41544cdfaf9786f25fe5ffb10e838e19a27570", - "sha256:7b0a64cda4145548fed9efc10322770f929b944ce5cee6c0dfe0c87bf4c0c8c9", - "sha256:8439c030a11780786a2002261569bdf362264f605dfa4d65090b64b05c9f79a7", - "sha256:8961c0f32cd0336fb8e8ead11a1f8cd99ec07145ec2931122faaac1c8f7fd987", - "sha256:89a48c0d158a3cc3f654da4c2de1ceba85263fafb861b98b59040a5086259722", - "sha256:a76b38c52400b762e48131494ba26be363491ac4f9a04c1b7e92483d169f6582", - "sha256:da6013fd84a690242c310d77ddb8441a559e9cb3d3d59ebac9aca1a57b2e18bc", - "sha256:e55b384612d93be96506932a786bbcde5a2db7a9e6a4bb4bffe8b733f5b9036b", - "sha256:e81b76cace8eda1fca50e345242ba977f9be6ae3945af8d46326d776b4cf78d1", - "sha256:e8236383a20872c0cdf5a62b554b27538db7fa1bbec52429d8d106effbaeca08", - "sha256:f04e857b59d9d1ccc39ce2da1021d196e47234873820cbeaad210724b1ee28ac", - "sha256:fadbfe37f74051d024037f223b8e001611eac868b5c5b06144ef4d8b799862f2" + "attrs": { + "hashes": [ + "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4", + "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd" ], - "markers": "python_version < '3.9'", - "version": "==0.2.1" + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", + "version": "==21.4.0" }, "billiard": { "hashes": [ @@ -157,11 +290,11 @@ }, "click": { "hashes": [ - "sha256:24e1a4a9ec5bf6299411369b208c1df2188d9eb8d916302fe6bf03faed227f1e", - "sha256:479707fe14d9ec9a0757618b7a100a0ae4c4e236fac5b7f80ca68028141a1a72" + "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e", + "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48" ], "markers": "python_version >= '3.7'", - "version": "==8.1.2" + "version": "==8.1.3" }, "click-didyoumean": { "hashes": [ @@ -187,29 +320,31 @@ }, "cryptography": { "hashes": [ - "sha256:0a3bf09bb0b7a2c93ce7b98cb107e9170a90c51a0162a20af1c61c765b90e60b", - "sha256:1f64a62b3b75e4005df19d3b5235abd43fa6358d5516cfc43d87aeba8d08dd51", - "sha256:32db5cc49c73f39aac27574522cecd0a4bb7384e71198bc65a0d23f901e89bb7", - "sha256:4881d09298cd0b669bb15b9cfe6166f16fc1277b4ed0d04a22f3d6430cb30f1d", - "sha256:4e2dddd38a5ba733be6a025a1475a9f45e4e41139d1321f412c6b360b19070b6", - "sha256:53e0285b49fd0ab6e604f4c5d9c5ddd98de77018542e88366923f152dbeb3c29", - "sha256:70f8f4f7bb2ac9f340655cbac89d68c527af5bb4387522a8413e841e3e6628c9", - "sha256:7b2d54e787a884ffc6e187262823b6feb06c338084bbe80d45166a1cb1c6c5bf", - "sha256:7be666cc4599b415f320839e36367b273db8501127b38316f3b9f22f17a0b815", - "sha256:8241cac0aae90b82d6b5c443b853723bcc66963970c67e56e71a2609dc4b5eaf", - "sha256:82740818f2f240a5da8dfb8943b360e4f24022b093207160c77cadade47d7c85", - "sha256:8897b7b7ec077c819187a123174b645eb680c13df68354ed99f9b40a50898f77", - "sha256:c2c5250ff0d36fd58550252f54915776940e4e866f38f3a7866d92b32a654b86", - "sha256:ca9f686517ec2c4a4ce930207f75c00bf03d94e5063cbc00a1dc42531511b7eb", - "sha256:d2b3d199647468d410994dbeb8cec5816fb74feb9368aedf300af709ef507e3e", - "sha256:da73d095f8590ad437cd5e9faf6628a218aa7c387e1fdf67b888b47ba56a17f0", - "sha256:e167b6b710c7f7bc54e67ef593f8731e1f45aa35f8a8a7b72d6e42ec76afd4b3", - "sha256:ea634401ca02367c1567f012317502ef3437522e2fc44a3ea1844de028fa4b84", - "sha256:ec6597aa85ce03f3e507566b8bcdf9da2227ec86c4266bd5e6ab4d9e0cc8dab2", - "sha256:f64b232348ee82f13aac22856515ce0195837f6968aeaa94a3d0353ea2ec06a6" + "sha256:06bfafa6e53ccbfb7a94be4687b211a025ce0625e3f3c60bb15cd048a18f3ed8", + "sha256:0db5cf21bd7d092baacb576482b0245102cea2d3cf09f09271ce9f69624ecb6f", + "sha256:125702572be12bcd318e3a14e9e70acd4be69a43664a75f0397e8650fe3c6cc3", + "sha256:1858eff6246bb8bbc080eee78f3dd1528739e3f416cba5f9914e8631b8df9871", + "sha256:315af6268de72bcfa0bb3401350ce7d921f216e6b60de12a363dad128d9d459f", + "sha256:451aaff8b8adf2dd0597cbb1fdcfc8a7d580f33f843b7cce75307a7f20112dd8", + "sha256:58021d6e9b1d88b1105269d0da5e60e778b37dfc0e824efc71343dd003726831", + "sha256:618391152147a1221c87b1b0b7f792cafcfd4b5a685c5c72eeea2ddd29aeceff", + "sha256:6d4daf890e674d191757d8d7d60dc3a29c58c72c7a76a05f1c0a326013f47e8b", + "sha256:74b55f67f4cf026cb84da7a1b04fc2a1d260193d4ad0ea5e9897c8b74c1e76ac", + "sha256:7ceae26f876aabe193b13a0c36d1bb8e3e7e608d17351861b437bd882f617e9f", + "sha256:930b829e8a2abaf43a19f38277ae3c5e1ffcf547b936a927d2587769ae52c296", + "sha256:a18ff4bfa9d64914a84d7b06c46eb86e0cc03113470b3c111255aceb6dcaf81d", + "sha256:ae1cd29fbe6b716855454e44f4bf743465152e15d2d317303fe3b58ee9e5af7a", + "sha256:b1ee5c82cf03b30f6ae4e32d2bcb1e167ef74d6071cbb77c2af30f101d0b360b", + "sha256:bf585476fcbcd37bed08072e8e2db3954ce1bfc68087a2dc9c19cfe0b90979ca", + "sha256:c4a58eeafbd7409054be41a377e726a7904a17c26f45abf18125d21b1215b08b", + "sha256:cce90609e01e1b192fae9e13665058ab46b2ea53a3c05a3ea74a3eb8c3af8857", + "sha256:d610d0ee14dd9109006215c7c0de15eee91230b70a9bce2263461cf7c3720b83", + "sha256:e69a0e36e62279120e648e787b76d79b41e0f9e86c1c636a4f38d415595c722e", + "sha256:f095988548ec5095e3750cdb30e6962273d239b1998ba1aac66c0d5bee7111c1", + "sha256:faf0f5456c059c7b1c29441bdd5e988f0ba75bdc3eea776520d8dcb1e30e1b5c" ], "markers": "python_version >= '3.6'", - "version": "==36.0.2" + "version": "==37.0.1" }, "deprecated": { "hashes": [ @@ -221,11 +356,19 @@ }, "django": { "hashes": [ - "sha256:07c8638e7a7f548dc0acaaa7825d84b7bd42b10e8d22268b3d572946f1e9b687", - "sha256:4e8177858524417563cc0430f29ea249946d831eacb0068a1455686587df40b5" + "sha256:6d93497a0a9bf6ba0e0b1a29cccdc40efbfc76297255b1309b3a884a688ec4b6", + "sha256:b896ca61edc079eb6bbaa15cf6071eb69d6aac08cce5211583cfb41515644fdf" ], - "markers": "python_version >= '3.8'", - "version": "==4.0.4" + "markers": "python_version >= '3.6'", + "version": "==3.2.13" + }, + "django-celery-beat": { + "hashes": [ + "sha256:97ae5eb309541551bdb07bf60cc57cadacf42a74287560ced2d2c06298620234", + "sha256:ab43049634fd18dc037927d7c2c7d5f67f95283a20ebbda55f42f8606412e66c" + ], + "index": "pypi", + "version": "==2.2.1" }, "django-ninja": { "hashes": [ @@ -235,6 +378,79 @@ "index": "pypi", "version": "==0.17.0" }, + "django-timezone-field": { + "hashes": [ + "sha256:5dd5bd9249382bef8847d3e7e4c32b7be182a4b538f354130d1252ed228892f8", + "sha256:7552d2b0f145684b7de3fb5046101c7efd600cc6ba951b15c630fa1e1b83558e" + ], + "markers": "python_version >= '3.5'", + "version": "==4.2.3" + }, + "frozenlist": { + "hashes": [ + "sha256:006d3595e7d4108a12025ddf415ae0f6c9e736e726a5db0183326fd191b14c5e", + "sha256:01a73627448b1f2145bddb6e6c2259988bb8aee0fb361776ff8604b99616cd08", + "sha256:03a7dd1bfce30216a3f51a84e6dd0e4a573d23ca50f0346634916ff105ba6e6b", + "sha256:0437fe763fb5d4adad1756050cbf855bbb2bf0d9385c7bb13d7a10b0dd550486", + "sha256:04cb491c4b1c051734d41ea2552fde292f5f3a9c911363f74f39c23659c4af78", + "sha256:0c36e78b9509e97042ef869c0e1e6ef6429e55817c12d78245eb915e1cca7468", + "sha256:25af28b560e0c76fa41f550eacb389905633e7ac02d6eb3c09017fa1c8cdfde1", + "sha256:2fdc3cd845e5a1f71a0c3518528bfdbfe2efaf9886d6f49eacc5ee4fd9a10953", + "sha256:30530930410855c451bea83f7b272fb1c495ed9d5cc72895ac29e91279401db3", + "sha256:31977f84828b5bb856ca1eb07bf7e3a34f33a5cddce981d880240ba06639b94d", + "sha256:3c62964192a1c0c30b49f403495911298810bada64e4f03249ca35a33ca0417a", + "sha256:3f7c935c7b58b0d78c0beea0c7358e165f95f1fd8a7e98baa40d22a05b4a8141", + "sha256:40dff8962b8eba91fd3848d857203f0bd704b5f1fa2b3fc9af64901a190bba08", + "sha256:40ec383bc194accba825fbb7d0ef3dda5736ceab2375462f1d8672d9f6b68d07", + "sha256:436496321dad302b8b27ca955364a439ed1f0999311c393dccb243e451ff66aa", + "sha256:4406cfabef8f07b3b3af0f50f70938ec06d9f0fc26cbdeaab431cbc3ca3caeaa", + "sha256:45334234ec30fc4ea677f43171b18a27505bfb2dba9aca4398a62692c0ea8868", + "sha256:47be22dc27ed933d55ee55845d34a3e4e9f6fee93039e7f8ebadb0c2f60d403f", + "sha256:4a44ebbf601d7bac77976d429e9bdb5a4614f9f4027777f9e54fd765196e9d3b", + "sha256:4eda49bea3602812518765810af732229b4291d2695ed24a0a20e098c45a707b", + "sha256:57f4d3f03a18facacb2a6bcd21bccd011e3b75d463dc49f838fd699d074fabd1", + "sha256:603b9091bd70fae7be28bdb8aa5c9990f4241aa33abb673390a7f7329296695f", + "sha256:65bc6e2fece04e2145ab6e3c47428d1bbc05aede61ae365b2c1bddd94906e478", + "sha256:691ddf6dc50480ce49f68441f1d16a4c3325887453837036e0fb94736eae1e58", + "sha256:6983a31698490825171be44ffbafeaa930ddf590d3f051e397143a5045513b01", + "sha256:6a202458d1298ced3768f5a7d44301e7c86defac162ace0ab7434c2e961166e8", + "sha256:6eb275c6385dd72594758cbe96c07cdb9bd6becf84235f4a594bdf21e3596c9d", + "sha256:754728d65f1acc61e0f4df784456106e35afb7bf39cfe37227ab00436fb38676", + "sha256:768efd082074bb203c934e83a61654ed4931ef02412c2fbdecea0cff7ecd0274", + "sha256:772965f773757a6026dea111a15e6e2678fbd6216180f82a48a40b27de1ee2ab", + "sha256:871d42623ae15eb0b0e9df65baeee6976b2e161d0ba93155411d58ff27483ad8", + "sha256:88aafd445a233dbbf8a65a62bc3249a0acd0d81ab18f6feb461cc5a938610d24", + "sha256:8c905a5186d77111f02144fab5b849ab524f1e876a1e75205cd1386a9be4b00a", + "sha256:8cf829bd2e2956066dd4de43fd8ec881d87842a06708c035b37ef632930505a2", + "sha256:92e650bd09b5dda929523b9f8e7f99b24deac61240ecc1a32aeba487afcd970f", + "sha256:93641a51f89473837333b2f8100f3f89795295b858cd4c7d4a1f18e299dc0a4f", + "sha256:94c7a8a9fc9383b52c410a2ec952521906d355d18fccc927fca52ab575ee8b93", + "sha256:9f892d6a94ec5c7b785e548e42722e6f3a52f5f32a8461e82ac3e67a3bd073f1", + "sha256:acb267b09a509c1df5a4ca04140da96016f40d2ed183cdc356d237286c971b51", + "sha256:adac9700675cf99e3615eb6a0eb5e9f5a4143c7d42c05cea2e7f71c27a3d0846", + "sha256:aff388be97ef2677ae185e72dc500d19ecaf31b698986800d3fc4f399a5e30a5", + "sha256:b5009062d78a8c6890d50b4e53b0ddda31841b3935c1937e2ed8c1bda1c7fb9d", + "sha256:b684c68077b84522b5c7eafc1dc735bfa5b341fb011d5552ebe0968e22ed641c", + "sha256:b9e3e9e365991f8cc5f5edc1fd65b58b41d0514a6a7ad95ef5c7f34eb49b3d3e", + "sha256:bd89acd1b8bb4f31b47072615d72e7f53a948d302b7c1d1455e42622de180eae", + "sha256:bde99812f237f79eaf3f04ebffd74f6718bbd216101b35ac7955c2d47c17da02", + "sha256:c6c321dd013e8fc20735b92cb4892c115f5cdb82c817b1e5b07f6b95d952b2f0", + "sha256:ce6f2ba0edb7b0c1d8976565298ad2deba6f8064d2bebb6ffce2ca896eb35b0b", + "sha256:d2257aaba9660f78c7b1d8fea963b68f3feffb1a9d5d05a18401ca9eb3e8d0a3", + "sha256:d26b650b71fdc88065b7a21f8ace70175bcf3b5bdba5ea22df4bfd893e795a3b", + "sha256:d6d32ff213aef0fd0bcf803bffe15cfa2d4fde237d1d4838e62aec242a8362fa", + "sha256:e1e26ac0a253a2907d654a37e390904426d5ae5483150ce3adedb35c8c06614a", + "sha256:e30b2f9683812eb30cf3f0a8e9f79f8d590a7999f731cf39f9105a7c4a39489d", + "sha256:e84cb61b0ac40a0c3e0e8b79c575161c5300d1d89e13c0e02f76193982f066ed", + "sha256:e982878792c971cbd60ee510c4ee5bf089a8246226dea1f2138aa0bb67aff148", + "sha256:f20baa05eaa2bcd5404c445ec51aed1c268d62600362dc6cfe04fae34a424bd9", + "sha256:f7353ba3367473d1d616ee727945f439e027f0bb16ac1a750219a8344d1d5d3c", + "sha256:f96293d6f982c58ebebb428c50163d010c2f05de0cde99fd681bfdc18d4b2dc2", + "sha256:ff9310f05b9d9c5c4dd472983dc956901ee6cb2c3ec1ab116ecdde25f3ce4951" + ], + "markers": "python_version >= '3.7'", + "version": "==1.3.0" + }, "ghp-import": { "hashes": [ "sha256:5f8962b30b20652cdffa9c5a9812f7de6bcb56ec475acac579807719bf242c46", @@ -255,24 +471,23 @@ "sha256:1208431ca90a8cca1a6b8af391bb53c1a2db74e5d1cef6ddced95d4b2062edc6", "sha256:ea4c597ebf37142f827b8f39299579e31685c31d3a438b59f469406afd0f2539" ], - "markers": "python_version < '3.10'", + "markers": "python_version >= '3.7'", "version": "==4.11.3" }, "jinja2": { "hashes": [ - "sha256:539835f51a74a69f41b848a9645dbdc35b4f20a3b601e2d9a7e22947b15ff119", - "sha256:640bed4bb501cbd17194b3cace1dc2126f5b619cf068a726b98192a0fde74ae9" + "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852", + "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61" ], "markers": "python_version >= '3.7'", - "version": "==3.1.1" + "version": "==3.1.2" }, "jwcrypto": { "hashes": [ - "sha256:db93a656d9a7a35dda5a68deb5c9f301f4e60507d8aef1559e0637b9ac497137", - "sha256:f88816eb0a41b8f006af978ced5f171f33782525006cdb055b536a40f4d46ac9" + "sha256:edf4309321721e5161cefccdd64b04509e03924feaf3de085b47780765989ae3" ], "index": "pypi", - "version": "==1.0" + "version": "==1.2" }, "kombu": { "hashes": [ @@ -362,11 +577,11 @@ }, "mkdocs-material": { "hashes": [ - "sha256:c177ff180b024bc061714c9483a8d26d36e1b9fdef4be8e70e243770416fe9d7", - "sha256:fbe39baa57c70fdbe9d1a24c6c2d0625e255e74f22b20aff43abb64157446f4d" + "sha256:7489f1ccce97a806be6bba659e21f7d404c2f85a7ff72abbac6d47d0836f5952", + "sha256:a569868c7c17bd1c87efa4dea7cb0cd20c4eac52ed5589750cec9dfcd3e3c7d5" ], "index": "pypi", - "version": "==8.2.9" + "version": "==8.2.12" }, "mkdocs-material-extensions": { "hashes": [ @@ -392,6 +607,71 @@ "markers": "python_version >= '3.7'", "version": "==0.2.2" }, + "multidict": { + "hashes": [ + "sha256:0327292e745a880459ef71be14e709aaea2f783f3537588fb4ed09b6c01bca60", + "sha256:041b81a5f6b38244b34dc18c7b6aba91f9cdaf854d9a39e5ff0b58e2b5773b9c", + "sha256:0556a1d4ea2d949efe5fd76a09b4a82e3a4a30700553a6725535098d8d9fb672", + "sha256:05f6949d6169878a03e607a21e3b862eaf8e356590e8bdae4227eedadacf6e51", + "sha256:07a017cfa00c9890011628eab2503bee5872f27144936a52eaab449be5eaf032", + "sha256:0b9e95a740109c6047602f4db4da9949e6c5945cefbad34a1299775ddc9a62e2", + "sha256:19adcfc2a7197cdc3987044e3f415168fc5dc1f720c932eb1ef4f71a2067e08b", + "sha256:19d9bad105dfb34eb539c97b132057a4e709919ec4dd883ece5838bcbf262b80", + "sha256:225383a6603c086e6cef0f2f05564acb4f4d5f019a4e3e983f572b8530f70c88", + "sha256:23b616fdc3c74c9fe01d76ce0d1ce872d2d396d8fa8e4899398ad64fb5aa214a", + "sha256:2957489cba47c2539a8eb7ab32ff49101439ccf78eab724c828c1a54ff3ff98d", + "sha256:2d36e929d7f6a16d4eb11b250719c39560dd70545356365b494249e2186bc389", + "sha256:2e4a0785b84fb59e43c18a015ffc575ba93f7d1dbd272b4cdad9f5134b8a006c", + "sha256:3368bf2398b0e0fcbf46d85795adc4c259299fec50c1416d0f77c0a843a3eed9", + "sha256:373ba9d1d061c76462d74e7de1c0c8e267e9791ee8cfefcf6b0b2495762c370c", + "sha256:4070613ea2227da2bfb2c35a6041e4371b0af6b0be57f424fe2318b42a748516", + "sha256:45183c96ddf61bf96d2684d9fbaf6f3564d86b34cb125761f9a0ef9e36c1d55b", + "sha256:4571f1beddff25f3e925eea34268422622963cd8dc395bb8778eb28418248e43", + "sha256:47e6a7e923e9cada7c139531feac59448f1f47727a79076c0b1ee80274cd8eee", + "sha256:47fbeedbf94bed6547d3aa632075d804867a352d86688c04e606971595460227", + "sha256:497988d6b6ec6ed6f87030ec03280b696ca47dbf0648045e4e1d28b80346560d", + "sha256:4bae31803d708f6f15fd98be6a6ac0b6958fcf68fda3c77a048a4f9073704aae", + "sha256:50bd442726e288e884f7be9071016c15a8742eb689a593a0cac49ea093eef0a7", + "sha256:514fe2b8d750d6cdb4712346a2c5084a80220821a3e91f3f71eec11cf8d28fd4", + "sha256:5774d9218d77befa7b70d836004a768fb9aa4fdb53c97498f4d8d3f67bb9cfa9", + "sha256:5fdda29a3c7e76a064f2477c9aab1ba96fd94e02e386f1e665bca1807fc5386f", + "sha256:5ff3bd75f38e4c43f1f470f2df7a4d430b821c4ce22be384e1459cb57d6bb013", + "sha256:626fe10ac87851f4cffecee161fc6f8f9853f0f6f1035b59337a51d29ff3b4f9", + "sha256:6701bf8a5d03a43375909ac91b6980aea74b0f5402fbe9428fc3f6edf5d9677e", + "sha256:684133b1e1fe91eda8fa7447f137c9490a064c6b7f392aa857bba83a28cfb693", + "sha256:6f3cdef8a247d1eafa649085812f8a310e728bdf3900ff6c434eafb2d443b23a", + "sha256:75bdf08716edde767b09e76829db8c1e5ca9d8bb0a8d4bd94ae1eafe3dac5e15", + "sha256:7c40b7bbece294ae3a87c1bc2abff0ff9beef41d14188cda94ada7bcea99b0fb", + "sha256:8004dca28e15b86d1b1372515f32eb6f814bdf6f00952699bdeb541691091f96", + "sha256:8064b7c6f0af936a741ea1efd18690bacfbae4078c0c385d7c3f611d11f0cf87", + "sha256:89171b2c769e03a953d5969b2f272efa931426355b6c0cb508022976a17fd376", + "sha256:8cbf0132f3de7cc6c6ce00147cc78e6439ea736cee6bca4f068bcf892b0fd658", + "sha256:9cc57c68cb9139c7cd6fc39f211b02198e69fb90ce4bc4a094cf5fe0d20fd8b0", + "sha256:a007b1638e148c3cfb6bf0bdc4f82776cef0ac487191d093cdc316905e504071", + "sha256:a2c34a93e1d2aa35fbf1485e5010337c72c6791407d03aa5f4eed920343dd360", + "sha256:a45e1135cb07086833ce969555df39149680e5471c04dfd6a915abd2fc3f6dbc", + "sha256:ac0e27844758d7177989ce406acc6a83c16ed4524ebc363c1f748cba184d89d3", + "sha256:aef9cc3d9c7d63d924adac329c33835e0243b5052a6dfcbf7732a921c6e918ba", + "sha256:b9d153e7f1f9ba0b23ad1568b3b9e17301e23b042c23870f9ee0522dc5cc79e8", + "sha256:bfba7c6d5d7c9099ba21f84662b037a0ffd4a5e6b26ac07d19e423e6fdf965a9", + "sha256:c207fff63adcdf5a485969131dc70e4b194327666b7e8a87a97fbc4fd80a53b2", + "sha256:d0509e469d48940147e1235d994cd849a8f8195e0bca65f8f5439c56e17872a3", + "sha256:d16cce709ebfadc91278a1c005e3c17dd5f71f5098bfae1035149785ea6e9c68", + "sha256:d48b8ee1d4068561ce8033d2c344cf5232cb29ee1a0206a7b828c79cbc5982b8", + "sha256:de989b195c3d636ba000ee4281cd03bb1234635b124bf4cd89eeee9ca8fcb09d", + "sha256:e07c8e79d6e6fd37b42f3250dba122053fddb319e84b55dd3a8d6446e1a7ee49", + "sha256:e2c2e459f7050aeb7c1b1276763364884595d47000c1cddb51764c0d8976e608", + "sha256:e5b20e9599ba74391ca0cfbd7b328fcc20976823ba19bc573983a25b32e92b57", + "sha256:e875b6086e325bab7e680e4316d667fc0e5e174bb5611eb16b3ea121c8951b86", + "sha256:f4f052ee022928d34fe1f4d2bc743f32609fb79ed9c49a1710a5ad6b2198db20", + "sha256:fcb91630817aa8b9bc4a74023e4198480587269c272c58b3279875ed7235c293", + "sha256:fd9fc9c4849a07f3635ccffa895d57abce554b467d611a5009ba4f39b78a8849", + "sha256:feba80698173761cddd814fa22e88b0661e98cb810f9f986c54aa34d281e4937", + "sha256:feea820722e69451743a3d56ad74948b68bf456984d63c1a92e8347b7b88452d" + ], + "markers": "python_version >= '3.7'", + "version": "==6.0.2" + }, "mysqlclient": { "hashes": [ "sha256:02c8826e6add9b20f4cb12dcf016485f7b1d6e30356a1204d05431867a1b3947", @@ -477,11 +757,11 @@ }, "pygments": { "hashes": [ - "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65", - "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a" + "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb", + "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519" ], - "markers": "python_version >= '3.5'", - "version": "==2.11.2" + "markers": "python_version >= '3.6'", + "version": "==2.12.0" }, "pyjwt": { "hashes": [ @@ -493,11 +773,11 @@ }, "pymdown-extensions": { "hashes": [ - "sha256:a80553b243d3ed2d6c27723bcd64ca9887e560e6f4808baa96f36e93061eaf90", - "sha256:b37461a181c1c8103cfe1660081726a0361a8294cbfda88e5b02cefe976f0546" + "sha256:1baa22a60550f731630474cad28feb0405c8101f1a7ddc3ec0ed86ee510bcc43", + "sha256:5b7432456bf555ce2b0ab3c2439401084cda8110f24f6b3ecef952b8313dfa1b" ], "markers": "python_version >= '3.7'", - "version": "==9.3" + "version": "==9.4" }, "pyparsing": { "hashes": [ @@ -507,6 +787,12 @@ "markers": "python_full_version >= '3.6.8'", "version": "==3.0.8" }, + "python-crontab": { + "hashes": [ + "sha256:1e35ed7a3cdc3100545b43e196d34754e6551e7f95e4caebbe0e1c0ca41c2f1b" + ], + "version": "==2.6.0" + }, "python-dateutil": { "hashes": [ "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86", @@ -630,7 +916,7 @@ "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0'", "version": "==1.26.9" }, "vine": { @@ -756,6 +1042,84 @@ "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", "version": "==1.14.0" }, + "yarl": { + "hashes": [ + "sha256:044daf3012e43d4b3538562da94a88fb12a6490652dbc29fb19adfa02cf72eac", + "sha256:0cba38120db72123db7c58322fa69e3c0efa933040ffb586c3a87c063ec7cae8", + "sha256:167ab7f64e409e9bdd99333fe8c67b5574a1f0495dcfd905bc7454e766729b9e", + "sha256:1be4bbb3d27a4e9aa5f3df2ab61e3701ce8fcbd3e9846dbce7c033a7e8136746", + "sha256:1ca56f002eaf7998b5fcf73b2421790da9d2586331805f38acd9997743114e98", + "sha256:1d3d5ad8ea96bd6d643d80c7b8d5977b4e2fb1bab6c9da7322616fd26203d125", + "sha256:1eb6480ef366d75b54c68164094a6a560c247370a68c02dddb11f20c4c6d3c9d", + "sha256:1edc172dcca3f11b38a9d5c7505c83c1913c0addc99cd28e993efeaafdfaa18d", + "sha256:211fcd65c58bf250fb994b53bc45a442ddc9f441f6fec53e65de8cba48ded986", + "sha256:29e0656d5497733dcddc21797da5a2ab990c0cb9719f1f969e58a4abac66234d", + "sha256:368bcf400247318382cc150aaa632582d0780b28ee6053cd80268c7e72796dec", + "sha256:39d5493c5ecd75c8093fa7700a2fb5c94fe28c839c8e40144b7ab7ccba6938c8", + "sha256:3abddf0b8e41445426d29f955b24aeecc83fa1072be1be4e0d194134a7d9baee", + "sha256:3bf8cfe8856708ede6a73907bf0501f2dc4e104085e070a41f5d88e7faf237f3", + "sha256:3ec1d9a0d7780416e657f1e405ba35ec1ba453a4f1511eb8b9fbab81cb8b3ce1", + "sha256:45399b46d60c253327a460e99856752009fcee5f5d3c80b2f7c0cae1c38d56dd", + "sha256:52690eb521d690ab041c3919666bea13ab9fbff80d615ec16fa81a297131276b", + "sha256:534b047277a9a19d858cde163aba93f3e1677d5acd92f7d10ace419d478540de", + "sha256:580c1f15500e137a8c37053e4cbf6058944d4c114701fa59944607505c2fe3a0", + "sha256:59218fef177296451b23214c91ea3aba7858b4ae3306dde120224cfe0f7a6ee8", + "sha256:5ba63585a89c9885f18331a55d25fe81dc2d82b71311ff8bd378fc8004202ff6", + "sha256:5bb7d54b8f61ba6eee541fba4b83d22b8a046b4ef4d8eb7f15a7e35db2e1e245", + "sha256:6152224d0a1eb254f97df3997d79dadd8bb2c1a02ef283dbb34b97d4f8492d23", + "sha256:67e94028817defe5e705079b10a8438b8cb56e7115fa01640e9c0bb3edf67332", + "sha256:695ba021a9e04418507fa930d5f0704edbce47076bdcfeeaba1c83683e5649d1", + "sha256:6a1a9fe17621af43e9b9fcea8bd088ba682c8192d744b386ee3c47b56eaabb2c", + "sha256:6ab0c3274d0a846840bf6c27d2c60ba771a12e4d7586bf550eefc2df0b56b3b4", + "sha256:6feca8b6bfb9eef6ee057628e71e1734caf520a907b6ec0d62839e8293e945c0", + "sha256:737e401cd0c493f7e3dd4db72aca11cfe069531c9761b8ea474926936b3c57c8", + "sha256:788713c2896f426a4e166b11f4ec538b5736294ebf7d5f654ae445fd44270832", + "sha256:797c2c412b04403d2da075fb93c123df35239cd7b4cc4e0cd9e5839b73f52c58", + "sha256:8300401dc88cad23f5b4e4c1226f44a5aa696436a4026e456fe0e5d2f7f486e6", + "sha256:87f6e082bce21464857ba58b569370e7b547d239ca22248be68ea5d6b51464a1", + "sha256:89ccbf58e6a0ab89d487c92a490cb5660d06c3a47ca08872859672f9c511fc52", + "sha256:8b0915ee85150963a9504c10de4e4729ae700af11df0dc5550e6587ed7891e92", + "sha256:8cce6f9fa3df25f55521fbb5c7e4a736683148bcc0c75b21863789e5185f9185", + "sha256:95a1873b6c0dd1c437fb3bb4a4aaa699a48c218ac7ca1e74b0bee0ab16c7d60d", + "sha256:9b4c77d92d56a4c5027572752aa35082e40c561eec776048330d2907aead891d", + "sha256:9bfcd43c65fbb339dc7086b5315750efa42a34eefad0256ba114cd8ad3896f4b", + "sha256:9c1f083e7e71b2dd01f7cd7434a5f88c15213194df38bc29b388ccdf1492b739", + "sha256:a1d0894f238763717bdcfea74558c94e3bc34aeacd3351d769460c1a586a8b05", + "sha256:a467a431a0817a292121c13cbe637348b546e6ef47ca14a790aa2fa8cc93df63", + "sha256:aa32aaa97d8b2ed4e54dc65d241a0da1c627454950f7d7b1f95b13985afd6c5d", + "sha256:ac10bbac36cd89eac19f4e51c032ba6b412b3892b685076f4acd2de18ca990aa", + "sha256:ac35ccde589ab6a1870a484ed136d49a26bcd06b6a1c6397b1967ca13ceb3913", + "sha256:bab827163113177aee910adb1f48ff7af31ee0289f434f7e22d10baf624a6dfe", + "sha256:baf81561f2972fb895e7844882898bda1eef4b07b5b385bcd308d2098f1a767b", + "sha256:bf19725fec28452474d9887a128e98dd67eee7b7d52e932e6949c532d820dc3b", + "sha256:c01a89a44bb672c38f42b49cdb0ad667b116d731b3f4c896f72302ff77d71656", + "sha256:c0910c6b6c31359d2f6184828888c983d54d09d581a4a23547a35f1d0b9484b1", + "sha256:c10ea1e80a697cf7d80d1ed414b5cb8f1eec07d618f54637067ae3c0334133c4", + "sha256:c1164a2eac148d85bbdd23e07dfcc930f2e633220f3eb3c3e2a25f6148c2819e", + "sha256:c145ab54702334c42237a6c6c4cc08703b6aa9b94e2f227ceb3d477d20c36c63", + "sha256:c17965ff3706beedafd458c452bf15bac693ecd146a60a06a214614dc097a271", + "sha256:c19324a1c5399b602f3b6e7db9478e5b1adf5cf58901996fc973fe4fccd73eed", + "sha256:c2a1ac41a6aa980db03d098a5531f13985edcb451bcd9d00670b03129922cd0d", + "sha256:c6ddcd80d79c96eb19c354d9dca95291589c5954099836b7c8d29278a7ec0bda", + "sha256:c9c6d927e098c2d360695f2e9d38870b2e92e0919be07dbe339aefa32a090265", + "sha256:cc8b7a7254c0fc3187d43d6cb54b5032d2365efd1df0cd1749c0c4df5f0ad45f", + "sha256:cff3ba513db55cc6a35076f32c4cdc27032bd075c9faef31fec749e64b45d26c", + "sha256:d260d4dc495c05d6600264a197d9d6f7fc9347f21d2594926202fd08cf89a8ba", + "sha256:d6f3d62e16c10e88d2168ba2d065aa374e3c538998ed04996cd373ff2036d64c", + "sha256:da6df107b9ccfe52d3a48165e48d72db0eca3e3029b5b8cb4fe6ee3cb870ba8b", + "sha256:dfe4b95b7e00c6635a72e2d00b478e8a28bfb122dc76349a06e20792eb53a523", + "sha256:e39378894ee6ae9f555ae2de332d513a5763276a9265f8e7cbaeb1b1ee74623a", + "sha256:ede3b46cdb719c794427dcce9d8beb4abe8b9aa1e97526cc20de9bd6583ad1ef", + "sha256:f2a8508f7350512434e41065684076f640ecce176d262a7d54f0da41d99c5a95", + "sha256:f44477ae29025d8ea87ec308539f95963ffdc31a82f42ca9deecf2d505242e72", + "sha256:f64394bd7ceef1237cc604b5a89bf748c95982a84bcd3c4bbeb40f685c810794", + "sha256:fc4dd8b01a8112809e6b636b00f487846956402834a7fd59d46d4f4267181c41", + "sha256:fce78593346c014d0d986b7ebc80d782b7f5e19843ca798ed62f8e3ba8728576", + "sha256:fd547ec596d90c8676e369dd8a581a21227fe9b4ad37d0dc7feb4ccf544c2d59" + ], + "markers": "python_version >= '3.6'", + "version": "==1.7.2" + }, "zipp": { "hashes": [ "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad", @@ -766,14 +1130,6 @@ } }, "develop": { - "appnope": { - "hashes": [ - "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24", - "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e" - ], - "markers": "sys_platform == 'darwin'", - "version": "==0.1.3" - }, "astroid": { "hashes": [ "sha256:4e5ba10571e197785e312966ea5efb2f5783176d4c1a73fa922d474ae2be59f7", @@ -852,19 +1208,19 @@ }, "identify": { "hashes": [ - "sha256:3f3244a559290e7d3deb9e9adc7b33594c1bc85a9dd82e0f1be519bf12a1ec17", - "sha256:5f06b14366bd1facb88b00540a1de05b69b310cbc2654db3c7e07fa3a4339323" + "sha256:3acfe15a96e4272b4ec5662ee3e231ceba976ef63fd9980ed2ce9cc415df393f", + "sha256:c83af514ea50bf2be2c4a3f2fb349442b59dc87284558ae9ff54191bff3541d2" ], "markers": "python_version >= '3.7'", - "version": "==2.4.12" + "version": "==2.5.0" }, "ipython": { "hashes": [ - "sha256:1b672bfd7a48d87ab203d9af8727a3b0174a4566b4091e9447c22fb63ea32857", - "sha256:70e5eb132cac594a34b5f799bd252589009905f05104728aea6a403ec2519dc1" + "sha256:341456643a764c28f670409bbd5d2518f9b82c013441084ff2c2fc999698f83b", + "sha256:807ae3cf43b84693c9272f70368440a9a7eaa2e7e6882dad943c32fbf7e51402" ], "index": "pypi", - "version": "==8.2.0" + "version": "==8.3.0" }, "isort": { "hashes": [ @@ -1019,11 +1375,11 @@ }, "pygments": { "hashes": [ - "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65", - "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a" + "sha256:5eb116118f9612ff1ee89ac96437bb6b49e8f04d8a13b514ba26f620208e26eb", + "sha256:dc9c10fb40944260f6ed4c688ece0cd2048414940f1cea51b8b226318411c519" ], - "markers": "python_version >= '3.5'", - "version": "==2.11.2" + "markers": "python_version >= '3.6'", + "version": "==2.12.0" }, "pylint": { "hashes": [ @@ -1072,6 +1428,14 @@ "markers": "python_version >= '3.6'", "version": "==6.0" }, + "setuptools": { + "hashes": [ + "sha256:26ead7d1f93efc0f8c804d9fafafbe4a44b179580a7105754b245155f9af05a8", + "sha256:47c7b0c0f8fc10eec4cf1e71c6fdadf8decaa74ffa087e68cd1c20db7ad6a592" + ], + "markers": "python_version >= '3.7'", + "version": "==62.1.0" + }, "six": { "hashes": [ "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", diff --git a/requirements-dev.txt b/requirements-dev.txt index e4362f8fc..d4df902ba 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,3 +1,10 @@ + + Warning: The lock flag -r/--requirements will be deprecated in a future version + of pipenv in favor of the new requirements command. For more info see + https://pipenv.pypa.io/en/latest/advanced/#generating-a-requirements-txt + NOTE: the requirements command parses Pipfile.lock directly without performing any + locking operations. Updating packages should be done by running pipenv lock + # # These requirements were autogenerated by pipenv # To regenerate from the project's Pipfile, run: @@ -9,16 +16,26 @@ # requirements. To emit only development requirements, pass "--dev-only". -i https://mirrors.aliyun.com/pypi/simple +aiohttp==3.8.1; python_version >= '3.6' +aiosignal==1.2.0; python_version >= '3.6' +alibabacloud-credentials==0.2.0; python_version >= '3.6' +alibabacloud-dysmsapi20170525==2.0.9 +alibabacloud-endpoint-util==0.0.3 +alibabacloud-gateway-spi==0.0.1; python_version >= '3.6' +alibabacloud-openapi-util==0.1.6 +alibabacloud-tea-openapi==0.3.3; python_version >= '3.6' +alibabacloud-tea-util==0.3.5; python_version >= '3.6' +alibabacloud-tea-xml==0.0.2 +alibabacloud-tea==0.2.9; python_version >= '3.6' amqp==5.1.1; python_version >= '3.6' -appnope==0.1.3; sys_platform == 'darwin' -asgiref==3.5.0; python_version >= '3.7' +asgiref==3.5.1; python_version >= '3.7' astroid==2.11.3; python_full_version >= '3.6.2' asttokens==2.0.5 astunparse==1.6.3; python_version < '3.9' async-timeout==4.0.2; python_version >= '3.6' +attrs==21.4.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' autopep8==1.6.0 backcall==0.2.0 -backports.zoneinfo==0.2.1; python_version < '3.9' billiard==3.6.4.0 celery==5.2.6 certifi==2021.10.8 @@ -28,25 +45,28 @@ charset-normalizer==2.0.12; python_version >= '3' click-didyoumean==0.3.0; python_full_version >= '3.6.2' and python_full_version < '4.0.0' click-plugins==1.1.1 click-repl==0.2.0 -click==8.1.2; python_version >= '3.7' -cryptography==36.0.2; python_version >= '3.6' +click==8.1.3; python_version >= '3.7' +cryptography==37.0.1; python_version >= '3.6' decorator==5.1.1; python_version >= '3.5' deprecated==1.2.13; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' dill==0.3.4; python_version >= '2.7' and python_version != '3.0' distlib==0.3.4 +django-celery-beat==2.2.1 django-ninja==0.17.0 -django==4.0.4; python_version >= '3.8' +django-timezone-field==4.2.3; python_version >= '3.5' +django==3.2.13; python_version >= '3.6' executing==0.8.3 filelock==3.6.0; python_version >= '3.7' +frozenlist==1.3.0; python_version >= '3.7' ghp-import==2.0.2 -identify==2.4.12; python_version >= '3.7' +identify==2.5.0; python_version >= '3.7' idna==3.3; python_version >= '3' -importlib-metadata==4.11.3; python_version < '3.10' -ipython==8.2.0 +importlib-metadata==4.11.3; python_version >= '3.7' +ipython==8.3.0 isort==5.10.1; python_version < '4.0' and python_full_version >= '3.6.1' jedi==0.18.1; python_version >= '3.6' -jinja2==3.1.1; python_version >= '3.7' -jwcrypto==1.0 +jinja2==3.1.2; python_version >= '3.7' +jwcrypto==1.2 kombu==5.2.4; python_version >= '3.7' lazy-object-proxy==1.7.1; python_version >= '3.6' markdown==3.3.6; python_version >= '3.6' @@ -56,10 +76,11 @@ mccabe==0.7.0; python_version >= '3.6' mergedeep==1.3.4; python_version >= '3.6' mkdocs-autorefs==0.4.1; python_version >= '3.7' mkdocs-material-extensions==1.0.3; python_version >= '3.6' -mkdocs-material==8.2.9 +mkdocs-material==8.2.12 mkdocs==1.3.0 mkdocstrings-python-legacy==0.2.2; python_version >= '3.7' mkdocstrings==0.18.1 +multidict==6.0.2; python_version >= '3.7' mysqlclient==2.1.0 nodeenv==1.6.0 oauthlib==3.2.0 @@ -75,11 +96,12 @@ pure-eval==0.2.2 pycodestyle==2.8.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' pycparser==2.21 pydantic==1.9.0; python_full_version >= '3.6.1' -pygments==2.11.2; python_version >= '3.5' +pygments==2.12.0; python_version >= '3.6' pyjwt==2.3.0 pylint==2.13.7 -pymdown-extensions==9.3; python_version >= '3.7' +pymdown-extensions==9.4; python_version >= '3.7' pyparsing==3.0.8; python_full_version >= '3.6.8' +python-crontab==2.6.0 python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' pytkdocs==0.16.1; python_version >= '3.7' pytz==2022.1 @@ -87,6 +109,7 @@ pyyaml-env-tag==0.1; python_version >= '3.6' pyyaml==6.0; python_version >= '3.6' redis==4.2.2 requests==2.27.1 +setuptools==62.1.0; python_version >= '3.7' six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' sqlparse==0.4.2; python_version >= '3.5' stack-data==0.2.0 @@ -94,11 +117,12 @@ toml==0.10.2 tomli==2.0.1; python_version < '3.11' traitlets==5.1.1; python_version >= '3.7' typing-extensions==4.2.0; python_version >= '3.7' -urllib3==1.26.9; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4' +urllib3==1.26.9; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0' vine==5.0.0; python_version >= '3.6' virtualenv==20.14.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' watchdog==2.1.7; python_version >= '3.6' wcwidth==0.2.5 wheel==0.37.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' wrapt==1.14.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +yarl==1.7.2; python_version >= '3.6' zipp==3.8.0; python_version >= '3.7' diff --git a/requirements.txt b/requirements.txt index 18b888121..aa6c11d26 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,10 @@ + + Warning: The lock flag -r/--requirements will be deprecated in a future version + of pipenv in favor of the new requirements command. For more info see + https://pipenv.pypa.io/en/latest/advanced/#generating-a-requirements-txt + NOTE: the requirements command parses Pipfile.lock directly without performing any + locking operations. Updating packages should be done by running pipenv lock + # # These requirements were autogenerated by pipenv # To regenerate from the project's Pipfile, run: @@ -6,11 +13,22 @@ # -i https://mirrors.aliyun.com/pypi/simple +aiohttp==3.8.1; python_version >= '3.6' +aiosignal==1.2.0; python_version >= '3.6' +alibabacloud-credentials==0.2.0; python_version >= '3.6' +alibabacloud-dysmsapi20170525==2.0.9 +alibabacloud-endpoint-util==0.0.3 +alibabacloud-gateway-spi==0.0.1; python_version >= '3.6' +alibabacloud-openapi-util==0.1.6 +alibabacloud-tea-openapi==0.3.3; python_version >= '3.6' +alibabacloud-tea-util==0.3.5; python_version >= '3.6' +alibabacloud-tea-xml==0.0.2 +alibabacloud-tea==0.2.9; python_version >= '3.6' amqp==5.1.1; python_version >= '3.6' -asgiref==3.5.0; python_version >= '3.7' +asgiref==3.5.1; python_version >= '3.7' astunparse==1.6.3; python_version < '3.9' async-timeout==4.0.2; python_version >= '3.6' -backports.zoneinfo==0.2.1; python_version < '3.9' +attrs==21.4.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' billiard==3.6.4.0 celery==5.2.6 certifi==2021.10.8 @@ -19,36 +37,41 @@ charset-normalizer==2.0.12; python_version >= '3' click-didyoumean==0.3.0; python_full_version >= '3.6.2' and python_full_version < '4.0.0' click-plugins==1.1.1 click-repl==0.2.0 -click==8.1.2; python_version >= '3.7' -cryptography==36.0.2; python_version >= '3.6' +click==8.1.3; python_version >= '3.7' +cryptography==37.0.1; python_version >= '3.6' deprecated==1.2.13; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' +django-celery-beat==2.2.1 django-ninja==0.17.0 -django==4.0.4; python_version >= '3.8' +django-timezone-field==4.2.3; python_version >= '3.5' +django==3.2.13; python_version >= '3.6' +frozenlist==1.3.0; python_version >= '3.7' ghp-import==2.0.2 idna==3.3; python_version >= '3' -importlib-metadata==4.11.3; python_version < '3.10' -jinja2==3.1.1; python_version >= '3.7' -jwcrypto==1.0 +importlib-metadata==4.11.3; python_version >= '3.7' +jinja2==3.1.2; python_version >= '3.7' +jwcrypto==1.2 kombu==5.2.4; python_version >= '3.7' markdown==3.3.6; python_version >= '3.6' markupsafe==2.1.1; python_version >= '3.7' mergedeep==1.3.4; python_version >= '3.6' mkdocs-autorefs==0.4.1; python_version >= '3.7' mkdocs-material-extensions==1.0.3; python_version >= '3.6' -mkdocs-material==8.2.9 +mkdocs-material==8.2.12 mkdocs==1.3.0 mkdocstrings-python-legacy==0.2.2; python_version >= '3.7' mkdocstrings==0.18.1 +multidict==6.0.2; python_version >= '3.7' mysqlclient==2.1.0 oauthlib==3.2.0 packaging==21.3; python_version >= '3.6' prompt-toolkit==3.0.29; python_full_version >= '3.6.2' pycparser==2.21 pydantic==1.9.0; python_full_version >= '3.6.1' -pygments==2.11.2; python_version >= '3.5' +pygments==2.12.0; python_version >= '3.6' pyjwt==2.3.0 -pymdown-extensions==9.3; python_version >= '3.7' +pymdown-extensions==9.4; python_version >= '3.7' pyparsing==3.0.8; python_full_version >= '3.6.8' +python-crontab==2.6.0 python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' pytkdocs==0.16.1; python_version >= '3.7' pytz==2022.1 @@ -60,10 +83,11 @@ six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3 sqlparse==0.4.2; python_version >= '3.5' toml==0.10.2 typing-extensions==4.2.0; python_version >= '3.7' -urllib3==1.26.9; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4' +urllib3==1.26.9; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_full_version < '4.0.0' vine==5.0.0; python_version >= '3.6' watchdog==2.1.7; python_version >= '3.6' wcwidth==0.2.5 wheel==0.37.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' wrapt==1.14.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' +yarl==1.7.2; python_version >= '3.6' zipp==3.8.0; python_version >= '3.7' From 83210e2e885f743036be5cc3480e98976be4392f Mon Sep 17 00:00:00 2001 From: fanhe Date: Sun, 1 May 2022 09:44:51 +0800 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=F0=9F=90=9B=20=E6=B7=BB=E5=8A=A0dja?= =?UTF-8?q?ngo-cors-headers=E4=BE=9D=E8=B5=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Pipfile.lock | 2 +- requirements.txt | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Pipfile.lock b/Pipfile.lock index f3953c2ef..97da087c2 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "f44dca3e46cb58345011f2cec1ab1d1e4cd45cbd45f2bd8142358bc3cb372135" + "sha256": "85c8efe048ded153f0c1bc725e07b631df4e5824967609ca4c25f40a44b3c1e7" }, "pipfile-spec": 6, "requires": { diff --git a/requirements.txt b/requirements.txt index 3d3aa178f..88c298305 100644 --- a/requirements.txt +++ b/requirements.txt @@ -41,9 +41,6 @@ click==8.1.3; python_version >= '3.7' cryptography==37.0.1; python_version >= '3.6' deprecated==1.2.13; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' django-celery-beat==2.2.1 -click==8.1.2; python_version >= '3.7' -cryptography==37.0.0; python_version >= '3.6' -deprecated==1.2.13; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' django-cors-headers==3.11.0 django-ninja==0.17.0 django-timezone-field==4.2.3; python_version >= '3.5'