fastapi-rtk 2.21.2__tar.gz → 2.22.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/PKG-INFO +25 -1
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/README.md +22 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/__init__.py +4 -0
- fastapi_rtk-2.22.0/fastapi_rtk/_version.py +1 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/guide/backend.md +29 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/__init__.py +8 -4
- fastapi_rtk-2.22.0/fastapi_rtk/auth/saml.py +376 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/decorators.py +5 -1
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/const.py +2 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/routers.py +201 -2
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/apis.py +14 -2
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/async_task_runner.py +9 -16
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/pyproject.toml +1 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/conftest.py +205 -0
- fastapi_rtk-2.22.0/tests/integration/auth/test_saml.py +383 -0
- fastapi_rtk-2.22.0/tests/unit/auth/test_saml.py +277 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/test_decorators.py +31 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_const.py +2 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_async_task_runner.py +98 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/uv.lock +207 -1
- fastapi_rtk-2.21.2/fastapi_rtk/_version.py +0 -1
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/.gitignore +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/LICENSE +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/adapters/agents-md/AGENTS.md +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/adapters/claude/skill.frontmatter +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/guide/frontend.md +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/guide/jsonforms.md +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/guide/legacy.md +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/agent_docs/guide/overview.md +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/base_api.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/model_rest_api.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/params.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_add_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_bulk_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_delete_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_download_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_edit_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_file_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_info_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_list_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_lookup_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_schema_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/api/model_rest_api/services/_show_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/apis/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/apis/info.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/apis/license.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/auth.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/hashers/pbkdf2.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/hashers/scrypt.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/ldap.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/password_helpers/fab.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/strategies/config.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/strategies/db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/auth/strategies/jwt.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/column.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/exceptions.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/filters.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/interface.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/model.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/generic/session.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/column.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/exceptions.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/_decorators_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/_lifecycle_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/_logger.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/_processing_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/audit.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/audit/types.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/filters.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/extensions/geoalchemy2/geometry_converter.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/filters.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/interface.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/model.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/backends/sqla/session.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/cache.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/file_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/filter.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/interface.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/loop_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/model.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/session.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/bases/user_cache.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/caches/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/caches/cache.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/caches/serializers.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/caches/storages.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/cli.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/_security_crypto.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/adapters/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/adapters/agents_md.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/adapters/claude.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/base.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/agent/docs.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi/README +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi/alembic.ini.mako +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi/env.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi/script.py.mako +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/README +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/alembic.ini.mako +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/env.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/db/templates/fastapi-multidb/script.py.mako +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/export.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/security.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/commands/translate.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/const.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/types.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/cli/utils.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/config.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/decorators.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/dependencies.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/exceptions.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/file_managers/file_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/file_managers/image_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/file_managers/s3_file_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/file_managers/s3_image_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/filters.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/globals.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/babel/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/babel/cli.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/babel/config.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/babel.cfg +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/lazy_text.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/messages.pot +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/translations/de/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.mo +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/lang/translations/en/LC_MESSAGES/messages.po +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/loop_managers/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/loop_managers/loop_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/middlewares.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/mixins.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/models.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/py.typed +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/registry.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/schemas.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/models.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_builtin_role_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_entities.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_exception_filters.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_export.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_import.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_permission_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_results.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_role_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/_user_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/security_manager/security_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/security/sqla/user_cache.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/setting.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/types.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/class_factory.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/csv_json_converter.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/deep_merge.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/extender_mixin.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/formatter.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/hooks.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/lazy.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/merge_schema.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/multiple_async_contexts.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/pydantic.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/run_utils.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/self_dependencies.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/smartdefaultdict.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/sqla.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/timezone.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/update_signature.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/utils/use_default_when_none.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/fastapi_rtk/version.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/docker-compose.gis.yml +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/api/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/api/test_add_edit_relation_response.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/api/test_lookup.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/api/test_model_rest_api.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/apis/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/apis/test_info_callable_schema.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/apis/test_info_contract.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/auth/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/auth/test_auth_flow.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/auth/test_oauth.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/conftest.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/file_managers/test_file_routes.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/conftest.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/test_permission_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/test_role_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/test_security_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/security/sqla/test_user_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/integration/test_app_smoke.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/conftest.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_columns.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_download_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_file_mixin.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_init.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_lookup_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/model_rest_api/test_service_delegators.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/api/test_base_api.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/apis/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/apis/test_info.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/apis/test_license.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/hashers/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/hashers/test_pbkdf2.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/hashers/test_scrypt.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/password_helpers/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/password_helpers/test_fab.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/strategies/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/strategies/test_config.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/strategies/test_db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/strategies/test_jwt.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/auth/test_auth.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_column.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_exceptions.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_filters.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_interface.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_model.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/generic/test_session.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/conftest.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_audit.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_audit_service_delegators.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_audit_types.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_column.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_filters.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_geoalchemy2.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_interface.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_model.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/backends/sqla/test_session.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_file_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_filter.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_interface.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_model.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/bases/test_session.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/conftest.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/test_db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/test_export.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/test_security.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/test_security_crypto.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/commands/test_translate.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/conftest.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/test_cli.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/test_types.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/cli/test_utils.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/conftest.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/test_file_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/test_image_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/test_s3_file_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/file_managers/test_s3_image_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/lang/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/lang/test_babel_cli.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/lang/test_babel_config.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/lang/test_lazy_text.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/conftest.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_builtin_role_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_entities.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_permission_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_results.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_role_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/security_manager/test_user_service.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/test_apis.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/test_models.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/security/sqla/test_security_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_caches.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_config.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_db.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_decorators.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_dependencies.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_exceptions.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_fastapi_react_toolkit.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_filters.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_globals.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_manager.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_middlewares.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_mixins.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_models.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_registry.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_routers.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_schemas.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_setting.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_types.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_user_cache.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/test_version.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/__init__.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_class_factory.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_csv_json_converter.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_deep_merge.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_extender_mixin.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_flask_appbuilder_utils.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_formatter.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_hooks.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_lazy.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_merge_schema.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_multiple_async_contexts.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_pydantic.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_run_utils.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_self_dependencies.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_smartdefaultdict.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_sqla.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_timezone.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_update_signature.py +0 -0
- {fastapi_rtk-2.21.2 → fastapi_rtk-2.22.0}/tests/unit/utils/test_use_default_when_none.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: fastapi-rtk
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.22.0
|
|
4
4
|
Summary: A package that provides a set of tools to build a FastAPI application with a Class-Based CRUD API.
|
|
5
5
|
Project-URL: Homepage, https://codeberg.org/datatactics/fastapi-rtk
|
|
6
6
|
Project-URL: Issues, https://codeberg.org/datatactics/fastapi-rtk/issues
|
|
@@ -50,6 +50,8 @@ Requires-Dist: pyinstrument>=5.1.2; extra == 'profiler'
|
|
|
50
50
|
Provides-Extra: s3
|
|
51
51
|
Requires-Dist: smart-open[s3]>=7.5.0; extra == 's3'
|
|
52
52
|
Requires-Dist: urllib3>=2.7.0; extra == 's3'
|
|
53
|
+
Provides-Extra: saml
|
|
54
|
+
Requires-Dist: python3-saml>=1.16.0; extra == 'saml'
|
|
53
55
|
Provides-Extra: test
|
|
54
56
|
Requires-Dist: aiosqlite>=0.21.0; extra == 'test'
|
|
55
57
|
Requires-Dist: pytest-asyncio>=1.3.0; extra == 'test'
|
|
@@ -351,6 +353,28 @@ A single adapter can also be swapped on the web `Provider` via `adapters={{ stor
|
|
|
351
353
|
|
|
352
354
|
See the wiki page [React Native and Adapters](https://codeberg.org/datatactics/fastapi-rtk/wiki/06.04-React-Native-and-Adapters) for the full adapter reference (interfaces, per-adapter web/RN table, auth transports).
|
|
353
355
|
|
|
356
|
+
## Signatures
|
|
357
|
+
|
|
358
|
+
The wheel and sdist of every PyPI release and the tarball of every npm release are signed with [cosign](https://github.com/sigstore/cosign). These files and their signature bundles (`<file>.sigstore.json`) are attached to the [release on Codeberg](https://codeberg.org/datatactics/fastapi-rtk/releases). Verify a downloaded file against the public key in this repo ([`cosign.pub`](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/cosign.pub)):
|
|
359
|
+
|
|
360
|
+
```sh
|
|
361
|
+
pip download --no-deps fastapi-rtk==<version>
|
|
362
|
+
cosign verify-blob --key cosign.pub \
|
|
363
|
+
--bundle fastapi_rtk-<version>-py3-none-any.whl.sigstore.json \
|
|
364
|
+
fastapi_rtk-<version>-py3-none-any.whl
|
|
365
|
+
|
|
366
|
+
npm pack fastapi-rtk@<version>
|
|
367
|
+
cosign verify-blob --key cosign.pub \
|
|
368
|
+
--bundle fastapi-rtk-<version>.tgz.sigstore.json \
|
|
369
|
+
fastapi-rtk-<version>.tgz
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
[`scripts/verify-release.sh`](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/scripts/verify-release.sh) runs that check for all three files of a release, downloading them from PyPI and npm and their bundles from the Codeberg release (needs `curl`, `jq` and `cosign`):
|
|
373
|
+
|
|
374
|
+
```sh
|
|
375
|
+
scripts/verify-release.sh <version>
|
|
376
|
+
```
|
|
377
|
+
|
|
354
378
|
## License
|
|
355
379
|
|
|
356
380
|
FastAPI-RTK is licensed under the [MIT license](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/LICENSE).
|
|
@@ -292,6 +292,28 @@ A single adapter can also be swapped on the web `Provider` via `adapters={{ stor
|
|
|
292
292
|
|
|
293
293
|
See the wiki page [React Native and Adapters](https://codeberg.org/datatactics/fastapi-rtk/wiki/06.04-React-Native-and-Adapters) for the full adapter reference (interfaces, per-adapter web/RN table, auth transports).
|
|
294
294
|
|
|
295
|
+
## Signatures
|
|
296
|
+
|
|
297
|
+
The wheel and sdist of every PyPI release and the tarball of every npm release are signed with [cosign](https://github.com/sigstore/cosign). These files and their signature bundles (`<file>.sigstore.json`) are attached to the [release on Codeberg](https://codeberg.org/datatactics/fastapi-rtk/releases). Verify a downloaded file against the public key in this repo ([`cosign.pub`](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/cosign.pub)):
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
pip download --no-deps fastapi-rtk==<version>
|
|
301
|
+
cosign verify-blob --key cosign.pub \
|
|
302
|
+
--bundle fastapi_rtk-<version>-py3-none-any.whl.sigstore.json \
|
|
303
|
+
fastapi_rtk-<version>-py3-none-any.whl
|
|
304
|
+
|
|
305
|
+
npm pack fastapi-rtk@<version>
|
|
306
|
+
cosign verify-blob --key cosign.pub \
|
|
307
|
+
--bundle fastapi-rtk-<version>.tgz.sigstore.json \
|
|
308
|
+
fastapi-rtk-<version>.tgz
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
[`scripts/verify-release.sh`](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/scripts/verify-release.sh) runs that check for all three files of a release, downloading them from PyPI and npm and their bundles from the Codeberg release (needs `curl`, `jq` and `cosign`):
|
|
312
|
+
|
|
313
|
+
```sh
|
|
314
|
+
scripts/verify-release.sh <version>
|
|
315
|
+
```
|
|
316
|
+
|
|
295
317
|
## License
|
|
296
318
|
|
|
297
319
|
FastAPI-RTK is licensed under the [MIT license](https://codeberg.org/datatactics/fastapi-rtk/src/branch/main/LICENSE).
|
|
@@ -61,6 +61,9 @@ __all__ = [
|
|
|
61
61
|
"BearerConfig",
|
|
62
62
|
"CookieConfig",
|
|
63
63
|
"StrategyConfig",
|
|
64
|
+
"SAMLClient",
|
|
65
|
+
"SAMLIdentity",
|
|
66
|
+
"SAMLResponseError",
|
|
64
67
|
# .backends.generic
|
|
65
68
|
"GenericPKAutoIncrement",
|
|
66
69
|
"GenericUnset",
|
|
@@ -245,6 +248,7 @@ __all__ = [
|
|
|
245
248
|
# .routers
|
|
246
249
|
"get_auth_router",
|
|
247
250
|
"get_oauth_router",
|
|
251
|
+
"get_saml_router",
|
|
248
252
|
"get_oauth_associate_router",
|
|
249
253
|
# .schemas
|
|
250
254
|
"PRIMARY_KEY",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "2.22.0"
|
|
@@ -714,6 +714,35 @@ The toolkit wires the login/callback routes; the frontend's OAuth sign-in hook k
|
|
|
714
714
|
|
|
715
715
|
A `"backend": "jwt"` client (for clients without a browser cookie store, such as React Native; web apps default to `cookie` and can opt in) answers the callback with `{"access_token", "token_type": "bearer"}` JSON instead of a cookie, so the client has to make the callback request itself: point `redirect_url` (or `redirect_url_factory`) at the URL the app receives the redirect on (on React Native the same URL goes to `createReactNativeAdapters({ oauthRedirectUrl })`, and `oauthSignin` then completes the sign-in itself), or call `oauthCallback("<name>", redirectQuery)` from `useAuth()` with that URL's query string, which forwards `code` and `state` to `GET /api/v1/auth/oauth-authorized/<name>` and stores the returned token. A JWT client rejects `redirect_url_after_callback` with a `ValueError` at startup and a popup login (`?popup=true`) with 400 `OAUTH_POPUP_NOT_SUPPORTED`, since both would drop the token.
|
|
716
716
|
|
|
717
|
+
Entry names must be unique across `OAUTH_CLIENTS`; a duplicate raises `ValueError` at startup.
|
|
718
|
+
|
|
719
|
+
A SAML 2.0 IdP (Microsoft Entra ID, for example) is an ordinary entry whose `oauth_client` is a `SAMLClient` (`from fastapi_rtk import SAMLClient`, needs `pip install fastapi-rtk[saml]`). Give it `name`, `sp_entity_id`, and either `idp_metadata_url` or all of `idp_entity_id`, `idp_sso_url`, `idp_cert`; `account_id_attribute` / `email_attribute` name the assertion attributes to read (the NameID when `None`). The other entry keys keep their OAuth meaning: `redirect_url` is the ACS URL registered at the IdP, `backend` must be `cookie` (`jwt` raises `ValueError`, since the IdP posts to the browser), and the hooks receive `oauth_token={"attributes": {...}, "nameid": ..., "assertion_id": ...}`, so roles come from returning `role_keys`, e.g. the Entra groups claim. Routes: `GET /api/v1/auth/login/<name>` (popup supported, so `oauthSignin('<name>')` works unchanged) and the ACS `POST /api/v1/auth/oauth-authorized/<name>`, which answers 303 to `redirect_url_after_callback`. A failed validation is 400 `SAML_RESPONSE_INVALID`, a reused assertion 400 `SAML_ASSERTION_REPLAYED` (tracked in `g.cache` until the assertion expires). The default `InMemoryCacheStorage` is per process, so with several workers or replicas a replay that lands on another worker passes: such a deployment must set `CACHE` to a storage every worker shares (a custom `AbstractCacheStorage`, since the toolkit ships none). The check and the record are separate calls: the default in-memory storage never suspends between them, but with a storage whose `get` awaits a network round trip (Redis, say) two simultaneous posts of one assertion can both pass. IdP-initiated logins are rejected, and login CSRF is not prevented, as for OAuth.
|
|
720
|
+
|
|
721
|
+
```python
|
|
722
|
+
from fastapi_rtk import SAMLClient
|
|
723
|
+
|
|
724
|
+
OAUTH_PROVIDERS = [
|
|
725
|
+
{
|
|
726
|
+
"oauth_client": SAMLClient(
|
|
727
|
+
name="entra",
|
|
728
|
+
sp_entity_id="my-app",
|
|
729
|
+
idp_entity_id="https://sts.windows.net/<tenant>/",
|
|
730
|
+
idp_sso_url="https://login.microsoftonline.com/<tenant>/saml2",
|
|
731
|
+
idp_cert="MIIC...",
|
|
732
|
+
account_id_attribute="http://schemas.microsoft.com/identity/claims/objectidentifier",
|
|
733
|
+
email_attribute="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
|
|
734
|
+
),
|
|
735
|
+
"redirect_url": "https://<host>/api/v1/auth/oauth-authorized/entra",
|
|
736
|
+
"associate_by_email": True,
|
|
737
|
+
"on_before_login": lambda user, oauth_token: {
|
|
738
|
+
"role_keys": oauth_token["attributes"].get(
|
|
739
|
+
"http://schemas.microsoft.com/ws/2008/06/identity/claims/groups", []
|
|
740
|
+
)
|
|
741
|
+
},
|
|
742
|
+
},
|
|
743
|
+
]
|
|
744
|
+
```
|
|
745
|
+
|
|
717
746
|
## 16. Migrations and user management CLI
|
|
718
747
|
|
|
719
748
|
```bash
|
|
@@ -7,13 +7,17 @@ from .auth import (
|
|
|
7
7
|
StrategyConfig,
|
|
8
8
|
)
|
|
9
9
|
from .password_helpers import FABPasswordHelper
|
|
10
|
+
from .saml import SAMLClient, SAMLIdentity, SAMLResponseError
|
|
10
11
|
|
|
11
12
|
__all__ = [
|
|
12
13
|
"FABPasswordHelper",
|
|
13
|
-
"CookieConfig",
|
|
14
|
-
"BearerConfig",
|
|
15
|
-
"StrategyConfig",
|
|
16
|
-
"Auth",
|
|
17
14
|
"Authenticator",
|
|
18
15
|
"AuthConfigurator",
|
|
16
|
+
"Auth",
|
|
17
|
+
"BearerConfig",
|
|
18
|
+
"CookieConfig",
|
|
19
|
+
"StrategyConfig",
|
|
20
|
+
"SAMLClient",
|
|
21
|
+
"SAMLIdentity",
|
|
22
|
+
"SAMLResponseError",
|
|
19
23
|
]
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
"""SAML 2.0 service provider client, registered as an `OAUTH_CLIENTS` entry."""
|
|
2
|
+
|
|
3
|
+
import collections.abc
|
|
4
|
+
import dataclasses
|
|
5
|
+
import importlib
|
|
6
|
+
import math
|
|
7
|
+
import time
|
|
8
|
+
import types
|
|
9
|
+
import typing
|
|
10
|
+
import urllib.parse
|
|
11
|
+
|
|
12
|
+
from ..utils import smart_run
|
|
13
|
+
|
|
14
|
+
if typing.TYPE_CHECKING:
|
|
15
|
+
import onelogin.saml2.settings
|
|
16
|
+
|
|
17
|
+
__all__ = ["SAMLClient", "SAMLIdentity", "SAMLResponseError"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclasses.dataclass
|
|
21
|
+
class SAMLIdentity:
|
|
22
|
+
"""
|
|
23
|
+
The identity read off a validated SAML response.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
account_id: str
|
|
27
|
+
account_email: str | None
|
|
28
|
+
assertion_id: str
|
|
29
|
+
not_on_or_after: int | None
|
|
30
|
+
nameid: str
|
|
31
|
+
attributes: dict[str, list[str]]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class SAMLResponseError(Exception):
|
|
35
|
+
"""
|
|
36
|
+
A SAML response failed validation.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, errors: list[str], reason: str | None):
|
|
40
|
+
"""
|
|
41
|
+
Create the error from python3-saml's error codes and reason.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
errors: The python3-saml error codes.
|
|
45
|
+
reason: The python3-saml reason for the last error.
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(f"{', '.join(errors)}: {reason}")
|
|
48
|
+
self.errors = errors
|
|
49
|
+
self.reason = reason
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SAMLClient:
|
|
53
|
+
"""
|
|
54
|
+
A SAML 2.0 identity provider, used as the `oauth_client` of an `OAUTH_CLIENTS` entry.
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
def __init__(
|
|
58
|
+
self,
|
|
59
|
+
name: str,
|
|
60
|
+
sp_entity_id: str,
|
|
61
|
+
idp_entity_id: str | None = None,
|
|
62
|
+
idp_sso_url: str | None = None,
|
|
63
|
+
idp_cert: str | None = None,
|
|
64
|
+
idp_metadata_url: str | None = None,
|
|
65
|
+
account_id_attribute: str | None = None,
|
|
66
|
+
email_attribute: str | None = None,
|
|
67
|
+
):
|
|
68
|
+
"""
|
|
69
|
+
Configure the client; IdP fields left None come from `idp_metadata_url`.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
name: Provider name, the last path segment of the login and ACS routes.
|
|
73
|
+
sp_entity_id: Entity ID of this service provider at the IdP.
|
|
74
|
+
idp_entity_id: Entity ID (issuer) of the IdP.
|
|
75
|
+
idp_sso_url: HTTP-Redirect single sign-on URL of the IdP.
|
|
76
|
+
idp_cert: Base64 X.509 signing certificate of the IdP.
|
|
77
|
+
idp_metadata_url: IdP metadata URL, source of every IdP field left None.
|
|
78
|
+
account_id_attribute: Attribute holding the account id; the NameID when None.
|
|
79
|
+
email_attribute: Attribute holding the email; the NameID when None.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
ValueError: No metadata URL and one of the three IdP fields is missing.
|
|
83
|
+
ImportError: The `saml` extra is not installed.
|
|
84
|
+
"""
|
|
85
|
+
if not idp_metadata_url and not (idp_entity_id and idp_sso_url and idp_cert):
|
|
86
|
+
raise ValueError(
|
|
87
|
+
f"SAML client {name!r} needs idp_metadata_url or all of "
|
|
88
|
+
"idp_entity_id, idp_sso_url and idp_cert"
|
|
89
|
+
)
|
|
90
|
+
self.name = name
|
|
91
|
+
self.sp_entity_id = sp_entity_id
|
|
92
|
+
self.idp_entity_id = idp_entity_id
|
|
93
|
+
self.idp_sso_url = idp_sso_url
|
|
94
|
+
self.idp_cert = idp_cert
|
|
95
|
+
self.idp_metadata_url = idp_metadata_url
|
|
96
|
+
self.account_id_attribute = account_id_attribute
|
|
97
|
+
self.email_attribute = email_attribute
|
|
98
|
+
self._idp: dict | None = None
|
|
99
|
+
# Fails at startup rather than at the first login when the extra is missing.
|
|
100
|
+
_onelogin("onelogin.saml2.settings")
|
|
101
|
+
|
|
102
|
+
async def get_authorization_url(
|
|
103
|
+
self, acs_url: str, relay_state: collections.abc.Callable[[str], str]
|
|
104
|
+
) -> str:
|
|
105
|
+
"""
|
|
106
|
+
Build the IdP redirect URL for a new AuthnRequest.
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
acs_url: The ACS URL the IdP posts the response to.
|
|
110
|
+
relay_state: Called with the AuthnRequest ID, returns the RelayState.
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
str: The IdP URL to redirect the browser to.
|
|
114
|
+
"""
|
|
115
|
+
return await smart_run(self._authorization_url, acs_url, relay_state)
|
|
116
|
+
|
|
117
|
+
async def process_response(
|
|
118
|
+
self, acs_url: str, post_data: dict[str, str], request_id: str
|
|
119
|
+
) -> SAMLIdentity:
|
|
120
|
+
"""
|
|
121
|
+
Validate a posted SAML response.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
acs_url: The ACS URL the response must be addressed to.
|
|
125
|
+
post_data: The posted form fields, `SAMLResponse` included.
|
|
126
|
+
request_id: The AuthnRequest ID the response must answer.
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
SAMLIdentity: The authenticated identity.
|
|
130
|
+
|
|
131
|
+
Raises:
|
|
132
|
+
SAMLResponseError: The response failed validation.
|
|
133
|
+
"""
|
|
134
|
+
return await smart_run(self._process_response, acs_url, post_data, request_id)
|
|
135
|
+
|
|
136
|
+
def _authorization_url(
|
|
137
|
+
self, acs_url: str, relay_state: collections.abc.Callable[[str], str]
|
|
138
|
+
) -> str:
|
|
139
|
+
"""
|
|
140
|
+
Build the IdP redirect URL; the blocking half of `get_authorization_url`.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
acs_url: The ACS URL the IdP posts the response to.
|
|
144
|
+
relay_state: Called with the AuthnRequest ID, returns the RelayState.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
str: The IdP URL to redirect the browser to.
|
|
148
|
+
"""
|
|
149
|
+
authn_request_module = _onelogin("onelogin.saml2.authn_request")
|
|
150
|
+
utils = _onelogin("onelogin.saml2.utils")
|
|
151
|
+
settings = self._settings(acs_url)
|
|
152
|
+
authn_request = authn_request_module.OneLogin_Saml2_Authn_Request(settings)
|
|
153
|
+
return utils.OneLogin_Saml2_Utils.redirect(
|
|
154
|
+
settings.get_idp_sso_url(),
|
|
155
|
+
{
|
|
156
|
+
"SAMLRequest": authn_request.get_request(),
|
|
157
|
+
"RelayState": relay_state(authn_request.get_id()),
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
def _process_response(
|
|
162
|
+
self, acs_url: str, post_data: dict[str, str], request_id: str
|
|
163
|
+
) -> SAMLIdentity:
|
|
164
|
+
"""
|
|
165
|
+
Validate a posted SAML response; the blocking half of `process_response`.
|
|
166
|
+
|
|
167
|
+
Args:
|
|
168
|
+
acs_url: The ACS URL the response must be addressed to.
|
|
169
|
+
post_data: The posted form fields, `SAMLResponse` included.
|
|
170
|
+
request_id: The AuthnRequest ID the response must answer.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
SAMLIdentity: The authenticated identity.
|
|
174
|
+
|
|
175
|
+
Raises:
|
|
176
|
+
SAMLResponseError: The response failed validation.
|
|
177
|
+
"""
|
|
178
|
+
response_module = _onelogin("onelogin.saml2.response")
|
|
179
|
+
settings = self._settings(acs_url)
|
|
180
|
+
try:
|
|
181
|
+
response = response_module.OneLogin_Saml2_Response(
|
|
182
|
+
settings, post_data.get("SAMLResponse", "")
|
|
183
|
+
)
|
|
184
|
+
valid = response.is_valid(_request_data(acs_url), request_id)
|
|
185
|
+
except Exception as e:
|
|
186
|
+
# The response is decoded and parsed in the constructor, so garbage raises here.
|
|
187
|
+
raise SAMLResponseError(["invalid_response"], repr(e)) from e
|
|
188
|
+
if not valid:
|
|
189
|
+
raise SAMLResponseError(["invalid_response"], response.get_error())
|
|
190
|
+
# The outer Response InResponseTo is unsigned, and python3-saml checks the signed one only when both are present.
|
|
191
|
+
signed_in_response_to = [
|
|
192
|
+
node.get("InResponseTo")
|
|
193
|
+
for node in response._query_assertion(
|
|
194
|
+
"/saml:Subject/saml:SubjectConfirmation/saml:SubjectConfirmationData"
|
|
195
|
+
)
|
|
196
|
+
]
|
|
197
|
+
if request_id not in signed_in_response_to:
|
|
198
|
+
raise SAMLResponseError(
|
|
199
|
+
["invalid_in_response_to"],
|
|
200
|
+
f"signed InResponseTo {signed_in_response_to!r} does not match {request_id!r}",
|
|
201
|
+
)
|
|
202
|
+
try:
|
|
203
|
+
# is_valid leaves NameID and duplicate-attribute checks to these getters.
|
|
204
|
+
attributes = response.get_attributes()
|
|
205
|
+
nameid = response.get_nameid()
|
|
206
|
+
assertion_id = response.get_assertion_id()
|
|
207
|
+
not_on_or_after = response.get_assertion_not_on_or_after()
|
|
208
|
+
except Exception as e:
|
|
209
|
+
raise SAMLResponseError(["invalid_response"], repr(e)) from e
|
|
210
|
+
account_id = nameid
|
|
211
|
+
if self.account_id_attribute:
|
|
212
|
+
account_id = _first(attributes, self.account_id_attribute)
|
|
213
|
+
if account_id is None:
|
|
214
|
+
raise SAMLResponseError(
|
|
215
|
+
["missing_account_id"],
|
|
216
|
+
f"attribute {self.account_id_attribute!r} is missing",
|
|
217
|
+
)
|
|
218
|
+
return SAMLIdentity(
|
|
219
|
+
account_id=account_id,
|
|
220
|
+
account_email=_first(attributes, self.email_attribute)
|
|
221
|
+
if self.email_attribute
|
|
222
|
+
else nameid,
|
|
223
|
+
assertion_id=assertion_id,
|
|
224
|
+
not_on_or_after=not_on_or_after,
|
|
225
|
+
nameid=nameid,
|
|
226
|
+
attributes=attributes,
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
def _settings(
|
|
230
|
+
self, acs_url: str
|
|
231
|
+
) -> "onelogin.saml2.settings.OneLogin_Saml2_Settings":
|
|
232
|
+
"""
|
|
233
|
+
Build the python3-saml settings for one ACS URL.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
acs_url: The ACS URL of this service provider.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
OneLogin_Saml2_Settings: The validated settings.
|
|
240
|
+
"""
|
|
241
|
+
settings_module = _onelogin("onelogin.saml2.settings")
|
|
242
|
+
return settings_module.OneLogin_Saml2_Settings(
|
|
243
|
+
{
|
|
244
|
+
"strict": True,
|
|
245
|
+
"debug": False,
|
|
246
|
+
"sp": {
|
|
247
|
+
"entityId": self.sp_entity_id,
|
|
248
|
+
"assertionConsumerService": {
|
|
249
|
+
"url": acs_url,
|
|
250
|
+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
|
251
|
+
},
|
|
252
|
+
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified",
|
|
253
|
+
},
|
|
254
|
+
"idp": self._idp_data(),
|
|
255
|
+
"security": {
|
|
256
|
+
"wantAssertionsSigned": True,
|
|
257
|
+
"wantMessagesSigned": False,
|
|
258
|
+
"wantNameId": True,
|
|
259
|
+
"rejectDeprecatedAlgorithm": True,
|
|
260
|
+
"requestedAuthnContext": False,
|
|
261
|
+
"wantAttributeStatement": False,
|
|
262
|
+
"allowSingleLabelDomains": True,
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
sp_validation_only=False,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
def _idp_data(self) -> dict:
|
|
269
|
+
"""
|
|
270
|
+
Return the python3-saml `idp` settings, fetching the metadata on first use.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
dict: The `idp` settings block, cached on the instance.
|
|
274
|
+
"""
|
|
275
|
+
if self._idp is not None:
|
|
276
|
+
return self._idp
|
|
277
|
+
idp = {
|
|
278
|
+
"entityId": self.idp_entity_id,
|
|
279
|
+
"singleSignOnService": {
|
|
280
|
+
"url": self.idp_sso_url,
|
|
281
|
+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
|
282
|
+
},
|
|
283
|
+
"x509cert": self.idp_cert,
|
|
284
|
+
}
|
|
285
|
+
if not (self.idp_entity_id and self.idp_sso_url and self.idp_cert):
|
|
286
|
+
parser = _onelogin("onelogin.saml2.idp_metadata_parser")
|
|
287
|
+
metadata = parser.OneLogin_Saml2_IdPMetadataParser.parse_remote(
|
|
288
|
+
self.idp_metadata_url, timeout=10
|
|
289
|
+
)["idp"]
|
|
290
|
+
idp["entityId"] = self.idp_entity_id or metadata.get("entityId")
|
|
291
|
+
idp["x509cert"] = self.idp_cert or metadata.get("x509cert")
|
|
292
|
+
# Metadata listing several signing certificates (rollover) carries them here instead.
|
|
293
|
+
if not idp["x509cert"] and metadata.get("x509certMulti"):
|
|
294
|
+
idp["x509certMulti"] = metadata["x509certMulti"]
|
|
295
|
+
del idp["x509cert"]
|
|
296
|
+
# Raised before caching, so a later login fetches the metadata again.
|
|
297
|
+
if not (idp.get("x509cert") or idp.get("x509certMulti")):
|
|
298
|
+
raise ValueError(
|
|
299
|
+
f"IdP metadata at {self.idp_metadata_url!r} lists no signing certificate"
|
|
300
|
+
)
|
|
301
|
+
if not self.idp_sso_url:
|
|
302
|
+
idp["singleSignOnService"] = metadata["singleSignOnService"]
|
|
303
|
+
self._idp = idp
|
|
304
|
+
return idp
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def seconds_until(timestamp: int | None) -> int:
|
|
308
|
+
"""
|
|
309
|
+
Seconds from now until a unix timestamp.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
timestamp: The unix timestamp; None means one hour.
|
|
313
|
+
|
|
314
|
+
Returns:
|
|
315
|
+
int: The seconds left, at least 1.
|
|
316
|
+
"""
|
|
317
|
+
if timestamp is None:
|
|
318
|
+
return 3600
|
|
319
|
+
return max(1, math.ceil(timestamp - time.time()))
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def _first(attributes: dict[str, list[str]], name: str) -> str | None:
|
|
323
|
+
"""
|
|
324
|
+
Return the first value of an assertion attribute.
|
|
325
|
+
|
|
326
|
+
Args:
|
|
327
|
+
attributes: The assertion attributes, name to values.
|
|
328
|
+
name: The attribute name.
|
|
329
|
+
|
|
330
|
+
Returns:
|
|
331
|
+
str | None: The first value, None when the attribute is absent or empty.
|
|
332
|
+
"""
|
|
333
|
+
values = attributes.get(name) or []
|
|
334
|
+
return values[0] if values else None
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def _request_data(acs_url: str) -> dict:
|
|
338
|
+
"""
|
|
339
|
+
Build the python3-saml request data for a response posted to the ACS URL.
|
|
340
|
+
|
|
341
|
+
Args:
|
|
342
|
+
acs_url: The ACS URL the IdP posted to.
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
dict: The request data python3-saml validates `Destination` against.
|
|
346
|
+
"""
|
|
347
|
+
# Built from the ACS URL, not the request: behind a proxy prefix the app sees a different path than the IdP posted to.
|
|
348
|
+
acs = urllib.parse.urlsplit(acs_url)
|
|
349
|
+
return {
|
|
350
|
+
"https": "on" if acs.scheme == "https" else "off",
|
|
351
|
+
"http_host": acs.netloc,
|
|
352
|
+
"script_name": acs.path,
|
|
353
|
+
"get_data": {},
|
|
354
|
+
"post_data": {},
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def _onelogin(module: str) -> types.ModuleType:
|
|
359
|
+
"""
|
|
360
|
+
Import a python3-saml module, naming the extra when it is missing.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
module: The dotted module name.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
types.ModuleType: The imported module.
|
|
367
|
+
|
|
368
|
+
Raises:
|
|
369
|
+
ImportError: The `saml` extra is not installed.
|
|
370
|
+
"""
|
|
371
|
+
try:
|
|
372
|
+
return importlib.import_module(module)
|
|
373
|
+
except ImportError as e:
|
|
374
|
+
raise ImportError(
|
|
375
|
+
"SAML login needs the 'saml' extra: pip install fastapi-rtk[saml]"
|
|
376
|
+
) from e
|
|
@@ -68,7 +68,11 @@ def _current_command_path() -> str | None:
|
|
|
68
68
|
Returns:
|
|
69
69
|
str | None: The space-joined command names, or `None` when there is no active Click context.
|
|
70
70
|
"""
|
|
71
|
-
|
|
71
|
+
# typer >= 0.26 runs commands on its own vendored click, with its own context stack.
|
|
72
|
+
typer_get_current_context = getattr(typer.main, "get_current_context", None)
|
|
73
|
+
ctx = typer_get_current_context(silent=True) if typer_get_current_context else None
|
|
74
|
+
if ctx is None:
|
|
75
|
+
ctx = click.get_current_context(silent=True)
|
|
72
76
|
if ctx is None:
|
|
73
77
|
return None
|
|
74
78
|
names = []
|
|
@@ -164,6 +164,8 @@ class ErrorCode(str, enum.Enum):
|
|
|
164
164
|
FILE_NOT_FOUND = "FILE_NOT_FOUND"
|
|
165
165
|
IMAGE_NOT_FOUND = "IMAGE_NOT_FOUND"
|
|
166
166
|
OAUTH_POPUP_NOT_SUPPORTED = "OAUTH_POPUP_NOT_SUPPORTED"
|
|
167
|
+
SAML_RESPONSE_INVALID = "SAML_RESPONSE_INVALID"
|
|
168
|
+
SAML_ASSERTION_REPLAYED = "SAML_ASSERTION_REPLAYED"
|
|
167
169
|
COLUMN_NOT_FOUND = "COLUMN_NOT_FOUND"
|
|
168
170
|
|
|
169
171
|
|