iatoolkit 2.69.0__tar.gz → 2.71.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.
- {iatoolkit-2.69.0/src/iatoolkit.egg-info → iatoolkit-2.71.0}/PKG-INFO +1 -1
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/__init__.py +1 -1
- iatoolkit-2.71.0/src/iatoolkit/common/access.py +206 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/core.py +11 -1
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/locales/en.yaml +49 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/locales/es.yaml +49 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/models.py +6 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/api_key_service.py +13 -4
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/auth_service.py +45 -7
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_wiki_service.py +5 -3
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/profile_service.py +25 -1
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/prompt_service.py +7 -1
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/query_service.py +7 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/tenant_wiki_service.py +13 -12
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/_chat_header.html +1 -1
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/api_key_api_view.py +7 -4
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/base_login_view.py +1 -1
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/chat_context_preview_api_view.py +2 -2
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/chat_view.py +1 -1
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/configuration_api_view.py +4 -7
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/login_view.py +1 -1
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/prompt_api_view.py +11 -6
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/prompt_context_preview_api_view.py +2 -2
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/prompt_resource_api_view.py +3 -4
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/tool_api_view.py +2 -2
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/users_api_view.py +5 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/wiki_page_view.py +2 -4
- {iatoolkit-2.69.0 → iatoolkit-2.71.0/src/iatoolkit.egg-info}/PKG-INFO +1 -1
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit.egg-info/SOURCES.txt +1 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/LICENSE +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/LICENSE_COMMUNITY.md +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/pyproject.toml +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/readme.md +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/setup.cfg +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/base_company.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/cli_commands.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/bridge_validation.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/company_paths.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/exceptions.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/asset_storage.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/database_provider.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/memory_compilation_trigger.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/secret_provider.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/signup_policy_resolver.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/interfaces/web_search_provider.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/memory_tools.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/model_registry.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/routes.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/search_terms.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/secret_resolver.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/session_manager.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/text_splitter.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/url_safety.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/common/util.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/company_registry.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/llm_capabilities.yaml +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_compiler.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_correction.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_document_ingest.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_image_ingest.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_meeting_ingest.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents/memory_question_review.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_agents_pack.yaml +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/channel_business_identity.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/channel_conversation_continuity.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/channel_response_style.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/chat_state_rules.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/chat_user_profile.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/core_identity.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/email_output.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/file_download_output.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/format_styles.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/html_structures.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/links_documents.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/memory_usage.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/multimodal_basics.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/output_basics.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/presentation_formatting.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/query_main.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_aggregation_scope.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_casting.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_core.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_jsonb.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_mysql_casting.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_mysql_json.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_redshift_basics.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_redshift_types.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/sql_rules.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts/tool_html_passthrough.prompt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_prompts_pack.yaml +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/config/system_tools_pack.yaml +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/brevo_mail_app.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/call_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/file_connector.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/file_connector_factory.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/google_cloud_storage_connector.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/google_drive_connector.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/local_file_connector.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/connectors/s3_connector.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/google_auth_client.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/google_chat_app.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/inference_embeddings_client.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/inference_handler.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/jina_embeddings_client.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_gateway_resolver.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/anthropic_adapter.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/deepseek_adapter.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/gemini_adapter.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/openai_adapter.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/openai_compatible_chat_adapter.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_providers/openrouter_adapter.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_proxy.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/llm_response.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/infra/redis_session_manager.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/advisory_lock.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/api_key_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/bridge_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/database_manager.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/document_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/env_secret_provider.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/filesystem_asset_repository.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/knowledge_wiki_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/llm_query_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/mcp_token_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/memory_question_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/memory_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/memory_usage_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/profile_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/prompt_resource_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/sql_dataset_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/sql_source_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/repositories/vs_repo.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/runtime_logging.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/attachment_policy_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/branding_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/company_context_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/configuration_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/context_builder_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/dispatcher_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/embedding_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/excel_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/file_processor_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/history_manager_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/http_tool_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/i18n_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/inference_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/jwt_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/knowledge_base_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/language_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/license_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/llm_client_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/mail_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/markdown_wiki_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/mcp_token_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_compiler_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_lookup_policy_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_question_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_sources.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/memory_usage_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/noop_memory_compilation_trigger.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/operator_identity_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/contracts.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/image_normalizer.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/parsing_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/pdf_ocr_detection.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/provider_factory.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/provider_resolver.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/providers/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/providers/basic_provider.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/providers/docling_provider.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/parsers/validator.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/pdf_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/prompt_resource_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/signup_policy_resolver.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/sql_dataset_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/sql_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/sql_source_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/storage_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/structured_output_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/system_agent_catalog.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/system_agent_runner.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/system_prompt_catalog.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/system_tools.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/telemetry_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/tool_output_contract.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/tool_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/user_feedback_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/user_session_context_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/visual_kb_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/visual_tool_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/warmup_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/web_search/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/web_search/provider_factory.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/web_search/providers/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/web_search/providers/brave_provider.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/web_search_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/services/wiki_page_index_service.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/images/favicon.ico +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/images/favicon.png +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/images/fernando.jpeg +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/images/iatoolkit_core.png +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/images/iatoolkit_logo.png +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_feedback_button.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_filepond.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_header_navigation.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_help_content.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_history_button.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_logout_button.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_main.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_memory_button.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_model_selector.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_onboarding_button.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_prompt_manager.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/js/chat_reload_button.js +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/account.css +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/chat_iatoolkit.css +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/chat_modal.css +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/chat_public.css +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/documents.css +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/landing_page.css +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/llm_output.css +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/onboarding.css +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/static/styles/wiki_page.css +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/_company_header.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/_login_widget.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/account.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/base.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/change_password.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/chat.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/chat_memory_modal.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/chat_modals.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/error.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/forgot_password.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/home_hosted_default.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/login_global.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/memory/wiki_schema.md +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/onboarding_shell.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/pdf/base.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/pdf/letter.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/pdf/report.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/pdf/simple.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/signup.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/templates/wiki_page.html +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/__init__.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/account_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/categories_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/change_password_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/connectors_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/embedding_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/forgot_password_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/help_content_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/history_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/home_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/init_context_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/llmquery_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/load_document_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/logout_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/memory_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/profile_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/rag_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/root_redirect_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/signup_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/static_page_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/user_feedback_api_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit/views/verify_user_view.py +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit.egg-info/dependency_links.txt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit.egg-info/requires.txt +0 -0
- {iatoolkit-2.69.0 → iatoolkit-2.71.0}/src/iatoolkit.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Copyright (c) 2024 Fernando Libedinsky
|
|
2
|
+
# Product: IAToolkit
|
|
3
|
+
#
|
|
4
|
+
# IAToolkit is open source software.
|
|
5
|
+
|
|
6
|
+
"""Who may do what: the one question every check asks (BL-010, Fase 1).
|
|
7
|
+
|
|
8
|
+
Code never names a role. It asks whether a principal (a person's email, or a
|
|
9
|
+
service account `service:<key>`) has a level over a resource:
|
|
10
|
+
|
|
11
|
+
access.can(company, user_identifier, EDIT, "company")
|
|
12
|
+
access.can(company, user_identifier, USE, "agent", "cobranza")
|
|
13
|
+
|
|
14
|
+
Three levels, each including the one below:
|
|
15
|
+
1 USE use an agent, tool, model, SQL source / see content
|
|
16
|
+
2 EDIT change the resource (an agent's prompt, a folder's documents)
|
|
17
|
+
3 ADMIN decide on it: who has access, delete it, its settings
|
|
18
|
+
|
|
19
|
+
Over the resource type "company", EDIT is entering the admin console and
|
|
20
|
+
editing every resource, and ADMIN is administering the company (its team,
|
|
21
|
+
settings). Billing and deleting the tenant are for its owners only (`is_owner`).
|
|
22
|
+
|
|
23
|
+
Enterprise registers the decider backed by groups and grants. Without it,
|
|
24
|
+
the decision falls back to the person's role in the company, as before.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
import logging
|
|
30
|
+
from typing import Callable, Protocol
|
|
31
|
+
|
|
32
|
+
USE = 1
|
|
33
|
+
EDIT = 2
|
|
34
|
+
ADMIN = 3
|
|
35
|
+
|
|
36
|
+
LEVEL_NAMES = {USE: "use", EDIT: "edit", ADMIN: "admin"}
|
|
37
|
+
|
|
38
|
+
# The level each legacy role holds over everything (core without enterprise).
|
|
39
|
+
ROLE_LEVELS = {"user": USE, "editor": EDIT, "admin": ADMIN, "owner": ADMIN}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AccessDecider(Protocol):
|
|
43
|
+
def can(self, company_short_name: str, user_identifier: str | None, level: int,
|
|
44
|
+
resource_type: str, resource_key: str | None = None,
|
|
45
|
+
resource_path: str | None = None) -> bool: ...
|
|
46
|
+
|
|
47
|
+
def is_owner(self, company_short_name: str, user_identifier: str | None) -> bool: ...
|
|
48
|
+
|
|
49
|
+
def effective_role(self, company_short_name: str, user_identifier: str | None) -> str | None: ...
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class RoleAccessDecider:
|
|
53
|
+
"""Core's fallback: the role of iat_user_company grants its level over everything."""
|
|
54
|
+
|
|
55
|
+
def __init__(self, role_reader: Callable[[str, str], str | None]):
|
|
56
|
+
self.role_reader = role_reader
|
|
57
|
+
|
|
58
|
+
def _role(self, company_short_name, user_identifier) -> str | None:
|
|
59
|
+
if not user_identifier:
|
|
60
|
+
return None
|
|
61
|
+
return self.role_reader(company_short_name, user_identifier)
|
|
62
|
+
|
|
63
|
+
def can(self, company_short_name, user_identifier, level, resource_type, resource_key=None,
|
|
64
|
+
resource_path=None) -> bool:
|
|
65
|
+
if level <= USE and resource_type != "company":
|
|
66
|
+
# Without groups nothing is restricted: whoever enters the company uses
|
|
67
|
+
# what it offers (members, and the end users a key acts for).
|
|
68
|
+
return True
|
|
69
|
+
role = self._role(company_short_name, user_identifier)
|
|
70
|
+
if role is None:
|
|
71
|
+
return False
|
|
72
|
+
return ROLE_LEVELS.get(role, USE) >= level
|
|
73
|
+
|
|
74
|
+
def is_owner(self, company_short_name, user_identifier) -> bool:
|
|
75
|
+
return self._role(company_short_name, user_identifier) == "owner"
|
|
76
|
+
|
|
77
|
+
def effective_role(self, company_short_name, user_identifier) -> str | None:
|
|
78
|
+
return self._role(company_short_name, user_identifier)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class Access:
|
|
82
|
+
"""The registered decider; enterprise replaces core's fallback at boot."""
|
|
83
|
+
|
|
84
|
+
_decider: AccessDecider | None = None
|
|
85
|
+
|
|
86
|
+
@classmethod
|
|
87
|
+
def register(cls, decider: AccessDecider) -> None:
|
|
88
|
+
cls._decider = decider
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def clear(cls) -> None:
|
|
92
|
+
cls._decider = None
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def registered(cls) -> bool:
|
|
96
|
+
"""Whether a decider other than core's role fallback is in place (enterprise)."""
|
|
97
|
+
return cls._decider is not None and not isinstance(cls._decider, RoleAccessDecider)
|
|
98
|
+
|
|
99
|
+
@classmethod
|
|
100
|
+
def decider(cls) -> AccessDecider:
|
|
101
|
+
if cls._decider is None:
|
|
102
|
+
cls._decider = RoleAccessDecider(_role_from_profile_service)
|
|
103
|
+
return cls._decider
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def decider_for(role_reader: Callable[[str, str], str | None]) -> AccessDecider:
|
|
107
|
+
"""The registered decider, or the role fallback reading roles with `role_reader`
|
|
108
|
+
(views that already hold a ProfileRepo)."""
|
|
109
|
+
return Access.decider() if Access.registered() else RoleAccessDecider(role_reader)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _role_from_profile_service(company_short_name: str, user_identifier: str) -> str | None:
|
|
113
|
+
from iatoolkit import current_iatoolkit
|
|
114
|
+
from iatoolkit.services.profile_service import ProfileService
|
|
115
|
+
|
|
116
|
+
return current_iatoolkit().get_injector().get(ProfileService).get_role_in_company(
|
|
117
|
+
company_short_name, user_identifier)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def can(company_short_name: str, user_identifier: str | None, level: int, resource_type: str,
|
|
121
|
+
resource_key: str | None = None, resource_path: str | None = None) -> bool:
|
|
122
|
+
try:
|
|
123
|
+
return bool(Access.decider().can(company_short_name, user_identifier, level, resource_type,
|
|
124
|
+
resource_key, resource_path))
|
|
125
|
+
except Exception as exc:
|
|
126
|
+
# Deciding failed: deny, never fall open.
|
|
127
|
+
logging.warning("Access check failed (%s, %s, %s %s): %s", company_short_name, user_identifier,
|
|
128
|
+
level, resource_type, exc)
|
|
129
|
+
return False
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def is_owner(company_short_name: str, user_identifier: str | None) -> bool:
|
|
133
|
+
try:
|
|
134
|
+
return bool(Access.decider().is_owner(company_short_name, user_identifier))
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
logging.warning("Owner check failed (%s, %s): %s", company_short_name, user_identifier, exc)
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def effective_roles(company_short_name: str, user_identifiers: list[str]) -> dict[str, str | None]:
|
|
141
|
+
"""`effective_role` for many people at once (the Users screen)."""
|
|
142
|
+
decider = Access.decider()
|
|
143
|
+
try:
|
|
144
|
+
bulk = getattr(decider, "effective_roles", None)
|
|
145
|
+
if callable(bulk):
|
|
146
|
+
return bulk(company_short_name, user_identifiers)
|
|
147
|
+
return {u: decider.effective_role(company_short_name, u) for u in user_identifiers}
|
|
148
|
+
except Exception as exc:
|
|
149
|
+
logging.warning("Effective roles failed (%s): %s", company_short_name, exc)
|
|
150
|
+
return {}
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def effective_role(company_short_name: str, user_identifier: str | None) -> str | None:
|
|
154
|
+
"""What the person can do over the company, as the console's old role names.
|
|
155
|
+
|
|
156
|
+
Only for screens that still show or branch on a role name (labels, menus);
|
|
157
|
+
access is always decided with `can`.
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
return Access.decider().effective_role(company_short_name, user_identifier)
|
|
161
|
+
except Exception as exc:
|
|
162
|
+
logging.warning("Effective role failed (%s, %s): %s", company_short_name, user_identifier, exc)
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def role_assigned(company_short_name: str, user_identifier: str, role: str) -> None:
|
|
167
|
+
"""Somebody was given a legacy role (signup's bootstrap admin, company activation,
|
|
168
|
+
`init-install --owner`): the registered decider puts them in the matching group."""
|
|
169
|
+
decider = Access._decider
|
|
170
|
+
hook = getattr(decider, "role_assigned", None) if decider is not None else None
|
|
171
|
+
if callable(hook):
|
|
172
|
+
try:
|
|
173
|
+
hook(company_short_name, user_identifier, role)
|
|
174
|
+
except Exception as exc:
|
|
175
|
+
logging.warning("Role %s of %s in %s not moved to its group: %s", role, user_identifier,
|
|
176
|
+
company_short_name, exc)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def person_can(company_short_name: str, auth_result: dict | None, level: int, resource_type: str = "company",
|
|
180
|
+
resource_key: str | None = None) -> bool:
|
|
181
|
+
"""A signed-in person's access, from an AuthService result.
|
|
182
|
+
|
|
183
|
+
Only sessions carry `user_role`; an API key is not a person and gets False
|
|
184
|
+
(each view decides on its own whether a key may pass).
|
|
185
|
+
"""
|
|
186
|
+
if not isinstance(auth_result, dict) or "user_role" not in auth_result:
|
|
187
|
+
return False
|
|
188
|
+
if not Access.registered():
|
|
189
|
+
# Core alone: the session's role was just read from iat_user_company.
|
|
190
|
+
return _session_role_decider(auth_result).can(company_short_name, auth_result.get("user_identifier") or "session",
|
|
191
|
+
level, resource_type, resource_key)
|
|
192
|
+
return can(company_short_name, auth_result.get("user_identifier"), level, resource_type, resource_key)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def person_is_owner(company_short_name: str, auth_result: dict | None) -> bool:
|
|
196
|
+
if not isinstance(auth_result, dict) or "user_role" not in auth_result:
|
|
197
|
+
return False
|
|
198
|
+
if not Access.registered():
|
|
199
|
+
return _session_role_decider(auth_result).is_owner(company_short_name,
|
|
200
|
+
auth_result.get("user_identifier") or "session")
|
|
201
|
+
return is_owner(company_short_name, auth_result.get("user_identifier"))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _session_role_decider(auth_result: dict) -> RoleAccessDecider:
|
|
205
|
+
role = str(auth_result.get("user_role") or "").strip().lower() or None
|
|
206
|
+
return RoleAccessDecider(lambda company_short_name, user_identifier: role)
|
|
@@ -195,6 +195,13 @@ class IAToolkit:
|
|
|
195
195
|
thread.start()
|
|
196
196
|
return thread
|
|
197
197
|
|
|
198
|
+
@staticmethod
|
|
199
|
+
def _effective_role(company_short_name: str | None, user_identifier: str | None) -> str | None:
|
|
200
|
+
if not company_short_name or not user_identifier:
|
|
201
|
+
return None
|
|
202
|
+
from iatoolkit.common import access
|
|
203
|
+
return access.effective_role(company_short_name, user_identifier)
|
|
204
|
+
|
|
198
205
|
def _get_config_value(self, key: str, default=None):
|
|
199
206
|
# get a value from the config dict or the environment variable
|
|
200
207
|
return self.config.get(key, os.getenv(key, default))
|
|
@@ -653,7 +660,10 @@ class IAToolkit:
|
|
|
653
660
|
'app_name': 'IAToolkit',
|
|
654
661
|
'user_identifier': session_info.get('user_identifier'),
|
|
655
662
|
'company_short_name': route_company_short_name or session_info.get('company_short_name'),
|
|
656
|
-
|
|
663
|
+
# What the person can do over the company (BL-010, Fase 1), never the
|
|
664
|
+
# role an SSO hook may have written into the session profile.
|
|
665
|
+
'user_role': self._effective_role(route_company_short_name or session_info.get('company_short_name'),
|
|
666
|
+
session_info.get('user_identifier')),
|
|
657
667
|
'user_is_local': user_profile.get('user_is_local'),
|
|
658
668
|
'user_email': user_profile.get('user_email'),
|
|
659
669
|
'iatoolkit_base_url': request.url_root,
|
|
@@ -645,6 +645,48 @@ ui:
|
|
|
645
645
|
admin:
|
|
646
646
|
workspace: "Workspace"
|
|
647
647
|
team_invitations: "Invitations"
|
|
648
|
+
team_groups: "Groups"
|
|
649
|
+
team_groups_edit: "Groups"
|
|
650
|
+
team_groups_modal_title: "Person's groups"
|
|
651
|
+
team_groups_save: "Save"
|
|
652
|
+
team_groups_close: "Close"
|
|
653
|
+
team_groups_saved: "Groups updated."
|
|
654
|
+
team_groups_everyone_hint: "Every member is in Everyone: no need to tick it."
|
|
655
|
+
team_group_new: "New group"
|
|
656
|
+
team_group_name: "Name"
|
|
657
|
+
team_group_description: "Description"
|
|
658
|
+
team_group_members: "Members"
|
|
659
|
+
team_group_add_member: "Add"
|
|
660
|
+
team_group_remove: "Remove"
|
|
661
|
+
team_group_delete: "Delete group"
|
|
662
|
+
team_group_builtin: "Built-in"
|
|
663
|
+
team_group_save: "Save"
|
|
664
|
+
team_group_edit: "Edit"
|
|
665
|
+
team_group_everyone_note: "Includes everyone in the company."
|
|
666
|
+
team_group_owners_note: "Only an owner can change the owners. Owners is never empty."
|
|
667
|
+
team_group_grants: "Can"
|
|
668
|
+
team_group_no_grants: "No permissions over the company."
|
|
669
|
+
team_group_owners_all: "everything, billing included"
|
|
670
|
+
team_group_delete_confirm: "Delete this group? Its permissions are lost."
|
|
671
|
+
team_group_none_members: "No members."
|
|
672
|
+
team_group_member_placeholder: "person's email"
|
|
673
|
+
team_group_created: "Group created."
|
|
674
|
+
team_group_deleted: "Group deleted."
|
|
675
|
+
team_group_updated: "Group updated."
|
|
676
|
+
access_level_1: "use / see"
|
|
677
|
+
access_level_2: "edit"
|
|
678
|
+
access_level_3: "administer"
|
|
679
|
+
access_all_resources: "every resource"
|
|
680
|
+
access_company: "the company"
|
|
681
|
+
access_title: "Access"
|
|
682
|
+
access_default: "Company access (default)"
|
|
683
|
+
access_default_hint: "Used by whoever has access to every agent. Add groups to restrict it."
|
|
684
|
+
access_own_hint: "Only these groups (and those who administer the company) have access."
|
|
685
|
+
access_add_group: "Add group"
|
|
686
|
+
access_reset: "Back to company access"
|
|
687
|
+
access_saved: "Access updated."
|
|
688
|
+
access_group: "Group"
|
|
689
|
+
access_level: "Level"
|
|
648
690
|
team_invite_user: "Invite user"
|
|
649
691
|
team_invite_none: "No invitations yet."
|
|
650
692
|
team_invite_loading: "Loading invitations..."
|
|
@@ -2087,6 +2129,11 @@ ui:
|
|
|
2087
2129
|
routing_profile_hint: "Used by enterprise tool routing to rank and select candidate tools."
|
|
2088
2130
|
|
|
2089
2131
|
api_key:
|
|
2132
|
+
can_act_for_users: "May act on behalf of users"
|
|
2133
|
+
can_act_for_users_help: "Without this, the key acts as itself (service:<name>) and cannot pass for anyone. Turn it on only for integrations that must name the user they act for (a portal with its own login, an MCP gateway)."
|
|
2134
|
+
acts: "Acts"
|
|
2135
|
+
acts_as_itself: "As itself"
|
|
2136
|
+
acts_for_users: "On behalf of users"
|
|
2090
2137
|
title: "API Keys"
|
|
2091
2138
|
subtitle: "Manage company API keys used to authenticate external requests."
|
|
2092
2139
|
new_btn: "New API Key"
|
|
@@ -2597,6 +2644,8 @@ ui:
|
|
|
2597
2644
|
|
|
2598
2645
|
errors:
|
|
2599
2646
|
company_not_found: "The company {company_short_name} does not exist."
|
|
2647
|
+
access:
|
|
2648
|
+
agent_denied: "You do not have access to the agent {prompt_name}."
|
|
2600
2649
|
timeout: "timeout expired."
|
|
2601
2650
|
auth:
|
|
2602
2651
|
invalid_password: "The provided password is incorrect."
|
|
@@ -659,6 +659,48 @@ ui:
|
|
|
659
659
|
admin:
|
|
660
660
|
workspace: "Recursos"
|
|
661
661
|
team_invitations: "Invitaciones"
|
|
662
|
+
team_groups: "Grupos"
|
|
663
|
+
team_groups_edit: "Grupos"
|
|
664
|
+
team_groups_modal_title: "Grupos de la persona"
|
|
665
|
+
team_groups_save: "Guardar"
|
|
666
|
+
team_groups_close: "Cerrar"
|
|
667
|
+
team_groups_saved: "Grupos actualizados."
|
|
668
|
+
team_groups_everyone_hint: "Todos los miembros están en Todos: no hace falta marcarlo."
|
|
669
|
+
team_group_new: "Nuevo grupo"
|
|
670
|
+
team_group_name: "Nombre"
|
|
671
|
+
team_group_description: "Descripción"
|
|
672
|
+
team_group_members: "Miembros"
|
|
673
|
+
team_group_add_member: "Agregar"
|
|
674
|
+
team_group_remove: "Quitar"
|
|
675
|
+
team_group_delete: "Eliminar grupo"
|
|
676
|
+
team_group_builtin: "De fábrica"
|
|
677
|
+
team_group_save: "Guardar"
|
|
678
|
+
team_group_edit: "Editar"
|
|
679
|
+
team_group_everyone_note: "Incluye a todas las personas de la empresa."
|
|
680
|
+
team_group_owners_note: "Solo un dueño puede cambiar los dueños. Dueños nunca queda vacío."
|
|
681
|
+
team_group_grants: "Puede"
|
|
682
|
+
team_group_no_grants: "Sin permisos sobre la empresa."
|
|
683
|
+
team_group_owners_all: "todo, incluida la facturación"
|
|
684
|
+
team_group_delete_confirm: "¿Eliminar este grupo? Sus permisos se pierden."
|
|
685
|
+
team_group_none_members: "Sin miembros."
|
|
686
|
+
team_group_member_placeholder: "email de la persona"
|
|
687
|
+
team_group_created: "Grupo creado."
|
|
688
|
+
team_group_deleted: "Grupo eliminado."
|
|
689
|
+
team_group_updated: "Grupo actualizado."
|
|
690
|
+
access_level_1: "usar / ver"
|
|
691
|
+
access_level_2: "editar"
|
|
692
|
+
access_level_3: "administrar"
|
|
693
|
+
access_all_resources: "todos los recursos"
|
|
694
|
+
access_company: "la empresa"
|
|
695
|
+
access_title: "Acceso"
|
|
696
|
+
access_default: "Acceso de la empresa (por defecto)"
|
|
697
|
+
access_default_hint: "Lo usan quienes tienen acceso a todos los agentes. Agrega grupos para restringirlo."
|
|
698
|
+
access_own_hint: "Solo estos grupos (y quienes administran la empresa) tienen acceso."
|
|
699
|
+
access_add_group: "Agregar grupo"
|
|
700
|
+
access_reset: "Volver al acceso de la empresa"
|
|
701
|
+
access_saved: "Acceso actualizado."
|
|
702
|
+
access_group: "Grupo"
|
|
703
|
+
access_level: "Nivel"
|
|
662
704
|
team_invite_user: "Invitar usuario"
|
|
663
705
|
team_invite_none: "Todavía no hay invitaciones."
|
|
664
706
|
team_invite_loading: "Cargando invitaciones..."
|
|
@@ -2105,6 +2147,11 @@ ui:
|
|
|
2105
2147
|
routing_profile_hint: "Usado por el router enterprise para rankear y seleccionar herramientas candidatas."
|
|
2106
2148
|
|
|
2107
2149
|
api_key:
|
|
2150
|
+
can_act_for_users: "Puede actuar en nombre de usuarios"
|
|
2151
|
+
can_act_for_users_help: "Sin esta opción, la llave actúa como sí misma (service:<nombre>) y no puede hacerse pasar por nadie. Actívala solo para integraciones que deben indicar el usuario por el que actúan (un portal con login propio, un gateway MCP)."
|
|
2152
|
+
acts: "Actúa"
|
|
2153
|
+
acts_as_itself: "Como sí misma"
|
|
2154
|
+
acts_for_users: "En nombre de usuarios"
|
|
2108
2155
|
title: "API Keys"
|
|
2109
2156
|
subtitle: "Administra las API keys de la empresa para autenticar solicitudes externas."
|
|
2110
2157
|
new_btn: "Nueva API Key"
|
|
@@ -2612,6 +2659,8 @@ ui:
|
|
|
2612
2659
|
|
|
2613
2660
|
errors:
|
|
2614
2661
|
company_not_found: "La empresa {company_short_name} no existe."
|
|
2662
|
+
access:
|
|
2663
|
+
agent_denied: "No tienes acceso al agente {prompt_name}."
|
|
2615
2664
|
timeout: "El tiempo de espera ha expirado."
|
|
2616
2665
|
|
|
2617
2666
|
|
|
@@ -9,6 +9,7 @@ from sqlalchemy import Column, Integer, BigInteger, String, DateTime, Enum, Text
|
|
|
9
9
|
from sqlalchemy.orm import DeclarativeBase
|
|
10
10
|
from sqlalchemy.orm import relationship, class_mapper
|
|
11
11
|
from sqlalchemy.sql import func, literal_column
|
|
12
|
+
from sqlalchemy.sql import text as sql_text
|
|
12
13
|
from sqlalchemy import UniqueConstraint, Index
|
|
13
14
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
14
15
|
from datetime import datetime
|
|
@@ -125,6 +126,11 @@ class ApiKey(Base):
|
|
|
125
126
|
is_active = Column(Boolean, default=True, nullable=False)
|
|
126
127
|
created_at = Column(DateTime, default=datetime.now)
|
|
127
128
|
last_used_at = Column(DateTime, nullable=True) # Opcional: para rastrear uso
|
|
129
|
+
# A key is a service account that acts as itself ("service:<key name>"). Only with
|
|
130
|
+
# this on may it name the user it acts for, who must belong to the company
|
|
131
|
+
# (BL-010, Fase 0). Keys created before it existed keep acting for users;
|
|
132
|
+
# migration: iatoolkit-enterprise/migrations/2026-10-18-api-keys-can-act-for-users.sql
|
|
133
|
+
can_act_for_users = Column(Boolean, nullable=False, default=False, server_default=sql_text("true"))
|
|
128
134
|
|
|
129
135
|
company = relationship("Company", back_populates="api_keys")
|
|
130
136
|
|
|
@@ -30,6 +30,8 @@ class ApiKeyService:
|
|
|
30
30
|
"key_name": api_key.key_name,
|
|
31
31
|
"key": api_key.key,
|
|
32
32
|
"is_active": api_key.is_active,
|
|
33
|
+
# BL-010, Fase 0: whether the key may name the user it acts for.
|
|
34
|
+
"can_act_for_users": bool(getattr(api_key, "can_act_for_users", False)),
|
|
33
35
|
"created_at": api_key.created_at.isoformat() if api_key.created_at else None,
|
|
34
36
|
"last_used_at": api_key.last_used_at.isoformat() if api_key.last_used_at else None,
|
|
35
37
|
}
|
|
@@ -65,7 +67,7 @@ class ApiKeyService:
|
|
|
65
67
|
|
|
66
68
|
return {"data": self._api_key_to_dict(api_key)}
|
|
67
69
|
|
|
68
|
-
def create_api_key_entry(self, company_short_name: str, key_name: str) -> Dict:
|
|
70
|
+
def create_api_key_entry(self, company_short_name: str, key_name: str, can_act_for_users: bool = False) -> Dict:
|
|
69
71
|
company, error = self._get_company(company_short_name)
|
|
70
72
|
if error:
|
|
71
73
|
return error
|
|
@@ -81,7 +83,8 @@ class ApiKeyService:
|
|
|
81
83
|
alphabet = string.ascii_letters + string.digits
|
|
82
84
|
key = ''.join(secrets.choice(alphabet) for _ in range(length))
|
|
83
85
|
|
|
84
|
-
|
|
86
|
+
# A new key acts as itself unless the admin allows it to act for users (BL-010).
|
|
87
|
+
api_key = ApiKey(key=key, company_id=company.id, key_name=key_name, can_act_for_users=bool(can_act_for_users))
|
|
85
88
|
created = self.api_key_repo.create_api_key(api_key)
|
|
86
89
|
return {"data": self._api_key_to_dict(created)}
|
|
87
90
|
|
|
@@ -89,7 +92,8 @@ class ApiKeyService:
|
|
|
89
92
|
company_short_name: str,
|
|
90
93
|
api_key_id: int,
|
|
91
94
|
key_name: str | None = None,
|
|
92
|
-
is_active: bool | None = None
|
|
95
|
+
is_active: bool | None = None,
|
|
96
|
+
can_act_for_users: bool | None = None) -> Dict:
|
|
93
97
|
company, error = self._get_company(company_short_name)
|
|
94
98
|
if error:
|
|
95
99
|
return error
|
|
@@ -98,7 +102,7 @@ class ApiKeyService:
|
|
|
98
102
|
if not api_key:
|
|
99
103
|
return {"error": "API key not found.", "status_code": 404}
|
|
100
104
|
|
|
101
|
-
if key_name is None and is_active is None:
|
|
105
|
+
if key_name is None and is_active is None and can_act_for_users is None:
|
|
102
106
|
return {"error": "No changes provided.", "status_code": 400}
|
|
103
107
|
|
|
104
108
|
if key_name is not None:
|
|
@@ -116,6 +120,11 @@ class ApiKeyService:
|
|
|
116
120
|
return {"error": "Invalid value for is_active.", "status_code": 400}
|
|
117
121
|
api_key.is_active = is_active
|
|
118
122
|
|
|
123
|
+
if can_act_for_users is not None:
|
|
124
|
+
if not isinstance(can_act_for_users, bool):
|
|
125
|
+
return {"error": "Invalid value for can_act_for_users.", "status_code": 400}
|
|
126
|
+
api_key.can_act_for_users = can_act_for_users
|
|
127
|
+
|
|
119
128
|
updated = self.api_key_repo.update_api_key(api_key)
|
|
120
129
|
return {"data": self._api_key_to_dict(updated)}
|
|
121
130
|
|
|
@@ -14,7 +14,10 @@ from iatoolkit.repositories.models import AccessLog
|
|
|
14
14
|
from iatoolkit.infra.google_auth_client import GoogleAuthClient, GoogleAuthError
|
|
15
15
|
from flask import request
|
|
16
16
|
import logging
|
|
17
|
+
import re
|
|
18
|
+
import unicodedata
|
|
17
19
|
import hashlib
|
|
20
|
+
from iatoolkit.common import access
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
class AuthService:
|
|
@@ -284,7 +287,11 @@ class AuthService:
|
|
|
284
287
|
"success": True,
|
|
285
288
|
"company_short_name": session_info['company_short_name'],
|
|
286
289
|
"user_identifier": session_info['user_identifier'],
|
|
287
|
-
|
|
290
|
+
# From the database, never from the stored session profile: an SSO
|
|
291
|
+
# hook writes that profile, and must not be able to grant admin
|
|
292
|
+
# (BL-010, Fase 0).
|
|
293
|
+
"user_role": self._role_in_company(session_info['company_short_name'],
|
|
294
|
+
session_info['user_identifier']),
|
|
288
295
|
}
|
|
289
296
|
|
|
290
297
|
# --- Priority 2: Check for a valid API Key in headers ---
|
|
@@ -311,7 +318,22 @@ class AuthService:
|
|
|
311
318
|
# get the company from the api_key_entry
|
|
312
319
|
company = api_key_entry.company
|
|
313
320
|
|
|
314
|
-
#
|
|
321
|
+
# The key is a service account (BL-010, Fase 0): it acts as itself unless it
|
|
322
|
+
# was allowed to act for users. Every result says which key acted.
|
|
323
|
+
principal = self.service_principal(api_key_entry)
|
|
324
|
+
base = {
|
|
325
|
+
"success": True,
|
|
326
|
+
"company_short_name": company.short_name,
|
|
327
|
+
"auth_type": "api_key",
|
|
328
|
+
"auth_principal": principal,
|
|
329
|
+
"api_key_id": api_key_entry.id,
|
|
330
|
+
}
|
|
331
|
+
# Only an explicit False: a key loaded before the column existed keeps acting for users.
|
|
332
|
+
if getattr(api_key_entry, "can_act_for_users", None) is False:
|
|
333
|
+
# Whatever user the request names is ignored: the key cannot pass for anyone.
|
|
334
|
+
return {**base, "user_identifier": principal, "acting_for": None}
|
|
335
|
+
|
|
336
|
+
# A key allowed to act for users must say who (unless the endpoint allows none).
|
|
315
337
|
data = request.get_json(silent=True) or {}
|
|
316
338
|
user_identifier = data.get('user_identifier', '')
|
|
317
339
|
if not anonymous and not user_identifier:
|
|
@@ -320,11 +342,27 @@ class AuthService:
|
|
|
320
342
|
"error_message": self.i18n_service.t('errors.auth.no_user_identifier_api'),
|
|
321
343
|
"status_code": 403}
|
|
322
344
|
|
|
323
|
-
return {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
345
|
+
return {**base, "user_identifier": user_identifier, "acting_for": user_identifier or None}
|
|
346
|
+
|
|
347
|
+
SERVICE_PREFIX = "service:"
|
|
348
|
+
|
|
349
|
+
@classmethod
|
|
350
|
+
def service_principal(cls, api_key_entry) -> str:
|
|
351
|
+
"""The identity a key acts with by itself: "service:<key name>" (lowercase, dashes)."""
|
|
352
|
+
raw = unicodedata.normalize("NFKD", str(getattr(api_key_entry, "key_name", "") or ""))
|
|
353
|
+
ascii_name = raw.encode("ascii", "ignore").decode("ascii").strip().lower()
|
|
354
|
+
name = re.sub(r"[^a-z0-9._-]+", "-", ascii_name)
|
|
355
|
+
return f"{cls.SERVICE_PREFIX}{name.strip('-') or getattr(api_key_entry, 'id', 'key')}"
|
|
356
|
+
|
|
357
|
+
def _role_in_company(self, company_short_name: str, user_identifier: str) -> str | None:
|
|
358
|
+
"""What the person can do over the company, by its old role name (BL-010, Fase 1).
|
|
359
|
+
|
|
360
|
+
Views decide with `iatoolkit.common.access`; this name stays in the result to
|
|
361
|
+
tell a session from an API key and for screens that show it.
|
|
362
|
+
"""
|
|
363
|
+
if access.Access.registered():
|
|
364
|
+
return access.effective_role(company_short_name, user_identifier)
|
|
365
|
+
return self.profile_service.get_role_in_company(company_short_name, user_identifier)
|
|
328
366
|
|
|
329
367
|
def verify_for_company(self, company_short_name: str, anonymous: bool = False) -> dict:
|
|
330
368
|
"""
|
|
@@ -12,6 +12,7 @@ from injector import inject
|
|
|
12
12
|
|
|
13
13
|
from iatoolkit.services.markdown_wiki_service import MarkdownWikiService
|
|
14
14
|
from iatoolkit.services.tenant_wiki_service import TenantWikiService
|
|
15
|
+
from iatoolkit.common.access import ADMIN
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class MemoryWikiService:
|
|
@@ -223,7 +224,8 @@ def _read_schema_template(path: str) -> str:
|
|
|
223
224
|
return ""
|
|
224
225
|
|
|
225
226
|
|
|
226
|
-
# Memoria's wiki opens in the wiki viewer for
|
|
227
|
-
# BL-014); for
|
|
228
|
-
|
|
227
|
+
# Memoria's wiki opens in the wiki viewer only for those who administer the company
|
|
228
|
+
# ("Ver como wiki", BL-014); for everyone else, and for the wiki tools and MCP, it
|
|
229
|
+
# stays hidden.
|
|
230
|
+
TenantWikiService.register_system_wiki_readers(MemoryWikiService.SYSTEM_OWNER, ADMIN)
|
|
229
231
|
|
|
@@ -28,6 +28,7 @@ from typing import List, Dict
|
|
|
28
28
|
from sqlalchemy import update
|
|
29
29
|
from iatoolkit.common.interfaces.signup_policy_resolver import SignupPolicyResolver
|
|
30
30
|
from iatoolkit.services.signup_policy_resolver import AllowAllSignupPolicyResolver
|
|
31
|
+
from iatoolkit.common import access
|
|
31
32
|
|
|
32
33
|
|
|
33
34
|
class ProfileService:
|
|
@@ -107,6 +108,10 @@ class ProfileService:
|
|
|
107
108
|
return first_name, last_name
|
|
108
109
|
|
|
109
110
|
def _build_session_user_profile(self, user: User, company: Company, user_role: str | None) -> dict:
|
|
111
|
+
if access.Access.registered():
|
|
112
|
+
# What the person can do over the company (BL-010, Fase 1), for screens and
|
|
113
|
+
# prompt context; access is always decided with `access.can`.
|
|
114
|
+
user_role = access.effective_role(company.short_name, user.email)
|
|
110
115
|
auth_method = str(getattr(user, "auth_method", "local") or "local").lower()
|
|
111
116
|
is_local = auth_method == "local"
|
|
112
117
|
user_identifier = user.email
|
|
@@ -150,6 +155,7 @@ class ProfileService:
|
|
|
150
155
|
)
|
|
151
156
|
.values(role="admin")
|
|
152
157
|
)
|
|
158
|
+
access.role_assigned(company.short_name, user_email, "admin")
|
|
153
159
|
|
|
154
160
|
updated_params = dict(params)
|
|
155
161
|
updated_params.pop("bootstrap_admin_email", None)
|
|
@@ -396,6 +402,20 @@ class ProfileService:
|
|
|
396
402
|
"profile": profile
|
|
397
403
|
}
|
|
398
404
|
|
|
405
|
+
def get_role_in_company(self, company_short_name: str, user_identifier: str) -> str | None:
|
|
406
|
+
"""
|
|
407
|
+
The person's role in the company as iat_user_company records it; None if
|
|
408
|
+
not a member. Every authorization reads the role here, never from the
|
|
409
|
+
stored session profile: an SSO hook writes that profile (BL-010, Fase 0).
|
|
410
|
+
"""
|
|
411
|
+
try:
|
|
412
|
+
company = self.profile_repo.get_company_by_short_name(company_short_name)
|
|
413
|
+
user = self.profile_repo.get_user_by_email(str(user_identifier or "").strip().lower()) if company else None
|
|
414
|
+
return self.profile_repo.get_user_role_in_company(company.id, user.id) if user else None
|
|
415
|
+
except Exception as exc:
|
|
416
|
+
logging.warning("Role of %s in %s could not be read: %s", user_identifier, company_short_name, exc)
|
|
417
|
+
return None
|
|
418
|
+
|
|
399
419
|
def _account_user(self, company_short_name: str, session_info: dict):
|
|
400
420
|
profile = session_info.get("profile") or {}
|
|
401
421
|
method = (profile.get("extras") or {}).get("auth_method")
|
|
@@ -416,7 +436,8 @@ class ProfileService:
|
|
|
416
436
|
"last_name": user.last_name if user else "",
|
|
417
437
|
"full_name": f"{user.first_name} {user.last_name}" if user else profile.get("user_fullname", ""),
|
|
418
438
|
"email": profile.get("user_email") or session_info["user_identifier"],
|
|
419
|
-
"role":
|
|
439
|
+
"role": (access.effective_role(company_short_name, session_info["user_identifier"])
|
|
440
|
+
if access.Access.registered() else profile.get("user_role")) or "user",
|
|
420
441
|
"auth_method": method,
|
|
421
442
|
"can_edit_name": bool(user and method == "local"),
|
|
422
443
|
"language": self.lang_service.get_current_language(),
|
|
@@ -938,8 +959,11 @@ class ProfileService:
|
|
|
938
959
|
# get the company users from the repo
|
|
939
960
|
company_users = self.profile_repo.get_company_users_with_details(company_short_name)
|
|
940
961
|
|
|
962
|
+
effective = access.effective_roles(company_short_name, [u.email for u, _, _ in company_users]) \
|
|
963
|
+
if access.Access.registered() else {}
|
|
941
964
|
users_data = []
|
|
942
965
|
for user, role, last_access in company_users:
|
|
966
|
+
role = effective.get(user.email, role) if access.Access.registered() else role
|
|
943
967
|
users_data.append({
|
|
944
968
|
"first_name": user.first_name,
|
|
945
969
|
"last_name": user.last_name,
|