cornflow 1.0.8a3__tar.gz → 1.0.10__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.
Files changed (181) hide show
  1. {cornflow-1.0.8a3/cornflow.egg-info → cornflow-1.0.10}/PKG-INFO +1 -1
  2. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/__init__.py +1 -0
  3. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/actions.py +1 -2
  4. cornflow-1.0.10/cornflow/cli/users.py +77 -0
  5. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/config.py +4 -2
  6. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/authentication/auth.py +72 -19
  7. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/licenses.py +1 -0
  8. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/custom_test_case.py +1 -3
  9. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_cli.py +143 -3
  10. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_licenses.py +2 -2
  11. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_token.py +35 -4
  12. {cornflow-1.0.8a3 → cornflow-1.0.10/cornflow.egg-info}/PKG-INFO +1 -1
  13. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow.egg-info/requires.txt +5 -5
  14. {cornflow-1.0.8a3 → cornflow-1.0.10}/setup.py +1 -1
  15. cornflow-1.0.8a3/cornflow/cli/users.py +0 -32
  16. {cornflow-1.0.8a3 → cornflow-1.0.10}/MANIFEST.in +0 -0
  17. {cornflow-1.0.8a3 → cornflow-1.0.10}/README.rst +0 -0
  18. {cornflow-1.0.8a3 → cornflow-1.0.10}/airflow_config/__init__.py +0 -0
  19. {cornflow-1.0.8a3 → cornflow-1.0.10}/airflow_config/airflow_local_settings.py +0 -0
  20. {cornflow-1.0.8a3 → cornflow-1.0.10}/airflow_config/plugins/XCom/__init__.py +0 -0
  21. {cornflow-1.0.8a3 → cornflow-1.0.10}/airflow_config/plugins/XCom/gce_xcom_backend.py +0 -0
  22. {cornflow-1.0.8a3 → cornflow-1.0.10}/airflow_config/plugins/__init__.py +0 -0
  23. {cornflow-1.0.8a3 → cornflow-1.0.10}/airflow_config/webserver_ldap.py +0 -0
  24. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/__init__.py +0 -0
  25. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/app.py +0 -0
  26. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/arguments.py +0 -0
  27. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/config.py +0 -0
  28. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/migrations.py +0 -0
  29. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/permissions.py +0 -0
  30. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/roles.py +0 -0
  31. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/schemas.py +0 -0
  32. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/service.py +0 -0
  33. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/tools/__init__.py +0 -0
  34. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/tools/api_generator.py +0 -0
  35. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/tools/endpoint_tools.py +0 -0
  36. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/tools/models_tools.py +0 -0
  37. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/tools/schema_generator.py +0 -0
  38. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/tools/schemas_tools.py +0 -0
  39. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/tools/tools.py +0 -0
  40. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/utils.py +0 -0
  41. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/cli/views.py +0 -0
  42. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/commands/__init__.py +0 -0
  43. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/commands/access.py +0 -0
  44. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/commands/actions.py +0 -0
  45. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/commands/cleanup.py +0 -0
  46. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/commands/dag.py +0 -0
  47. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/commands/permissions.py +0 -0
  48. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/commands/roles.py +0 -0
  49. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/commands/schemas.py +0 -0
  50. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/commands/users.py +0 -0
  51. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/commands/views.py +0 -0
  52. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/__init__.py +0 -0
  53. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/action.py +0 -0
  54. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/alarms.py +0 -0
  55. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/apiview.py +0 -0
  56. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/case.py +0 -0
  57. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/dag.py +0 -0
  58. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/data_check.py +0 -0
  59. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/example_data.py +0 -0
  60. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/execution.py +0 -0
  61. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/health.py +0 -0
  62. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/instance.py +0 -0
  63. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/licenses.py +0 -0
  64. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/login.py +0 -0
  65. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/main_alarms.py +0 -0
  66. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/meta_resource.py +0 -0
  67. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/permission.py +0 -0
  68. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/roles.py +0 -0
  69. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/schemas.py +0 -0
  70. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/signup.py +0 -0
  71. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/tables.py +0 -0
  72. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/token.py +0 -0
  73. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/user.py +0 -0
  74. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/endpoints/user_role.py +0 -0
  75. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/gunicorn.py +0 -0
  76. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/README +0 -0
  77. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/alembic.ini +0 -0
  78. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/env.py +0 -0
  79. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/script.py.mako +0 -0
  80. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/00757b557b02_.py +0 -0
  81. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/1af47a419bbd_.py +0 -0
  82. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/4aac5e0c6e66_.py +0 -0
  83. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/7c3ea5ab5501_.py +0 -0
  84. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/a472b5ad50b7_.py +0 -0
  85. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/c2db9409cb5f_.py +0 -0
  86. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/c8a6c762e818_.py +0 -0
  87. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/ca449af8034c_.py +0 -0
  88. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/d0e0700dcd8e_.py +0 -0
  89. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/d1b5be1f0549_.py +0 -0
  90. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/e1a50dae1ac9_.py +0 -0
  91. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/e937a5234ce4_.py +0 -0
  92. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/ebdd955fcc5e_.py +0 -0
  93. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/migrations/versions/f3bee20314a2_.py +0 -0
  94. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/__init__.py +0 -0
  95. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/action.py +0 -0
  96. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/alarms.py +0 -0
  97. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/base_data_model.py +0 -0
  98. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/case.py +0 -0
  99. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/dag.py +0 -0
  100. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/dag_permissions.py +0 -0
  101. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/execution.py +0 -0
  102. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/instance.py +0 -0
  103. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/main_alarms.py +0 -0
  104. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/meta_models.py +0 -0
  105. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/permissions.py +0 -0
  106. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/role.py +0 -0
  107. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/user.py +0 -0
  108. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/user_role.py +0 -0
  109. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/models/view.py +0 -0
  110. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/__init__.py +0 -0
  111. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/action.py +0 -0
  112. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/alarms.py +0 -0
  113. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/case.py +0 -0
  114. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/common.py +0 -0
  115. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/dag.py +0 -0
  116. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/example_data.py +0 -0
  117. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/execution.py +0 -0
  118. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/health.py +0 -0
  119. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/instance.py +0 -0
  120. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/main_alarms.py +0 -0
  121. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/model_json.py +0 -0
  122. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/patch.py +0 -0
  123. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/permissions.py +0 -0
  124. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/query.py +0 -0
  125. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/role.py +0 -0
  126. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/schemas.py +0 -0
  127. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/solution_log.py +0 -0
  128. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/tables.py +0 -0
  129. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/user.py +0 -0
  130. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/user_role.py +0 -0
  131. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/schemas/view.py +0 -0
  132. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/__init__.py +0 -0
  133. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/authentication/__init__.py +0 -0
  134. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/authentication/decorators.py +0 -0
  135. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/authentication/ldap.py +0 -0
  136. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/compress.py +0 -0
  137. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/const.py +0 -0
  138. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/email.py +0 -0
  139. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/exceptions.py +0 -0
  140. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/log_config.py +0 -0
  141. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/query_tools.py +0 -0
  142. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/utils.py +0 -0
  143. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/utils_tables.py +0 -0
  144. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/shared/validators.py +0 -0
  145. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/__init__.py +0 -0
  146. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/const.py +0 -0
  147. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/custom_liveServer.py +0 -0
  148. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/integration/__init__.py +0 -0
  149. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/integration/test_commands.py +0 -0
  150. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/integration/test_cornflowclient.py +0 -0
  151. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/ldap/__init__.py +0 -0
  152. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/ldap/test_ldap_authentication.py +0 -0
  153. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/__init__.py +0 -0
  154. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_actions.py +0 -0
  155. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_alarms.py +0 -0
  156. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_apiview.py +0 -0
  157. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_cases.py +0 -0
  158. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_commands.py +0 -0
  159. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_dags.py +0 -0
  160. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_data_checks.py +0 -0
  161. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_example_data.py +0 -0
  162. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_executions.py +0 -0
  163. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_generate_from_schema.py +0 -0
  164. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_health.py +0 -0
  165. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_instances.py +0 -0
  166. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_instances_file.py +0 -0
  167. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_log_in.py +0 -0
  168. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_main_alarms.py +0 -0
  169. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_permissions.py +0 -0
  170. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_roles.py +0 -0
  171. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_schema_from_models.py +0 -0
  172. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_schemas.py +0 -0
  173. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_sign_up.py +0 -0
  174. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_tables.py +0 -0
  175. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/test_users.py +0 -0
  176. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow/tests/unit/tools.py +0 -0
  177. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow.egg-info/SOURCES.txt +0 -0
  178. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow.egg-info/dependency_links.txt +0 -0
  179. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow.egg-info/entry_points.txt +0 -0
  180. {cornflow-1.0.8a3 → cornflow-1.0.10}/cornflow.egg-info/top_level.txt +0 -0
  181. {cornflow-1.0.8a3 → cornflow-1.0.10}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.2
2
2
  Name: cornflow
3
- Version: 1.0.8a3
3
+ Version: 1.0.10
4
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
@@ -3,6 +3,7 @@ init file to have this as a module
3
3
  """
4
4
 
5
5
  import click
6
+
6
7
  from cornflow.cli.actions import actions
7
8
  from cornflow.cli.config import config
8
9
  from cornflow.cli.migrations import migrations
@@ -1,6 +1,5 @@
1
- import os
2
-
3
1
  import click
2
+
4
3
  from cornflow.cli.utils import get_app
5
4
  from cornflow.commands import register_actions_command
6
5
  from .arguments import verbose
@@ -0,0 +1,77 @@
1
+ import click
2
+
3
+ from cornflow.cli.arguments import username, password, email, verbose
4
+ from cornflow.cli.utils import get_app
5
+ from cornflow.commands import create_user_with_role
6
+ from cornflow.models import UserModel
7
+ from cornflow.shared.authentication.auth import BIAuth
8
+ from cornflow.shared.const import SERVICE_ROLE, VIEWER_ROLE
9
+ from cornflow.shared.exceptions import (
10
+ ObjectDoesNotExist,
11
+ NoPermission,
12
+ )
13
+
14
+
15
+ @click.group(name="users", help="Commands to manage the users")
16
+ def users():
17
+ pass
18
+
19
+
20
+ @click.group(name="create", help="Create a user")
21
+ def create():
22
+ pass
23
+
24
+
25
+ users.add_command(create)
26
+
27
+
28
+ @create.command(name="service", help="Create a service user")
29
+ @username
30
+ @password
31
+ @email
32
+ @verbose
33
+ def create_service_user(username, password, email, verbose):
34
+ app = get_app()
35
+ with app.app_context():
36
+ create_user_with_role(
37
+ username, email, password, "service user", SERVICE_ROLE, verbose=verbose
38
+ )
39
+
40
+
41
+ @create.command(name="viewer", help="Create a viewer user")
42
+ @username
43
+ @password
44
+ @email
45
+ @verbose
46
+ def create_viewer_user(username, password, email, verbose):
47
+ app = get_app()
48
+ with app.app_context():
49
+ create_user_with_role(
50
+ username, email, password, "viewer user", VIEWER_ROLE, verbose=verbose
51
+ )
52
+
53
+
54
+ @create.command(
55
+ name="token",
56
+ help="Creates a token for a user that is never going to expire. This token can only be used on BI endpoints",
57
+ )
58
+ @click.option(
59
+ "--idx", "-i", type=int, help="The id of the user to generate the token for"
60
+ )
61
+ @username
62
+ @password
63
+ def create_unexpiring_token(idx, username, password):
64
+ app = get_app()
65
+ with app.app_context():
66
+ user = UserModel.get_one_object(id=idx)
67
+ asking_user = UserModel.get_one_user_by_username(username)
68
+
69
+ if not asking_user.check_hash(password) or not asking_user.is_service_user():
70
+ raise NoPermission("The asking user has no permissions to generate tokens")
71
+
72
+ if not user:
73
+ raise ObjectDoesNotExist("User does not exist")
74
+
75
+ token = BIAuth.generate_token(idx)
76
+ click.echo(token)
77
+ return True
@@ -6,7 +6,8 @@ from apispec.ext.marshmallow import MarshmallowPlugin
6
6
 
7
7
  class DefaultConfig(object):
8
8
  SERVICE_NAME = os.getenv("SERVICE_NAME", "Cornflow")
9
- SECRET_KEY = os.getenv("SECRET_KEY")
9
+ SECRET_TOKEN_KEY = os.getenv("SECRET_KEY")
10
+ SECRET_BI_KEY = os.getenv("SECRET_BI_KEY")
10
11
  SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL", "sqlite:///cornflow.db")
11
12
  AIRFLOW_URL = os.getenv("AIRFLOW_URL")
12
13
  AIRFLOW_USER = os.getenv("AIRFLOW_USER")
@@ -91,7 +92,8 @@ class Testing(DefaultConfig):
91
92
  DEBUG = False
92
93
  TESTING = True
93
94
  PROPAGATE_EXCEPTIONS = True
94
- SECRET_KEY = "TESTINGSECRETKEY"
95
+ SECRET_TOKEN_KEY = "TESTINGSECRETKEY"
96
+ SECRET_BI_KEY = "THISISANOTHERKEY"
95
97
  SQLALCHEMY_DATABASE_URI = os.getenv("DATABASE_URL", "sqlite:///cornflow_test.db")
96
98
  AIRFLOW_URL = os.getenv("AIRFLOW_URL", "http://localhost:8080")
97
99
  PRESERVE_CONTEXT_ON_EXCEPTION = False
@@ -99,8 +99,7 @@ class Auth:
99
99
  if user_id is None:
100
100
  err = "The user id passed to generate the token is not valid."
101
101
  raise InvalidUsage(
102
- err,
103
- log_txt="Error while trying to generate token. " + err
102
+ err, log_txt="Error while trying to generate token. " + err
104
103
  )
105
104
 
106
105
  payload = {
@@ -109,7 +108,9 @@ class Auth:
109
108
  "sub": user_id,
110
109
  }
111
110
 
112
- return jwt.encode(payload, current_app.config["SECRET_KEY"], algorithm="HS256")
111
+ return jwt.encode(
112
+ payload, current_app.config["SECRET_TOKEN_KEY"], algorithm="HS256"
113
+ )
113
114
 
114
115
  @staticmethod
115
116
  def decode_token(token: str = None) -> dict:
@@ -123,27 +124,26 @@ class Auth:
123
124
  if token is None:
124
125
  err = "The provided token is not valid."
125
126
  raise InvalidUsage(
126
- err,
127
- log_txt="Error while trying to decode token. " + err
127
+ err, log_txt="Error while trying to decode token. " + err
128
128
  )
129
129
  try:
130
130
  payload = jwt.decode(
131
- token, current_app.config["SECRET_KEY"], algorithms="HS256"
131
+ token, current_app.config["SECRET_TOKEN_KEY"], algorithms="HS256"
132
132
  )
133
133
  return {"user_id": payload["sub"]}
134
134
  except jwt.ExpiredSignatureError:
135
135
  raise InvalidCredentials(
136
136
  "The token has expired, please login again",
137
- log_txt="Error while trying to decode token. The token has expired."
137
+ log_txt="Error while trying to decode token. The token has expired.",
138
138
  )
139
139
  except jwt.InvalidTokenError:
140
140
  raise InvalidCredentials(
141
141
  "Invalid token, please try again with a new token",
142
- log_txt="Error while trying to decode token. The token is invalid."
142
+ log_txt="Error while trying to decode token. The token is invalid.",
143
143
  )
144
144
 
145
145
  def validate_oid_token(
146
- self, token: str, client_id: str, tenant_id: str, issuer: str, provider: int
146
+ self, token: str, client_id: str, tenant_id: str, issuer: str, provider: int
147
147
  ) -> dict:
148
148
  """
149
149
  This method takes a token issued by an OID provider, the relevant information about the OID provider
@@ -172,12 +172,12 @@ class Auth:
172
172
  except jwt.ExpiredSignatureError:
173
173
  raise InvalidCredentials(
174
174
  "The token has expired, please login again",
175
- log_txt="Error while trying to validate a token. The token has expired. "
175
+ log_txt="Error while trying to validate a token. The token has expired.",
176
176
  )
177
177
  except jwt.InvalidTokenError:
178
178
  raise InvalidCredentials(
179
179
  "Invalid token, please try again with a new token",
180
- log_txt="Error while trying to validate a token. The token is not valid. "
180
+ log_txt="Error while trying to validate a token. The token is not valid.",
181
181
  )
182
182
 
183
183
  @staticmethod
@@ -191,12 +191,14 @@ class Auth:
191
191
  :rtype: str
192
192
  """
193
193
  if headers is None:
194
- raise InvalidUsage(log_txt="Error while trying to get a token from header. The header is invalid.")
194
+ raise InvalidUsage(
195
+ log_txt="Error while trying to get a token from header. The header is invalid."
196
+ )
195
197
 
196
198
  if "Authorization" not in headers:
197
199
  raise InvalidCredentials(
198
200
  "Auth token is not available",
199
- log_txt="Error while trying to get a token from header. The auth token is not available."
201
+ log_txt="Error while trying to get a token from header. The auth token is not available.",
200
202
  )
201
203
  auth_header = headers.get("Authorization")
202
204
  if not auth_header:
@@ -206,8 +208,7 @@ class Auth:
206
208
  except Exception as e:
207
209
  err = f"The authorization header has a bad syntax: {e}"
208
210
  raise InvalidCredentials(
209
- err,
210
- log_txt=f"Error while trying to get a token from header. " + err
211
+ err, log_txt=f"Error while trying to get a token from header. " + err
211
212
  )
212
213
 
213
214
  def get_user_from_header(self, headers: Headers = None) -> UserModel:
@@ -222,8 +223,7 @@ class Auth:
222
223
  if headers is None:
223
224
  err = "Headers are missing from the request. Authentication was not possible to perform."
224
225
  raise InvalidUsage(
225
- err,
226
- log_txt="Error while trying to get user from header. " + err
226
+ err, log_txt="Error while trying to get user from header. " + err
227
227
  )
228
228
  token = self.get_token_from_header(headers)
229
229
  data = self.decode_token(token)
@@ -232,8 +232,7 @@ class Auth:
232
232
  if user is None:
233
233
  err = "User does not exist, invalid token."
234
234
  raise ObjectDoesNotExist(
235
- err,
236
- log_txt="Error while trying to get user from header. " + err
235
+ err, log_txt="Error while trying to get user from header. " + err
237
236
  )
238
237
  return user
239
238
 
@@ -460,3 +459,57 @@ class Auth:
460
459
  kid = self._get_key_id(token)
461
460
  jwk = self._get_jwk(kid, tenant_id, provider)
462
461
  return self._rsa_pem_from_jwk(jwk)
462
+
463
+
464
+ class BIAuth(Auth):
465
+ def __init__(self, user_model=UserModel):
466
+ super().__init__(user_model)
467
+
468
+ @staticmethod
469
+ def decode_token(token: str = None) -> dict:
470
+ """
471
+ Decodes a given JSON Web token and extracts the sub from it to give it back.
472
+
473
+ :param str token: the given JSON Web Token
474
+ :return: the sub field of the token as the user_id
475
+ :rtype: dict
476
+ """
477
+ if token is None:
478
+ err = "The provided token is not valid."
479
+ raise InvalidUsage(
480
+ err, log_txt="Error while trying to decode token. " + err
481
+ )
482
+ try:
483
+ payload = jwt.decode(
484
+ token, current_app.config["SECRET_BI_KEY"], algorithms="HS256"
485
+ )
486
+ return {"user_id": payload["sub"]}
487
+ except jwt.InvalidTokenError:
488
+ raise InvalidCredentials(
489
+ "Invalid token, please try again with a new token",
490
+ log_txt="Error while trying to decode token. The token is invalid.",
491
+ )
492
+
493
+ @staticmethod
494
+ def generate_token(user_id: int = None) -> str:
495
+ """
496
+ Generates a token given a user_id with a duration of one day
497
+
498
+ :param int user_id: user code to be encoded in the token to identify the user afterward.
499
+ :return: the generated token
500
+ :rtype: str
501
+ """
502
+ if user_id is None:
503
+ err = "The user id passed to generate the token is not valid."
504
+ raise InvalidUsage(
505
+ err, log_txt="Error while trying to generate token. " + err
506
+ )
507
+
508
+ payload = {
509
+ "iat": datetime.utcnow(),
510
+ "sub": user_id,
511
+ }
512
+
513
+ return jwt.encode(
514
+ payload, current_app.config["SECRET_BI_KEY"], algorithm="HS256"
515
+ )
@@ -65,6 +65,7 @@ def get_licenses_summary():
65
65
  :return: a list of dicts with library, license, version, author, description, home page and license text.
66
66
  """
67
67
  license_list = []
68
+ # TODO: pkg_resources.working_set is deprecated, find a better way to get the list of packages
68
69
  for pkg in sorted(pkg_resources.working_set, key=lambda x: str(x).lower()):
69
70
  license_list += [
70
71
  {
@@ -101,7 +101,6 @@ class CustomTestCase(TestCase):
101
101
 
102
102
  @staticmethod
103
103
  def assign_role(user_id, role_id):
104
-
105
104
  if UserRoleModel.check_if_role_assigned(user_id, role_id):
106
105
  user_role = UserRoleModel.query.filter_by(
107
106
  user_id=user_id, role_id=role_id
@@ -288,7 +287,6 @@ class CustomTestCase(TestCase):
288
287
  self.assertEqual(payload_to_check["solution"], row.json["solution"])
289
288
 
290
289
  def delete_row(self, url):
291
-
292
290
  response = self.client.delete(
293
291
  url, follow_redirects=True, headers=self.get_header_with_auth(self.token)
294
292
  )
@@ -733,7 +731,7 @@ class LoginTestCases:
733
731
  self.assertEqual(str, type(self.response.json["token"]))
734
732
  decoded_token = jwt.decode(
735
733
  self.response.json["token"],
736
- current_app.config["SECRET_KEY"],
734
+ current_app.config["SECRET_TOKEN_KEY"],
737
735
  algorithms="HS256",
738
736
  )
739
737
 
@@ -2,17 +2,19 @@ import configparser
2
2
  import os
3
3
 
4
4
  from click.testing import CliRunner
5
+ from flask_testing import TestCase
6
+
5
7
  from cornflow.app import create_app
6
8
  from cornflow.cli import cli
7
- from cornflow.models import UserModel
8
9
  from cornflow.models import (
9
10
  ActionModel,
10
11
  RoleModel,
11
12
  ViewModel,
12
13
  PermissionViewRoleModel,
13
14
  )
15
+ from cornflow.models import UserModel
14
16
  from cornflow.shared import db
15
- from flask_testing import TestCase
17
+ from cornflow.shared.exceptions import NoPermission, ObjectDoesNotExist
16
18
 
17
19
 
18
20
  class CLITests(TestCase):
@@ -208,6 +210,7 @@ class CLITests(TestCase):
208
210
 
209
211
  def test_service_user_command(self):
210
212
  runner = CliRunner()
213
+ self.test_roles_init_command()
211
214
  result = runner.invoke(
212
215
  cli,
213
216
  [
@@ -222,7 +225,144 @@ class CLITests(TestCase):
222
225
  "test@test.org",
223
226
  ],
224
227
  )
225
- self.assertEqual(result.exit_code, 1)
228
+ self.assertEqual(result.exit_code, 0)
226
229
  user = UserModel.get_one_user_by_email("test@test.org")
227
230
  self.assertEqual(user.username, "test")
228
231
  self.assertEqual(user.email, "test@test.org")
232
+ self.assertEqual(user.roles, {4: "service"})
233
+ self.assertTrue(user.is_service_user())
234
+
235
+ def test_viewer_user_command(self):
236
+ runner = CliRunner()
237
+ self.test_roles_init_command()
238
+ result = runner.invoke(
239
+ cli,
240
+ [
241
+ "users",
242
+ "create",
243
+ "viewer",
244
+ "-u",
245
+ "test",
246
+ "-p",
247
+ "testPassword1!",
248
+ "-e",
249
+ "test@test.org",
250
+ ],
251
+ )
252
+
253
+ self.assertEqual(result.exit_code, 0)
254
+ user = UserModel.get_one_user_by_email("test@test.org")
255
+ self.assertEqual(user.username, "test")
256
+ self.assertEqual(user.email, "test@test.org")
257
+ self.assertEqual(user.roles, {1: "viewer"})
258
+ self.assertFalse(user.is_service_user())
259
+
260
+ def test_generate_token(self):
261
+ runner = CliRunner()
262
+
263
+ self.test_roles_init_command()
264
+
265
+ result = runner.invoke(
266
+ cli,
267
+ [
268
+ "users",
269
+ "create",
270
+ "viewer",
271
+ "-u",
272
+ "viewer_user",
273
+ "-p",
274
+ "testPassword1!",
275
+ "-e",
276
+ "viewer@test.org",
277
+ ],
278
+ )
279
+
280
+ self.assertEqual(result.exit_code, 0)
281
+
282
+ user_id = UserModel.get_one_user_by_username("viewer_user").id
283
+
284
+ result = runner.invoke(
285
+ cli,
286
+ [
287
+ "users",
288
+ "create",
289
+ "service",
290
+ "-u",
291
+ "test",
292
+ "-p",
293
+ "testPassword1!",
294
+ "-e",
295
+ "test@test.org",
296
+ ],
297
+ )
298
+
299
+ self.assertEqual(result.exit_code, 0)
300
+
301
+ result = runner.invoke(
302
+ cli,
303
+ [
304
+ "users",
305
+ "create",
306
+ "token",
307
+ "-i",
308
+ user_id,
309
+ "-u",
310
+ "test",
311
+ "-p",
312
+ "testPassword1!",
313
+ ],
314
+ )
315
+
316
+ self.assertIn("ey", result.output)
317
+
318
+ result = runner.invoke(
319
+ cli,
320
+ [
321
+ "users",
322
+ "create",
323
+ "token",
324
+ "-i",
325
+ user_id,
326
+ "-u",
327
+ "test",
328
+ "-p",
329
+ "Otherpassword",
330
+ ],
331
+ )
332
+
333
+ self.assertEqual(result.exit_code, 1)
334
+ self.assertIsInstance(result.exception, NoPermission)
335
+
336
+ result = runner.invoke(
337
+ cli,
338
+ [
339
+ "users",
340
+ "create",
341
+ "token",
342
+ "-i",
343
+ user_id,
344
+ "-u",
345
+ "viewer_user",
346
+ "-p",
347
+ "testPassword1!",
348
+ ],
349
+ )
350
+
351
+ self.assertIsInstance(result.exception, NoPermission)
352
+
353
+ result = runner.invoke(
354
+ cli,
355
+ [
356
+ "users",
357
+ "create",
358
+ "token",
359
+ "-i",
360
+ 100,
361
+ "-u",
362
+ "test",
363
+ "-p",
364
+ "testPassword1!",
365
+ ],
366
+ )
367
+
368
+ self.assertIsInstance(result.exception, ObjectDoesNotExist)
@@ -11,9 +11,9 @@ class TestLicensesListEndpoint(CustomTestCase):
11
11
  requirements = content.split("\n")
12
12
 
13
13
  requirements = [
14
- r.split("=")[0].split(">")[0].split("<")[0].lower()
14
+ r.split("=")[0].split(">")[0].split("<")[0].split("@")[0].lower()
15
15
  for r in requirements
16
- if r != ""
16
+ if r != "" and not r.startswith("#")
17
17
  ]
18
18
  return requirements
19
19
 
@@ -1,16 +1,16 @@
1
1
  """
2
2
  Unit test for the token endpoint
3
3
  """
4
+ import json
4
5
 
5
- # Import from libraries
6
6
  from flask import current_app
7
- import json
8
7
 
9
- # Import from internal modules
10
8
  from cornflow.models import UserModel
11
9
  from cornflow.shared import db
12
- from cornflow.tests.custom_test_case import CheckTokenTestCase
10
+ from cornflow.shared.authentication.auth import BIAuth, Auth
11
+ from cornflow.shared.exceptions import InvalidUsage
13
12
  from cornflow.tests.const import LOGIN_URL
13
+ from cornflow.tests.custom_test_case import CheckTokenTestCase, CustomTestCase
14
14
 
15
15
 
16
16
  class TestCheckToken(CheckTokenTestCase.TokenEndpoint):
@@ -64,3 +64,34 @@ class TestCheckToken(CheckTokenTestCase.TokenEndpoint):
64
64
  self.get_check_token()
65
65
  self.assertEqual(200, self.response.status_code)
66
66
  self.assertEqual(0, self.response.json["valid"])
67
+
68
+
69
+ class TestUnexpiringToken(CustomTestCase):
70
+ def test_token_unexpiring(self):
71
+ auth = BIAuth()
72
+
73
+ token = auth.generate_token(1)
74
+
75
+ response = auth.decode_token(token)
76
+ self.assertEqual(response, {"user_id": 1})
77
+
78
+ token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE3MDM1OTI1OTIsInN1YiI6MX0.Plvmi02FMfZOTn6bxArELEmDeyuP-2X794c5VtAFgCg"
79
+
80
+ response = auth.decode_token(token)
81
+ self.assertEqual(response, {"user_id": 1})
82
+
83
+ def test_user_not_valid(self):
84
+ auth = BIAuth()
85
+ self.assertRaises(InvalidUsage, auth.generate_token, None)
86
+
87
+ auth = Auth()
88
+ self.assertRaises(InvalidUsage, auth.generate_token, None)
89
+
90
+ def test_token_not_valid(self):
91
+ auth = BIAuth()
92
+ self.assertRaises(InvalidUsage, auth.decode_token, None)
93
+ token = ""
94
+ self.assertRaises(InvalidUsage, auth.decode_token, token)
95
+
96
+ auth = Auth()
97
+ self.assertRaises(InvalidUsage, auth.decode_token, None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 1.2
2
2
  Name: cornflow
3
- Version: 1.0.8a3
3
+ Version: 1.0.10
4
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
@@ -1,8 +1,8 @@
1
1
  alembic==1.9.2
2
2
  apispec<=6.2.0
3
3
  click<=8.1.3
4
- cornflow-client==1.0.16a2
5
- cryptography<=39.0.2
4
+ cornflow-client<=1.0.16
5
+ cryptography<=42.0.5
6
6
  disposable-email-domains>=0.0.86
7
7
  Flask==2.3.2
8
8
  flask-apispec<=0.11.4
@@ -14,7 +14,7 @@ Flask-Migrate<=4.0.4
14
14
  Flask-RESTful<=0.3.9
15
15
  Flask-SQLAlchemy==2.5.1
16
16
  gevent==23.9.1
17
- gunicorn<=20.1.0
17
+ gunicorn<=22.0.0
18
18
  jsonpatch<=1.32
19
19
  ldap3<=2.9.1
20
20
  marshmallow<=3.19.0
@@ -22,10 +22,10 @@ PuLP<=2.7.0
22
22
  psycopg2<=2.95
23
23
  PyJWT<=2.6.0
24
24
  pytups>=0.86.2
25
- requests<=2.29.0
25
+ requests<=2.31.0
26
26
  SQLAlchemy==1.3.21
27
27
  webargs<=8.2.0
28
- Werkzeug<=2.3.3
28
+ Werkzeug<=2.3.8
29
29
 
30
30
  [:python_version < "3.11"]
31
31
  greenlet<=2.0.2
@@ -9,7 +9,7 @@ with open("requirements.txt", "r") as fh:
9
9
 
10
10
  setuptools.setup(
11
11
  name="cornflow",
12
- version="1.0.8a3",
12
+ version="1.0.10",
13
13
  author="baobab soluciones",
14
14
  author_email="cornflow@baobabsoluciones.es",
15
15
  description="Cornflow is an open source multi-solver optimization server with a REST API built using flask.",
@@ -1,32 +0,0 @@
1
- import click
2
- from cornflow.cli.arguments import username, password, email, verbose
3
- from cornflow.cli.utils import get_app
4
- from cornflow.commands import create_user_with_role
5
- from cornflow.shared.const import SERVICE_ROLE
6
-
7
-
8
- @click.group(name="users", help="Commands to manage the users")
9
- def users():
10
- pass
11
-
12
-
13
- @click.group(name="create", help="Create a user")
14
- def create():
15
- pass
16
-
17
-
18
- users.add_command(create)
19
-
20
-
21
- @create.command(name="service", help="Create a service user")
22
- @username
23
- @password
24
- @email
25
- @verbose
26
- def create_service_user(username, password, email, verbose):
27
- app = get_app()
28
- with app.app_context():
29
-
30
- create_user_with_role(
31
- username, email, password, "service user", SERVICE_ROLE, verbose=verbose
32
- )
File without changes
File without changes