cornflow 1.2.0a1__tar.gz → 1.2.0a2__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.
- {cornflow-1.2.0a1/cornflow.egg-info → cornflow-1.2.0a2}/PKG-INFO +13 -13
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/README.rst +9 -9
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/airflow_config/airflow_local_settings.py +1 -1
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/migrations.py +23 -3
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/service.py +3 -9
- cornflow-1.2.0a2/cornflow/cli/utils.py +41 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/config.py +1 -1
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/__init__.py +7 -1
- cornflow-1.2.0a2/cornflow/endpoints/alarms.py +123 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/login.py +59 -38
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/meta_resource.py +11 -3
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/base_data_model.py +4 -32
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/meta_models.py +28 -22
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/user.py +7 -10
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/alarms.py +8 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/query.py +2 -1
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/user.py +2 -3
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/authentication/auth.py +19 -39
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/const.py +1 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/custom_test_case.py +42 -12
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_alarms.py +55 -1
- cornflow-1.2.0a2/cornflow/tests/unit/test_apiview.py +209 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_cli.py +6 -5
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_dags.py +0 -1
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_instances.py +12 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_log_in.py +8 -5
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_roles.py +38 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_token.py +11 -3
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_users.py +22 -6
- {cornflow-1.2.0a1 → cornflow-1.2.0a2/cornflow.egg-info}/PKG-INFO +13 -13
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow.egg-info/requires.txt +2 -2
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/requirements.txt +3 -3
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/setup.py +2 -2
- cornflow-1.2.0a1/cornflow/cli/utils.py +0 -26
- cornflow-1.2.0a1/cornflow/endpoints/alarms.py +0 -59
- cornflow-1.2.0a1/cornflow/tests/unit/test_apiview.py +0 -104
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/MANIFEST.in +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/airflow_config/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/airflow_config/plugins/XCom/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/airflow_config/plugins/XCom/gce_xcom_backend.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/airflow_config/plugins/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/airflow_config/webserver_ldap.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/app.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/actions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/arguments.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/config.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/permissions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/roles.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/schemas.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/tools/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/tools/api_generator.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/tools/endpoint_tools.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/tools/models_tools.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/tools/schema_generator.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/tools/schemas_tools.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/tools/tools.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/users.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/cli/views.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/commands/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/commands/access.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/commands/actions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/commands/cleanup.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/commands/dag.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/commands/permissions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/commands/roles.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/commands/schemas.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/commands/users.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/commands/views.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/action.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/apiview.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/case.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/dag.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/data_check.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/example_data.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/execution.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/health.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/instance.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/licenses.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/main_alarms.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/permission.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/roles.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/schemas.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/signup.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/tables.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/token.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/user.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/endpoints/user_role.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/gunicorn.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/README +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/alembic.ini +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/env.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/script.py.mako +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/00757b557b02_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/1af47a419bbd_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/4aac5e0c6e66_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/7c3ea5ab5501_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/991b98e24225_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/a472b5ad50b7_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/c2db9409cb5f_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/c8a6c762e818_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/ca449af8034c_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/d0e0700dcd8e_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/d1b5be1f0549_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/e1a50dae1ac9_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/e937a5234ce4_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/ebdd955fcc5e_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/migrations/versions/f3bee20314a2_.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/action.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/alarms.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/case.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/dag.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/dag_permissions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/execution.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/instance.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/main_alarms.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/permissions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/role.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/user_role.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/models/view.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/action.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/case.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/common.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/dag.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/example_data.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/execution.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/health.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/instance.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/main_alarms.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/model_json.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/patch.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/permissions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/role.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/schemas.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/solution_log.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/tables.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/user_role.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/schemas/view.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/authentication/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/authentication/decorators.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/authentication/ldap.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/compress.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/const.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/email.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/exceptions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/licenses.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/log_config.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/query_tools.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/utils.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/utils_tables.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/shared/validators.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/custom_liveServer.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/integration/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/integration/test_commands.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/integration/test_cornflowclient.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/ldap/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/ldap/test_ldap_authentication.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/__init__.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_actions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_application.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_cases.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_commands.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_data_checks.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_example_data.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_executions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_generate_from_schema.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_health.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_instances_file.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_licenses.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_main_alarms.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_permissions.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_schema_from_models.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_schemas.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_sign_up.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/test_tables.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow/tests/unit/tools.py +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow.egg-info/SOURCES.txt +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow.egg-info/dependency_links.txt +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow.egg-info/entry_points.txt +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/cornflow.egg-info/top_level.txt +0 -0
- {cornflow-1.2.0a1 → cornflow-1.2.0a2}/setup.cfg +0 -0
@@ -1,7 +1,7 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: cornflow
|
3
|
-
Version: 1.2.
|
4
|
-
Summary:
|
3
|
+
Version: 1.2.0a2
|
4
|
+
Summary: cornflow is an open source multi-solver optimization server with a REST API built using flask.
|
5
5
|
Home-page: https://github.com/baobabsoluciones/cornflow
|
6
6
|
Author: baobab soluciones
|
7
7
|
Author-email: cornflow@baobabsoluciones.es
|
@@ -12,8 +12,9 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
12
12
|
Requires-Python: >=3.9
|
13
13
|
Requires-Dist: alembic==1.9.2
|
14
14
|
Requires-Dist: apispec<=6.3.0
|
15
|
+
Requires-Dist: cachetools==5.3.3
|
15
16
|
Requires-Dist: click<=8.1.7
|
16
|
-
Requires-Dist: cornflow-client
|
17
|
+
Requires-Dist: cornflow-client>=1.2.0
|
17
18
|
Requires-Dist: cryptography<=42.0.5
|
18
19
|
Requires-Dist: disposable-email-domains>=0.0.86
|
19
20
|
Requires-Dist: Flask==2.3.2
|
@@ -40,7 +41,6 @@ Requires-Dist: requests<=2.32.3
|
|
40
41
|
Requires-Dist: SQLAlchemy==1.3.21
|
41
42
|
Requires-Dist: webargs<=8.3.0
|
42
43
|
Requires-Dist: Werkzeug==3.0.6
|
43
|
-
Requires-Dist: cachetools==5.3.3
|
44
44
|
Dynamic: author
|
45
45
|
Dynamic: author-email
|
46
46
|
Dynamic: classifier
|
@@ -50,7 +50,7 @@ Dynamic: requires-dist
|
|
50
50
|
Dynamic: requires-python
|
51
51
|
Dynamic: summary
|
52
52
|
|
53
|
-
|
53
|
+
cornflow
|
54
54
|
=========
|
55
55
|
|
56
56
|
.. image:: https://github.com/baobabsoluciones/cornflow/workflows/build/badge.svg?style=svg
|
@@ -70,13 +70,13 @@ Cornflow
|
|
70
70
|
|
71
71
|
.. image:: https://img.shields.io/badge/License-Apache2.0-blue
|
72
72
|
|
73
|
-
|
73
|
+
cornflow is an open source multi-solver optimization server with a REST API built using `flask <https://flask.palletsprojects.com>`_, `airflow <https://airflow.apache.org/>`_ and `pulp <https://coin-or.github.io/pulp/>`_.
|
74
74
|
|
75
|
-
While most deployment servers are based on the solving technique (MIP, CP, NLP, etc.),
|
75
|
+
While most deployment servers are based on the solving technique (MIP, CP, NLP, etc.), cornflow focuses on the optimization problems themselves. However, it does not impose any constraint on the type of problem and solution method to use.
|
76
76
|
|
77
|
-
With
|
77
|
+
With cornflow you can deploy a Traveling Salesman Problem solver next to a Knapsack solver or a Nurse Rostering Problem solver. As long as you describe the input and output data, you can upload any solution method for any problem and then use it with any data you want.
|
78
78
|
|
79
|
-
|
79
|
+
cornflow helps you formalize your problem by proposing development guidelines. It also provides a range of functionalities around your deployed solution method, namely:
|
80
80
|
|
81
81
|
* storage of users, instances, solutions and solution logs.
|
82
82
|
* deployment and maintenance of models, solvers and algorithms.
|
@@ -92,9 +92,9 @@ Cornflow helps you formalize your problem by proposing development guidelines. I
|
|
92
92
|
Installation instructions
|
93
93
|
-------------------------------
|
94
94
|
|
95
|
-
|
95
|
+
cornflow is tested with Ubuntu 20.04, python >= 3.8 and git.
|
96
96
|
|
97
|
-
Download the
|
97
|
+
Download the cornflow project and install requirements::
|
98
98
|
|
99
99
|
python3 -m venv venv
|
100
100
|
venv/bin/pip3 install cornflow
|
@@ -110,7 +110,7 @@ initialize the sqlite database::
|
|
110
110
|
flask create_admin_user -u cornflow -e cornflow_admin@admin.com -p cornflow_admin_password
|
111
111
|
|
112
112
|
|
113
|
-
activate the virtual environment and run
|
113
|
+
activate the virtual environment and run cornflow::
|
114
114
|
|
115
115
|
source venv/bin/activate
|
116
116
|
export FLASK_APP=cornflow.app
|
@@ -121,7 +121,7 @@ activate the virtual environment and run Cornflow::
|
|
121
121
|
export AIRFLOW_PWD=airflow_pwd
|
122
122
|
flask run
|
123
123
|
|
124
|
-
**
|
124
|
+
**cornflow needs a running installation of Airflow to operate and more configuration**. Check `the installation docs <https://baobabsoluciones.github.io/cornflow/main/install.html>`_ for more details on installing airflow, configuring the application and initializing the database.
|
125
125
|
|
126
126
|
Using cornflow to solve a PuLP model
|
127
127
|
---------------------------------------
|
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
cornflow
|
2
2
|
=========
|
3
3
|
|
4
4
|
.. image:: https://github.com/baobabsoluciones/cornflow/workflows/build/badge.svg?style=svg
|
@@ -18,13 +18,13 @@ Cornflow
|
|
18
18
|
|
19
19
|
.. image:: https://img.shields.io/badge/License-Apache2.0-blue
|
20
20
|
|
21
|
-
|
21
|
+
cornflow is an open source multi-solver optimization server with a REST API built using `flask <https://flask.palletsprojects.com>`_, `airflow <https://airflow.apache.org/>`_ and `pulp <https://coin-or.github.io/pulp/>`_.
|
22
22
|
|
23
|
-
While most deployment servers are based on the solving technique (MIP, CP, NLP, etc.),
|
23
|
+
While most deployment servers are based on the solving technique (MIP, CP, NLP, etc.), cornflow focuses on the optimization problems themselves. However, it does not impose any constraint on the type of problem and solution method to use.
|
24
24
|
|
25
|
-
With
|
25
|
+
With cornflow you can deploy a Traveling Salesman Problem solver next to a Knapsack solver or a Nurse Rostering Problem solver. As long as you describe the input and output data, you can upload any solution method for any problem and then use it with any data you want.
|
26
26
|
|
27
|
-
|
27
|
+
cornflow helps you formalize your problem by proposing development guidelines. It also provides a range of functionalities around your deployed solution method, namely:
|
28
28
|
|
29
29
|
* storage of users, instances, solutions and solution logs.
|
30
30
|
* deployment and maintenance of models, solvers and algorithms.
|
@@ -40,9 +40,9 @@ Cornflow helps you formalize your problem by proposing development guidelines. I
|
|
40
40
|
Installation instructions
|
41
41
|
-------------------------------
|
42
42
|
|
43
|
-
|
43
|
+
cornflow is tested with Ubuntu 20.04, python >= 3.8 and git.
|
44
44
|
|
45
|
-
Download the
|
45
|
+
Download the cornflow project and install requirements::
|
46
46
|
|
47
47
|
python3 -m venv venv
|
48
48
|
venv/bin/pip3 install cornflow
|
@@ -58,7 +58,7 @@ initialize the sqlite database::
|
|
58
58
|
flask create_admin_user -u cornflow -e cornflow_admin@admin.com -p cornflow_admin_password
|
59
59
|
|
60
60
|
|
61
|
-
activate the virtual environment and run
|
61
|
+
activate the virtual environment and run cornflow::
|
62
62
|
|
63
63
|
source venv/bin/activate
|
64
64
|
export FLASK_APP=cornflow.app
|
@@ -69,7 +69,7 @@ activate the virtual environment and run Cornflow::
|
|
69
69
|
export AIRFLOW_PWD=airflow_pwd
|
70
70
|
flask run
|
71
71
|
|
72
|
-
**
|
72
|
+
**cornflow needs a running installation of Airflow to operate and more configuration**. Check `the installation docs <https://baobabsoluciones.github.io/cornflow/main/install.html>`_ for more details on installing airflow, configuring the application and initializing the database.
|
73
73
|
|
74
74
|
Using cornflow to solve a PuLP model
|
75
75
|
---------------------------------------
|
@@ -19,5 +19,5 @@ STATE_COLORS = {
|
|
19
19
|
from airflow.www.utils import UIAlert
|
20
20
|
|
21
21
|
DASHBOARD_UIALERTS = [
|
22
|
-
UIAlert("Welcome! This is the backend of your
|
22
|
+
UIAlert("Welcome! This is the backend of your cornflow environment. Airflow™ is a platform created by the community to programmatically author, schedule and monitor workflows."),
|
23
23
|
]
|
@@ -3,7 +3,7 @@ import os.path
|
|
3
3
|
|
4
4
|
import click
|
5
5
|
from cornflow.shared import db
|
6
|
-
from flask_migrate import Migrate, migrate, upgrade, init
|
6
|
+
from flask_migrate import Migrate, migrate, upgrade, downgrade, init
|
7
7
|
|
8
8
|
from .utils import get_app
|
9
9
|
|
@@ -28,7 +28,27 @@ def migrate_migrations():
|
|
28
28
|
|
29
29
|
|
30
30
|
@migrations.command(name="upgrade", help="Apply migrations")
|
31
|
-
|
31
|
+
@click.option(
|
32
|
+
"-r", "--revision", type=str, help="The revision to upgrade to", default="head"
|
33
|
+
)
|
34
|
+
def upgrade_migrations(revision="head"):
|
35
|
+
app = get_app()
|
36
|
+
external = int(os.getenv("EXTERNAL_APP", 0))
|
37
|
+
if external == 0:
|
38
|
+
path = "./cornflow/migrations"
|
39
|
+
else:
|
40
|
+
path = f"./{os.getenv('EXTERNAL_APP_MODULE', 'external_app')}/migrations"
|
41
|
+
|
42
|
+
with app.app_context():
|
43
|
+
migration_client = Migrate(app=app, db=db, directory=path)
|
44
|
+
upgrade(revision=revision)
|
45
|
+
|
46
|
+
|
47
|
+
@migrations.command(name="downgrade", help="Downgrade migrations")
|
48
|
+
@click.option(
|
49
|
+
"-r", "--revision", type=str, help="The revision to downgrade to", default="-1"
|
50
|
+
)
|
51
|
+
def downgrade_migrations(revision="-1"):
|
32
52
|
app = get_app()
|
33
53
|
external = int(os.getenv("EXTERNAL_APP", 0))
|
34
54
|
if external == 0:
|
@@ -38,7 +58,7 @@ def upgrade_migrations():
|
|
38
58
|
|
39
59
|
with app.app_context():
|
40
60
|
migration_client = Migrate(app=app, db=db, directory=path)
|
41
|
-
|
61
|
+
downgrade(revision=revision)
|
42
62
|
|
43
63
|
|
44
64
|
@migrations.command(
|
@@ -6,6 +6,7 @@ from logging import error
|
|
6
6
|
|
7
7
|
|
8
8
|
import click
|
9
|
+
from .utils import get_db_conn
|
9
10
|
import cornflow
|
10
11
|
from cornflow.app import create_app
|
11
12
|
from cornflow.commands import (
|
@@ -56,15 +57,8 @@ def init_cornflow_service():
|
|
56
57
|
os.environ["SECRET_KEY"] = os.getenv("FERNET_KEY", Fernet.generate_key().decode())
|
57
58
|
|
58
59
|
# Cornflow db defaults
|
59
|
-
|
60
|
-
|
61
|
-
cornflow_db_user = os.getenv("CORNFLOW_DB_USER", "cornflow")
|
62
|
-
cornflow_db_password = os.getenv("CORNFLOW_DB_PASSWORD", "cornflow")
|
63
|
-
cornflow_db = os.getenv("CORNFLOW_DB", "cornflow")
|
64
|
-
cornflow_db_conn = os.getenv(
|
65
|
-
"cornflow_db_conn",
|
66
|
-
f"postgresql://{cornflow_db_user}:{cornflow_db_password}@{cornflow_db_host}:{cornflow_db_port}/{cornflow_db}",
|
67
|
-
)
|
60
|
+
os.environ["DEFAULT_POSTGRES"] = "1"
|
61
|
+
cornflow_db_conn = get_db_conn()
|
68
62
|
os.environ["DATABASE_URL"] = cornflow_db_conn
|
69
63
|
|
70
64
|
# Platform auth config and service users
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import os
|
2
|
+
import sys
|
3
|
+
from importlib import import_module
|
4
|
+
import warnings
|
5
|
+
|
6
|
+
|
7
|
+
def get_app():
|
8
|
+
env = os.getenv("FLASK_ENV", "development")
|
9
|
+
data_conn = get_db_conn()
|
10
|
+
if env == "production":
|
11
|
+
warnings.filterwarnings("ignore")
|
12
|
+
external = int(os.getenv("EXTERNAL_APP", 0))
|
13
|
+
if external == 0:
|
14
|
+
from cornflow.app import create_app
|
15
|
+
else:
|
16
|
+
sys.path.append("./")
|
17
|
+
external_app = os.getenv("EXTERNAL_APP_MODULE", "external_app")
|
18
|
+
external_module = import_module(external_app)
|
19
|
+
create_app = external_module.create_wsgi_app
|
20
|
+
|
21
|
+
if data_conn is None:
|
22
|
+
app = create_app(env)
|
23
|
+
else:
|
24
|
+
app = create_app(env, data_conn)
|
25
|
+
|
26
|
+
return app
|
27
|
+
|
28
|
+
|
29
|
+
def get_db_conn():
|
30
|
+
if int(os.getenv("DEFAULT_POSTGRES", 0)) == 0:
|
31
|
+
return os.getenv("DATABASE_URL", "sqlite:///cornflow.db")
|
32
|
+
else:
|
33
|
+
cornflow_db_host = os.getenv("CORNFLOW_DB_HOST", "cornflow_db")
|
34
|
+
cornflow_db_port = os.getenv("CORNFLOW_DB_PORT", "5432")
|
35
|
+
cornflow_db_user = os.getenv("CORNFLOW_DB_USER", "cornflow")
|
36
|
+
cornflow_db_password = os.getenv("CORNFLOW_DB_PASSWORD", "cornflow")
|
37
|
+
cornflow_db = os.getenv("CORNFLOW_DB", "cornflow")
|
38
|
+
return os.getenv(
|
39
|
+
"cornflow_db_conn",
|
40
|
+
f"postgresql://{cornflow_db_user}:{cornflow_db_password}@{cornflow_db_host}:{cornflow_db_port}/{cornflow_db}",
|
41
|
+
)
|
@@ -3,8 +3,9 @@ Initialization file for the endpoints module
|
|
3
3
|
All references to endpoints should be imported from here
|
4
4
|
The login resource gets created on app startup as it depends on configuration
|
5
5
|
"""
|
6
|
+
|
6
7
|
from .action import ActionListEndpoint
|
7
|
-
from .alarms import AlarmsEndpoint
|
8
|
+
from .alarms import AlarmsEndpoint, AlarmDetailEndpoint
|
8
9
|
from .apiview import ApiViewListEndpoint
|
9
10
|
from .case import (
|
10
11
|
CaseEndpoint,
|
@@ -225,6 +226,11 @@ alarms_resources = [
|
|
225
226
|
urls="/alarms/",
|
226
227
|
endpoint="alarms",
|
227
228
|
),
|
229
|
+
dict(
|
230
|
+
resource=AlarmDetailEndpoint,
|
231
|
+
urls="/alarms/<int:idx>/",
|
232
|
+
endpoint="alarms-detail",
|
233
|
+
),
|
228
234
|
dict(
|
229
235
|
resource=MainAlarmsEndpoint,
|
230
236
|
urls="/main-alarms/",
|
@@ -0,0 +1,123 @@
|
|
1
|
+
# Imports from libraries
|
2
|
+
from flask import current_app
|
3
|
+
from flask_apispec import doc, marshal_with, use_kwargs
|
4
|
+
|
5
|
+
# Import from internal modules
|
6
|
+
from cornflow.endpoints.meta_resource import BaseMetaResource
|
7
|
+
from cornflow.models import AlarmsModel
|
8
|
+
from cornflow.schemas.alarms import (
|
9
|
+
AlarmEditRequest,
|
10
|
+
AlarmsResponse,
|
11
|
+
AlarmsPostRequest,
|
12
|
+
QueryFiltersAlarms,
|
13
|
+
)
|
14
|
+
from cornflow.shared.authentication import Auth, authenticate
|
15
|
+
from cornflow.shared.exceptions import AirflowError, ObjectDoesNotExist, InvalidData
|
16
|
+
from cornflow.shared.const import SERVICE_ROLE
|
17
|
+
|
18
|
+
|
19
|
+
class AlarmsEndpoint(BaseMetaResource):
|
20
|
+
"""
|
21
|
+
Endpoint used to manage the table alarms.
|
22
|
+
Available methods: [get, post]
|
23
|
+
"""
|
24
|
+
|
25
|
+
def __init__(self):
|
26
|
+
super().__init__()
|
27
|
+
self.data_model = AlarmsModel
|
28
|
+
self.unique = ["id"]
|
29
|
+
|
30
|
+
@doc(
|
31
|
+
description="Get list of all the elements in the table",
|
32
|
+
tags=["None"],
|
33
|
+
)
|
34
|
+
@authenticate(auth_class=Auth())
|
35
|
+
@marshal_with(AlarmsResponse(many=True))
|
36
|
+
@use_kwargs(QueryFiltersAlarms, location="query")
|
37
|
+
def get(self, **kwargs):
|
38
|
+
"""
|
39
|
+
API method to get all the rows of the table.
|
40
|
+
It requires authentication to be passed in the form of a token that has to be linked to
|
41
|
+
an existing session (login) made by a user.
|
42
|
+
:return: A list of objects with the data, and an integer with the HTTP status code.
|
43
|
+
:rtype: Tuple(dict, integer)
|
44
|
+
"""
|
45
|
+
return self.get_list(**kwargs)
|
46
|
+
|
47
|
+
@doc(
|
48
|
+
description="Add a new row to the table",
|
49
|
+
tags=["None"],
|
50
|
+
)
|
51
|
+
@authenticate(auth_class=Auth())
|
52
|
+
@marshal_with(AlarmsResponse)
|
53
|
+
@use_kwargs(AlarmsPostRequest, location="json")
|
54
|
+
def post(self, **kwargs):
|
55
|
+
"""
|
56
|
+
API method to add a row to the table.
|
57
|
+
It requires authentication to be passed in the form of a token that has to be linked to
|
58
|
+
an existing session (login) made by a user.
|
59
|
+
:return: An object with the data for the created row,
|
60
|
+
and an integer with the HTTP status code.
|
61
|
+
:rtype: Tuple(dict, integer)
|
62
|
+
"""
|
63
|
+
return self.post_list(data=kwargs)
|
64
|
+
|
65
|
+
|
66
|
+
class AlarmDetailEndpointBase(BaseMetaResource):
|
67
|
+
"""
|
68
|
+
Endpoint used to get the information of a certain alarm. But not the data!
|
69
|
+
"""
|
70
|
+
|
71
|
+
def __init__(self):
|
72
|
+
super().__init__()
|
73
|
+
self.data_model = AlarmsModel
|
74
|
+
self.unique = ["id"]
|
75
|
+
|
76
|
+
|
77
|
+
class AlarmDetailEndpoint(AlarmDetailEndpointBase):
|
78
|
+
@doc(description="Get details of an alarm", tags=["None"], inherit=False)
|
79
|
+
@authenticate(auth_class=Auth())
|
80
|
+
@marshal_with(AlarmsResponse)
|
81
|
+
@BaseMetaResource.get_data_or_404
|
82
|
+
def get(self, idx):
|
83
|
+
"""
|
84
|
+
API method to get an execution created by the user and its related info.
|
85
|
+
It requires authentication to be passed in the form of a token that has to be linked to
|
86
|
+
an existing session (login) made by a user.
|
87
|
+
|
88
|
+
:param str idx: ID of the execution.
|
89
|
+
:return: A dictionary with a message (error if authentication failed, or the execution does not exist or
|
90
|
+
the data of the execution) and an integer with the HTTP status code.
|
91
|
+
:rtype: Tuple(dict, integer)
|
92
|
+
"""
|
93
|
+
current_app.logger.info(
|
94
|
+
f"User {self.get_user()} gets details of execution {idx}"
|
95
|
+
)
|
96
|
+
return self.get_detail(idx=idx)
|
97
|
+
|
98
|
+
@doc(description="Edit an execution", tags=["Executions"], inherit=False)
|
99
|
+
@authenticate(auth_class=Auth())
|
100
|
+
@use_kwargs(AlarmEditRequest, location="json")
|
101
|
+
def put(self, idx, **data):
|
102
|
+
"""
|
103
|
+
Edit an existing alarm
|
104
|
+
|
105
|
+
:param string idx: ID of the alarm.
|
106
|
+
:return: A dictionary with a message (error if authentication failed, or the alarm does not exist or
|
107
|
+
a message) and an integer with the HTTP status code.
|
108
|
+
:rtype: Tuple(dict, integer)
|
109
|
+
"""
|
110
|
+
current_app.logger.info(f"User {self.get_user()} edits alarm {idx}")
|
111
|
+
return self.put_detail(data, track_user=False, idx=idx)
|
112
|
+
|
113
|
+
@doc(description="Disable an alarm", tags=["None"])
|
114
|
+
@authenticate(auth_class=Auth())
|
115
|
+
def delete(self, idx):
|
116
|
+
"""
|
117
|
+
:param int alarm_id: Alarm id.
|
118
|
+
:return:
|
119
|
+
:rtype: Tuple(dict, integer)
|
120
|
+
"""
|
121
|
+
|
122
|
+
current_app.logger.info(f"Alarm {idx} was disabled by user {self.get_user()}")
|
123
|
+
return self.disable_detail(idx=idx)
|
@@ -2,15 +2,16 @@
|
|
2
2
|
External endpoint for the user to log in to the cornflow webserver
|
3
3
|
"""
|
4
4
|
|
5
|
+
from datetime import datetime, timezone, timedelta
|
6
|
+
|
5
7
|
# Partial imports
|
6
8
|
from flask import current_app, request
|
7
9
|
from flask_apispec import use_kwargs, doc
|
8
10
|
from sqlalchemy.exc import IntegrityError, DBAPIError
|
9
|
-
from datetime import datetime, timedelta
|
10
11
|
|
11
12
|
# Import from internal modules
|
12
13
|
from cornflow.endpoints.meta_resource import BaseMetaResource
|
13
|
-
from cornflow.models import UserModel, UserRoleModel
|
14
|
+
from cornflow.models import UserModel, UserRoleModel, PermissionsDAG
|
14
15
|
from cornflow.schemas.user import LoginEndpointRequest, LoginOpenAuthRequest
|
15
16
|
from cornflow.shared import db
|
16
17
|
from cornflow.shared.authentication import Auth, LDAPBase
|
@@ -51,22 +52,35 @@ class LoginBaseEndpoint(BaseMetaResource):
|
|
51
52
|
if auth_type == AUTH_DB:
|
52
53
|
user = self.auth_db_authenticate(**kwargs)
|
53
54
|
response.update({"change_password": check_last_password_change(user)})
|
54
|
-
current_app.logger.info(
|
55
|
+
current_app.logger.info(
|
56
|
+
f"User {user.id} logged in successfully using database authentication"
|
57
|
+
)
|
55
58
|
elif auth_type == AUTH_LDAP:
|
56
59
|
user = self.auth_ldap_authenticate(**kwargs)
|
57
|
-
current_app.logger.info(
|
60
|
+
current_app.logger.info(
|
61
|
+
f"User {user.id} logged in successfully using LDAP authentication"
|
62
|
+
)
|
58
63
|
elif auth_type == AUTH_OID:
|
59
|
-
if
|
64
|
+
if kwargs.get("username") and kwargs.get("password"):
|
60
65
|
if not current_app.config.get("SERVICE_USER_ALLOW_PASSWORD_LOGIN", 0):
|
61
|
-
raise InvalidUsage(
|
62
|
-
|
63
|
-
|
66
|
+
raise InvalidUsage(
|
67
|
+
"Must provide a token in Authorization header. Cannot log in with username and password",
|
68
|
+
400,
|
69
|
+
)
|
70
|
+
user = self.auth_oid_authenticate(
|
71
|
+
username=kwargs["username"], password=kwargs["password"]
|
72
|
+
)
|
73
|
+
current_app.logger.info(
|
74
|
+
f"Service user {user.id} logged in successfully using password"
|
75
|
+
)
|
64
76
|
token = self.auth_class.generate_token(user.id)
|
65
77
|
else:
|
66
78
|
token = self.auth_class().get_token_from_header(request.headers)
|
67
79
|
user = self.auth_oid_authenticate(token=token)
|
68
|
-
current_app.logger.info(
|
69
|
-
|
80
|
+
current_app.logger.info(
|
81
|
+
f"User {user.id} logged in successfully using OpenID authentication"
|
82
|
+
)
|
83
|
+
|
70
84
|
response.update({"token": token, "id": user.id})
|
71
85
|
return response, 200
|
72
86
|
else:
|
@@ -147,7 +161,9 @@ class LoginBaseEndpoint(BaseMetaResource):
|
|
147
161
|
|
148
162
|
return user
|
149
163
|
|
150
|
-
def auth_oid_authenticate(
|
164
|
+
def auth_oid_authenticate(
|
165
|
+
self, token: str = None, username: str = None, password: str = None
|
166
|
+
):
|
151
167
|
"""
|
152
168
|
Method in charge of performing the authentication using OpenID Connect tokens.
|
153
169
|
Supports any OIDC provider configured via provider_url.
|
@@ -158,33 +174,22 @@ class LoginBaseEndpoint(BaseMetaResource):
|
|
158
174
|
:return: the user object, or it raises an error if it has not been possible to log in
|
159
175
|
:rtype: :class:`UserModel`
|
160
176
|
"""
|
161
|
-
print("[auth_oid_authenticate] Starting OpenID authentication")
|
162
177
|
if token:
|
163
|
-
|
178
|
+
|
164
179
|
decoded_token = self.auth_class().decode_token(token)
|
165
|
-
print(f"[auth_oid_authenticate] Token decoded successfully: {decoded_token}")
|
166
|
-
|
167
|
-
username = decoded_token.get('username')
|
168
|
-
if not username:
|
169
|
-
print("[auth_oid_authenticate] Missing username in token claims")
|
170
|
-
raise InvalidCredentials(
|
171
|
-
"Invalid token: missing username claim",
|
172
|
-
log_txt="Token validation failed: missing username claim",
|
173
|
-
status_code=400
|
174
|
-
)
|
175
180
|
|
176
|
-
|
181
|
+
username = decoded_token.get("sub")
|
182
|
+
|
177
183
|
user = self.data_model.get_one_object(username=username)
|
178
184
|
|
179
185
|
if not user:
|
180
|
-
print(f"[auth_oid_authenticate] Creating new user: {username}")
|
181
186
|
current_app.logger.info(
|
182
187
|
f"OpenID user {username} does not exist and is created"
|
183
188
|
)
|
184
189
|
|
185
|
-
email = decoded_token.get(
|
186
|
-
first_name = decoded_token.get(
|
187
|
-
last_name = decoded_token.get(
|
190
|
+
email = decoded_token.get("email", f"{username}@cornflow.org")
|
191
|
+
first_name = decoded_token.get("given_name", "")
|
192
|
+
last_name = decoded_token.get("family_name", "")
|
188
193
|
|
189
194
|
data = {
|
190
195
|
"username": username,
|
@@ -203,13 +208,12 @@ class LoginBaseEndpoint(BaseMetaResource):
|
|
203
208
|
}
|
204
209
|
)
|
205
210
|
user_role.save()
|
211
|
+
if int(current_app.config["OPEN_DEPLOYMENT"]) == 1:
|
212
|
+
PermissionsDAG.add_all_permissions_to_user(user.id)
|
206
213
|
|
207
214
|
return user
|
208
215
|
|
209
|
-
elif
|
210
|
-
username
|
211
|
-
and password
|
212
|
-
):
|
216
|
+
elif username and password:
|
213
217
|
user = self.auth_db_authenticate(username, password)
|
214
218
|
if user.is_service_user():
|
215
219
|
return user
|
@@ -221,17 +225,34 @@ class LoginBaseEndpoint(BaseMetaResource):
|
|
221
225
|
def check_last_password_change(user):
|
222
226
|
"""
|
223
227
|
Check if the user needs to change their password based on the password rotation time.
|
224
|
-
|
228
|
+
|
225
229
|
:param user: The user object to check
|
226
230
|
:return: True if password needs to be changed, False otherwise
|
227
231
|
:rtype: bool
|
228
232
|
"""
|
229
233
|
if user.pwd_last_change:
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
234
|
+
# Handle the case where pwd_last_change is already a datetime object
|
235
|
+
if isinstance(user.pwd_last_change, datetime):
|
236
|
+
# If it's a naive datetime (no timezone info), make it timezone-aware
|
237
|
+
if user.pwd_last_change.tzinfo is None:
|
238
|
+
last_change = user.pwd_last_change.replace(tzinfo=timezone.utc)
|
239
|
+
else:
|
240
|
+
# Already timezone-aware
|
241
|
+
last_change = user.pwd_last_change
|
242
|
+
else:
|
243
|
+
# It's a timestamp (integer), convert to datetime
|
244
|
+
last_change = datetime.fromtimestamp(user.pwd_last_change, timezone.utc)
|
245
|
+
|
246
|
+
# Get current time with UTC timezone for proper comparison
|
247
|
+
current_time = datetime.now(timezone.utc)
|
248
|
+
|
249
|
+
# Calculate the expiration time based on the password rotation setting
|
250
|
+
expiration_time = last_change + timedelta(
|
251
|
+
days=int(current_app.config["PWD_ROTATION_TIME"])
|
252
|
+
)
|
253
|
+
|
254
|
+
# Compare the timezone-aware datetimes
|
255
|
+
if expiration_time < current_time:
|
235
256
|
return True
|
236
257
|
return False
|
237
258
|
|
@@ -13,6 +13,7 @@ from cornflow.shared.const import ALL_DEFAULT_ROLES
|
|
13
13
|
from cornflow.shared.exceptions import InvalidUsage, ObjectDoesNotExist, NoPermission
|
14
14
|
|
15
15
|
|
16
|
+
|
16
17
|
class BaseMetaResource(Resource, MethodResource):
|
17
18
|
"""
|
18
19
|
The base resource from all methods inherit from.
|
@@ -175,11 +176,18 @@ class BaseMetaResource(Resource, MethodResource):
|
|
175
176
|
METHODS USED FOR ACTIVATING / DISABLING RECORDS IN CASE WE DO NOT WANT TO DELETE THEM STRAIGHT AWAY
|
176
177
|
"""
|
177
178
|
|
178
|
-
def disable_detail(self):
|
179
|
+
def disable_detail(self, idx):
|
179
180
|
"""
|
180
|
-
Method
|
181
|
+
Method to DISABLE an object from the database
|
182
|
+
|
183
|
+
:param idx: the idx which identifies the object
|
184
|
+
:return: the object and a status code.
|
181
185
|
"""
|
182
|
-
|
186
|
+
row = self.data_model.query.get(idx)
|
187
|
+
if row is None:
|
188
|
+
raise ObjectDoesNotExist(f"Object with id {idx} not found.")
|
189
|
+
row.disable()
|
190
|
+
return {"message": "Object marked as disabled"}, 200
|
183
191
|
|
184
192
|
def activate_detail(self, **kwargs):
|
185
193
|
"""
|