cornflow 1.2.3a5__py3-none-any.whl → 1.2.4__py3-none-any.whl

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/app.py CHANGED
@@ -4,6 +4,8 @@ Main file with the creation of the app logic
4
4
 
5
5
  # Full imports
6
6
  import os
7
+ from logging.config import dictConfig
8
+
7
9
  import click
8
10
 
9
11
  # Partial imports
@@ -13,9 +15,8 @@ from flask_apispec.extension import FlaskApiSpec
13
15
  from flask_cors import CORS
14
16
  from flask_migrate import Migrate
15
17
  from flask_restful import Api
16
- from logging.config import dictConfig
17
- from werkzeug.middleware.dispatcher import DispatcherMiddleware
18
18
  from werkzeug.exceptions import NotFound
19
+ from werkzeug.middleware.dispatcher import DispatcherMiddleware
19
20
 
20
21
  # Module imports
21
22
  from cornflow.commands import (
@@ -36,7 +37,14 @@ from cornflow.endpoints.login import LoginEndpoint, LoginOpenAuthEndpoint
36
37
  from cornflow.endpoints.signup import SignUpEndpoint
37
38
  from cornflow.shared import db, bcrypt
38
39
  from cornflow.shared.compress import init_compress
39
- from cornflow.shared.const import AUTH_DB, AUTH_LDAP, AUTH_OID
40
+ from cornflow.shared.const import (
41
+ AUTH_DB,
42
+ AUTH_LDAP,
43
+ AUTH_OID,
44
+ CONDITIONAL_ENDPOINTS,
45
+ SIGNUP_WITH_AUTH,
46
+ SIGNUP_WITH_NO_AUTH,
47
+ )
40
48
  from cornflow.shared.exceptions import initialize_errorhandlers, ConfigurationError
41
49
  from cornflow.shared.log_config import log_config
42
50
 
@@ -95,13 +103,21 @@ def create_app(env_name="development", dataconn=None):
95
103
 
96
104
  if auth_type == AUTH_DB:
97
105
  signup_activated = int(app.config["SIGNUP_ACTIVATED"])
98
- if signup_activated == 1:
99
- api.add_resource(SignUpEndpoint, "/signup/", endpoint="signup")
100
- api.add_resource(LoginEndpoint, "/login/", endpoint="login")
106
+ if signup_activated in [SIGNUP_WITH_AUTH, SIGNUP_WITH_NO_AUTH]:
107
+ api.add_resource(
108
+ SignUpEndpoint, CONDITIONAL_ENDPOINTS["signup"], endpoint="signup"
109
+ )
110
+ api.add_resource(
111
+ LoginEndpoint, CONDITIONAL_ENDPOINTS["login"], endpoint="login"
112
+ )
101
113
  elif auth_type == AUTH_LDAP:
102
- api.add_resource(LoginEndpoint, "/login/", endpoint="login")
114
+ api.add_resource(
115
+ LoginEndpoint, CONDITIONAL_ENDPOINTS["login"], endpoint="login"
116
+ )
103
117
  elif auth_type == AUTH_OID:
104
- api.add_resource(LoginOpenAuthEndpoint, "/login/", endpoint="login")
118
+ api.add_resource(
119
+ LoginOpenAuthEndpoint, CONDITIONAL_ENDPOINTS["login"], endpoint="login"
120
+ )
105
121
  else:
106
122
  raise ConfigurationError(
107
123
  error="Invalid authentication type",
cornflow/cli/service.py CHANGED
@@ -36,6 +36,7 @@ from cornflow.shared.const import (
36
36
  ADMIN_ROLE,
37
37
  SERVICE_ROLE,
38
38
  PLANNER_ROLE,
39
+ SIGNUP_WITH_AUTH,
39
40
  )
40
41
  from cornflow.shared import db
41
42
  from cryptography.fernet import Fernet
@@ -171,7 +172,7 @@ def _setup_environment_variables():
171
172
  os.environ["CORNFLOW_LOGGING"] = cornflow_logging
172
173
  open_deployment = os.getenv("OPEN_DEPLOYMENT", 1)
173
174
  os.environ["OPEN_DEPLOYMENT"] = str(open_deployment)
174
- signup_activated = os.getenv("SIGNUP_ACTIVATED", 1)
175
+ signup_activated = os.getenv("SIGNUP_ACTIVATED", SIGNUP_WITH_AUTH)
175
176
  os.environ["SIGNUP_ACTIVATED"] = str(signup_activated)
176
177
  user_access_all_objects = os.getenv("USER_ACCESS_ALL_OBJECTS", 0)
177
178
  os.environ["USER_ACCESS_ALL_OBJECTS"] = str(user_access_all_objects)
@@ -3,7 +3,7 @@ from importlib import import_module
3
3
 
4
4
  from flask import current_app
5
5
 
6
- from cornflow.endpoints import resources, alarms_resources
6
+ from cornflow.endpoints import alarms_resources, get_resources
7
7
  from cornflow.models import RoleModel
8
8
  from cornflow.shared.const import (
9
9
  EXTRA_PERMISSION_ASSIGNATION,
@@ -17,6 +17,9 @@ def get_all_external(external_app):
17
17
  Get all resources, extra permissions, and custom roles actions.
18
18
  external_app: If provided, it will get the resources and extra permissions for the external app.
19
19
  """
20
+ # We get base and conditional resources
21
+ resources = get_resources()
22
+
20
23
  if external_app is None:
21
24
  resources_to_register = resources
22
25
  extra_permissions = EXTRA_PERMISSION_ASSIGNATION
@@ -53,12 +53,14 @@ def register_base_permissions_command(external_app: str = None, verbose: bool =
53
53
  save_and_delete_permissions(permissions_to_register, permissions_to_delete)
54
54
 
55
55
  if len(permissions_to_register) > 0:
56
- current_app.logger.info(f"Permissions registered: {permissions_to_register}")
56
+ current_app.logger.info(
57
+ f"Permissions registered: {len(permissions_to_register)}"
58
+ )
57
59
  else:
58
60
  current_app.logger.info("No new permissions to register")
59
61
 
60
62
  if len(permissions_to_delete) > 0:
61
- current_app.logger.info(f"Permissions deleted: {permissions_to_delete}")
63
+ current_app.logger.info(f"Permissions deleted: {len(permissions_to_delete)}")
62
64
  else:
63
65
  current_app.logger.info("No permissions to delete")
64
66
 
@@ -296,7 +298,7 @@ def register_dag_permissions_command(
296
298
 
297
299
  if verbose:
298
300
  if len(permissions) > 1:
299
- current_app.logger.info(f"DAG permissions registered: {permissions}")
301
+ current_app.logger.info(f"DAG permissions registered: {len(permissions)}")
300
302
  else:
301
303
  current_app.logger.info("No new DAG permissions")
302
304
 
@@ -7,6 +7,8 @@ from sqlalchemy.exc import DBAPIError, IntegrityError
7
7
 
8
8
  from cornflow.endpoints import resources, alarms_resources
9
9
 
10
+ from cornflow.endpoints import alarms_resources, get_resources
11
+
10
12
  # Imports from internal libraries
11
13
  from cornflow.models import ViewModel
12
14
  from cornflow.shared import db
@@ -182,6 +184,7 @@ def get_database_view():
182
184
 
183
185
 
184
186
  def get_resources_to_register(external_app):
187
+ resources = get_resources()
185
188
  if external_app is None:
186
189
  resources_to_register = resources
187
190
  if current_app.config["ALARMS_ENDPOINTS"]:
cornflow/config.py CHANGED
@@ -1,5 +1,5 @@
1
1
  import os
2
- from .shared.const import AUTH_DB, PLANNER_ROLE, AUTH_OID
2
+ from .shared.const import AUTH_DB, PLANNER_ROLE, AUTH_OID, SIGNUP_WITH_AUTH, SIGNUP_WITH_NO_AUTH
3
3
  from apispec import APISpec
4
4
  from apispec.ext.marshmallow import MarshmallowPlugin
5
5
 
@@ -25,7 +25,7 @@ class DefaultConfig(object):
25
25
  DEBUG = True
26
26
  TESTING = True
27
27
  LOG_LEVEL = int(os.getenv("LOG_LEVEL", 20))
28
- SIGNUP_ACTIVATED = int(os.getenv("SIGNUP_ACTIVATED", 1))
28
+ SIGNUP_ACTIVATED = int(os.getenv("SIGNUP_ACTIVATED", SIGNUP_WITH_AUTH))
29
29
  CORNFLOW_SERVICE_USER = os.getenv("CORNFLOW_SERVICE_USER", "service_user")
30
30
 
31
31
  # If service user is allowed to log with username and password
@@ -119,7 +119,7 @@ class Testing(DefaultConfig):
119
119
  AIRFLOW_PWD = os.getenv("AIRFLOW_PWD", "admin")
120
120
  OPEN_DEPLOYMENT = 1
121
121
  LOG_LEVEL = int(os.getenv("LOG_LEVEL", 10))
122
-
122
+ SIGNUP_ACTIVATED = SIGNUP_WITH_NO_AUTH
123
123
 
124
124
  class TestingOpenAuth(Testing):
125
125
  """
@@ -157,5 +157,5 @@ app_config = {
157
157
  "testing": Testing,
158
158
  "production": Production,
159
159
  "testing-oauth": TestingOpenAuth,
160
- "testing-root": TestingApplicationRoot
160
+ "testing-root": TestingApplicationRoot,
161
161
  }
@@ -4,6 +4,8 @@ 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 flask import current_app
8
+ from cornflow.shared.const import CONDITIONAL_ENDPOINTS
7
9
  from .action import ActionListEndpoint
8
10
  from .alarms import AlarmsEndpoint, AlarmDetailEndpoint
9
11
  from .apiview import ApiViewListEndpoint
@@ -237,3 +239,28 @@ alarms_resources = [
237
239
  endpoint="main-alarms",
238
240
  ),
239
241
  ]
242
+
243
+
244
+ def get_resources():
245
+ """
246
+ Get the resources based on the configuration
247
+
248
+ :return: The resources based on the configuration
249
+ :rtype: list
250
+ """
251
+ base_resources = resources.copy()
252
+ registered_resources = current_app.view_functions.keys()
253
+ for resource in registered_resources:
254
+ if resource in CONDITIONAL_ENDPOINTS.keys():
255
+ # Check if the resource already exists
256
+ if resource not in [
257
+ present_resource["endpoint"] for present_resource in base_resources
258
+ ]:
259
+ base_resources.append(
260
+ dict(
261
+ resource=current_app.view_functions[resource].view_class,
262
+ urls=CONDITIONAL_ENDPOINTS[resource],
263
+ endpoint=resource,
264
+ )
265
+ )
266
+ return base_resources
@@ -1,6 +1,5 @@
1
- """
1
+ """ """
2
2
 
3
- """
4
3
  # Import from libraries
5
4
  from flask_apispec import doc, marshal_with, use_kwargs
6
5
  from flask import current_app
@@ -1,6 +1,7 @@
1
1
  """
2
2
  External endpoint for the user to signup
3
3
  """
4
+
4
5
  # Import from libraries
5
6
  from flask import current_app
6
7
  from flask_apispec import use_kwargs, doc
@@ -9,8 +10,8 @@ from flask_apispec import use_kwargs, doc
9
10
  from cornflow.endpoints.meta_resource import BaseMetaResource
10
11
  from cornflow.models import PermissionsDAG, UserRoleModel, UserModel
11
12
  from cornflow.schemas.user import SignupRequest
12
- from cornflow.shared.authentication import Auth
13
- from cornflow.shared.const import AUTH_LDAP, AUTH_OID
13
+ from cornflow.shared.authentication import Auth, authenticate
14
+ from cornflow.shared.const import AUTH_LDAP, AUTH_OID, ADMIN_ROLE, SIGNUP_WITH_NO_AUTH
14
15
  from cornflow.shared.exceptions import (
15
16
  EndpointNotImplemented,
16
17
  InvalidCredentials,
@@ -23,6 +24,8 @@ class SignUpEndpoint(BaseMetaResource):
23
24
  Endpoint used to sign up to the cornflow web server.
24
25
  """
25
26
 
27
+ ROLES_WITH_ACCESS = [ADMIN_ROLE]
28
+
26
29
  def __init__(self):
27
30
  super().__init__()
28
31
  self.data_model = UserModel
@@ -30,6 +33,11 @@ class SignUpEndpoint(BaseMetaResource):
30
33
  self.user_role_association = UserRoleModel
31
34
 
32
35
  @doc(description="Sign up", tags=["Users"])
36
+ @authenticate(
37
+ auth_class=Auth(),
38
+ optional_auth="SIGNUP_ACTIVATED",
39
+ no_auth_list=[SIGNUP_WITH_NO_AUTH],
40
+ )
33
41
  @use_kwargs(SignupRequest, location="json")
34
42
  def post(self, **kwargs):
35
43
  """
@@ -57,14 +65,12 @@ class SignUpEndpoint(BaseMetaResource):
57
65
  if auth_type == AUTH_LDAP:
58
66
  err = "The user has to sign up on the active directory"
59
67
  raise EndpointNotImplemented(
60
- err,
61
- log_txt="Error while user tries to sign up. " + err
68
+ err, log_txt="Error while user tries to sign up. " + err
62
69
  )
63
70
  elif auth_type == AUTH_OID:
64
71
  err = "The user has to sign up with the OpenID protocol"
65
72
  raise EndpointNotImplemented(
66
- err,
67
- log_txt="Error while user tries to sign up. " + err
73
+ err, log_txt="Error while user tries to sign up. " + err
68
74
  )
69
75
 
70
76
  user = self.data_model(kwargs)
@@ -72,14 +78,13 @@ class SignUpEndpoint(BaseMetaResource):
72
78
  if user.check_username_in_use():
73
79
  raise InvalidCredentials(
74
80
  error="Username already in use, please supply another username",
75
- log_txt="Error while user tries to sign up. Username already in use."
76
-
81
+ log_txt="Error while user tries to sign up. Username already in use.",
77
82
  )
78
83
 
79
84
  if user.check_email_in_use():
80
85
  raise InvalidCredentials(
81
86
  error="Email already in use, please supply another email address",
82
- log_txt="Error while user tries to sign up. Email already in use."
87
+ log_txt="Error while user tries to sign up. Email already in use.",
83
88
  )
84
89
 
85
90
  user.save()
@@ -94,8 +99,9 @@ class SignUpEndpoint(BaseMetaResource):
94
99
  token = self.auth_class.generate_token(user.id)
95
100
  except Exception as e:
96
101
  raise InvalidUsage(
97
- error="Error in generating user token: " + str(e), status_code=400,
98
- log_txt="Error while user tries to sign up. Unable to generate token."
102
+ error="Error in generating user token: " + str(e),
103
+ status_code=400,
104
+ log_txt="Error while user tries to sign up. Unable to generate token.",
99
105
  )
100
106
  current_app.logger.info(f"New user created: {user}")
101
107
  return {"token": token, "id": user.id}, 201
@@ -1,17 +1,24 @@
1
1
  """
2
2
  This file contains the decorator used for the authentication
3
3
  """
4
+
4
5
  from functools import wraps
5
6
  from .auth import Auth
6
7
  from cornflow.shared.exceptions import InvalidCredentials
8
+ from flask import current_app
9
+ import os
7
10
 
8
11
 
9
- def authenticate(auth_class: Auth):
12
+ def authenticate(
13
+ auth_class: Auth, optional_auth: str = None, no_auth_list: list = None
14
+ ):
10
15
  """
11
16
  This is the decorator used for the authentication
12
17
 
13
18
  :param auth_class: the class used for the authentication. It should be `BaseAuth` or a class that inherits from it
14
19
  and that has a authenticate method.
20
+ :param optional_auth: the env variable that indicates if authentication should be deactivated
21
+ :param no_auth_list: the list of the values that indicates if authentication should be deactivated
15
22
  :type auth_class: `BaseAuth`
16
23
  :return: the wrapped function
17
24
  """
@@ -32,11 +39,35 @@ def authenticate(auth_class: Auth):
32
39
  :param kwargs: the original kwargs sent to the decorated function
33
40
  :return: the result of the call to the function
34
41
  """
42
+ if endpoint_qualified_for_no_auth(no_auth_list, optional_auth):
43
+ # Authentication is deactivated for this value
44
+ return func(*args, **kwargs)
45
+
35
46
  if auth_class.authenticate():
36
47
  return func(*args, **kwargs)
37
48
  else:
38
49
  raise InvalidCredentials("Unable to authenticate the user")
39
-
40
50
  return wrapper
41
51
 
42
52
  return decorator
53
+
54
+ def endpoint_qualified_for_no_auth(no_auth_list, optional_auth):
55
+ """
56
+ This function is used to check if the endpoint is qualified for no authentication.
57
+
58
+ :param no_auth_list: the list of the values that indicates if authentication should be deactivated
59
+ :param optional_auth: the env variable that indicates if authentication should be deactivated
60
+ :type no_auth_list: list
61
+ :type optional_auth: str
62
+ :return: True if the endpoint is qualified for no authentication, False otherwise
63
+ """
64
+ if optional_auth is None:
65
+ return False
66
+ if no_auth_list is None:
67
+ raise InvalidCredentials(
68
+ "The list of the values that indicates if authentication should be deactivated is not defined"
69
+ )
70
+ env_variable = current_app.config[optional_auth]
71
+ if env_variable in no_auth_list:
72
+ return True
73
+ return False
cornflow/shared/const.py CHANGED
@@ -1,7 +1,8 @@
1
1
  """
2
2
  In this file we import the values for different constants on cornflow server
3
3
  """
4
- CORNFLOW_VERSION = "1.2.3.a5"
4
+
5
+ CORNFLOW_VERSION = "1.2.4"
5
6
  INTERNAL_TOKEN_ISSUER = "cornflow"
6
7
 
7
8
  # endpoints responses for health check
@@ -44,6 +45,13 @@ AIRFLOW_TO_STATE_MAP = dict(
44
45
  failed=EXEC_STATE_ERROR,
45
46
  queued=EXEC_STATE_QUEUED,
46
47
  )
48
+ # SIGNUP OPTIONS
49
+ # NO_SIGNUP: no signup endpoint
50
+ # SIGNUP_WITH_NO_AUTH: signup endpoint with no auth
51
+ # SIGNUP_WITH_AUTH: signup endpoint with auth
52
+ NO_SIGNUP = 0
53
+ SIGNUP_WITH_NO_AUTH = 1
54
+ SIGNUP_WITH_AUTH = 2
47
55
 
48
56
  # These codes and names are inherited from flask app builder in order to have the same names and values
49
57
  # as this library that is the base of airflow
@@ -121,3 +129,9 @@ AIRFLOW_NOT_REACHABLE_MSG = "Airflow is not reachable"
121
129
  DAG_PAUSED_MSG = "The dag exists but it is paused in airflow"
122
130
  AIRFLOW_ERROR_MSG = "Airflow responded with an error:"
123
131
  DATA_DOES_NOT_EXIST_MSG = "The data entity does not exist on the database"
132
+
133
+ # Conditional endpoints
134
+ CONDITIONAL_ENDPOINTS = {
135
+ "signup": "/signup/",
136
+ "login": "/login/",
137
+ }
@@ -13,7 +13,7 @@ TestApiViewListEndpoint
13
13
  """
14
14
 
15
15
  # Import from internal modules
16
- from cornflow.endpoints import ApiViewListEndpoint, resources, alarms_resources
16
+ from cornflow.endpoints import ApiViewListEndpoint, alarms_resources, get_resources
17
17
  from cornflow.models import ViewModel
18
18
  from cornflow.shared.const import ROLES_MAP
19
19
  from cornflow.tests.const import APIVIEW_URL
@@ -42,6 +42,8 @@ class TestApiViewListEndpoint(CustomTestCase):
42
42
  """
43
43
  super().setUp()
44
44
  self.roles_with_access = ApiViewListEndpoint.ROLES_WITH_ACCESS
45
+ # Get resources within application context
46
+ resources = get_resources()
45
47
  self.payload = [
46
48
  {
47
49
  "name": view["endpoint"],
@@ -127,6 +129,8 @@ class TestApiViewModel(CustomTestCase):
127
129
  """
128
130
  super().setUp()
129
131
  self.roles_with_access = ApiViewListEndpoint.ROLES_WITH_ACCESS
132
+ # Get resources within application context
133
+ resources = get_resources()
130
134
  self.payload = [
131
135
  {
132
136
  "name": view["endpoint"],
@@ -141,6 +145,8 @@ class TestApiViewModel(CustomTestCase):
141
145
  """
142
146
  Test that the get_all_objects method works properly
143
147
  """
148
+ # Get resources within application context
149
+ resources = get_resources()
144
150
  expected_count = len(resources) + len(alarms_resources)
145
151
  # Test getting all objects
146
152
  all_instances = ViewModel.get_all_objects().all()
@@ -33,7 +33,7 @@ from cornflow.models import (
33
33
  from cornflow.models import UserModel
34
34
  from cornflow.shared import db
35
35
  from cornflow.shared.exceptions import NoPermission, ObjectDoesNotExist
36
- from cornflow.endpoints import resources, alarms_resources
36
+ from cornflow.endpoints import alarms_resources, get_resources
37
37
 
38
38
 
39
39
  class CLITests(TestCase):
@@ -278,6 +278,7 @@ class CLITests(TestCase):
278
278
  - Correct number of views created
279
279
  - Database state after initialization
280
280
  """
281
+ resources = get_resources()
281
282
  runner = CliRunner()
282
283
  result = runner.invoke(cli, ["views", "init", "-v"])
283
284
  self.assertEqual(result.exit_code, 0)
@@ -315,6 +316,7 @@ class CLITests(TestCase):
315
316
  - Correct number of actions, roles, views, and permissions
316
317
  - Database state after initialization
317
318
  """
319
+ resources = get_resources()
318
320
  runner = CliRunner()
319
321
  result = runner.invoke(cli, ["permissions", "init", "-v"])
320
322
  self.assertEqual(result.exit_code, 0)
@@ -325,7 +327,7 @@ class CLITests(TestCase):
325
327
  self.assertEqual(len(actions), 5)
326
328
  self.assertEqual(len(roles), 4)
327
329
  self.assertEqual(len(views), (len(resources) + len(alarms_resources)))
328
- self.assertEqual(len(permissions), 562)
330
+ self.assertEqual(len(permissions), 583)
329
331
 
330
332
  def test_permissions_base_command(self):
331
333
  """
@@ -337,6 +339,7 @@ class CLITests(TestCase):
337
339
  - Correct setup of all permission components
338
340
  - Database state consistency
339
341
  """
342
+ resources = get_resources()
340
343
  runner = CliRunner()
341
344
  runner.invoke(cli, ["actions", "init", "-v"])
342
345
  runner.invoke(cli, ["roles", "init", "-v"])
@@ -350,7 +353,7 @@ class CLITests(TestCase):
350
353
  self.assertEqual(len(actions), 5)
351
354
  self.assertEqual(len(roles), 4)
352
355
  self.assertEqual(len(views), (len(resources) + len(alarms_resources)))
353
- self.assertEqual(len(permissions), 562)
356
+ self.assertEqual(len(permissions), 583)
354
357
 
355
358
  def test_service_entrypoint(self):
356
359
  """
@@ -31,7 +31,7 @@ from cornflow.app import (
31
31
  register_views,
32
32
  )
33
33
  from cornflow.commands.dag import register_deployed_dags_command_test
34
- from cornflow.endpoints import resources, alarms_resources
34
+ from cornflow.endpoints import alarms_resources, get_resources
35
35
  from cornflow.models import (
36
36
  ActionModel,
37
37
  PermissionViewRoleModel,
@@ -98,6 +98,7 @@ class TestCommands(TestCase):
98
98
  "email": "testemail@test.org",
99
99
  "password": "Testpassword1!",
100
100
  }
101
+ resources = get_resources()
101
102
  self.resources = resources + alarms_resources
102
103
  self.runner = self.create_app().test_cli_runner()
103
104
  self.runner.invoke(register_roles, ["-v"])
@@ -0,0 +1,103 @@
1
+ """
2
+ Unit tests for the get_resources function
3
+ """
4
+
5
+ import unittest
6
+ from unittest.mock import patch, MagicMock
7
+ from flask import Flask
8
+ from cornflow.endpoints import get_resources, resources
9
+ from cornflow.shared.const import CONDITIONAL_ENDPOINTS
10
+
11
+
12
+ class TestGetResources(unittest.TestCase):
13
+ """Test the get_resources function"""
14
+
15
+ def setUp(self):
16
+ self.app = Flask(__name__)
17
+ self.mock_signup_view = MagicMock()
18
+ self.mock_signup_view.view_class = MagicMock()
19
+ self.mock_login_view = MagicMock()
20
+ self.mock_login_view.view_class = MagicMock()
21
+
22
+ def test_returns_base_resources_when_no_conditional_endpoints(self):
23
+ """Test that get_resources returns base resources when no conditional endpoints are registered"""
24
+ with self.app.app_context():
25
+ with patch("flask.current_app.view_functions", {}):
26
+ result = get_resources()
27
+ self.assertEqual(result, resources)
28
+
29
+ def test_adds_conditional_endpoint_when_registered(self):
30
+ """Test that get_resources adds conditional endpoints when they are registered"""
31
+ with self.app.app_context():
32
+ mock_view_functions = {"signup": self.mock_signup_view}
33
+
34
+ with patch("flask.current_app.view_functions", mock_view_functions):
35
+ result = get_resources()
36
+
37
+ # Should have one more resource than base
38
+ self.assertEqual(len(result), len(resources) + 1)
39
+
40
+ # Check that signup endpoint was added correctly
41
+ signup_resource = next(
42
+ (r for r in result if r["endpoint"] == "signup"), None
43
+ )
44
+ self.assertIsNotNone(signup_resource)
45
+ self.assertEqual(
46
+ signup_resource["urls"], CONDITIONAL_ENDPOINTS["signup"]
47
+ )
48
+ self.assertEqual(
49
+ signup_resource["resource"], self.mock_signup_view.view_class
50
+ )
51
+
52
+ def test_adds_both_signup_and_login_when_registered(self):
53
+ """Test that get_resources adds both signup and login when both are registered"""
54
+ with self.app.app_context():
55
+ mock_view_functions = {
56
+ "signup": self.mock_signup_view,
57
+ "login": self.mock_login_view,
58
+ }
59
+
60
+ with patch("flask.current_app.view_functions", mock_view_functions):
61
+ result = get_resources()
62
+
63
+ # Should have two more resources than base
64
+ self.assertEqual(len(result), len(resources) + 2)
65
+
66
+ # Check that both endpoints were added correctly
67
+ signup_resource = next(
68
+ (r for r in result if r["endpoint"] == "signup"), None
69
+ )
70
+ login_resource = next(
71
+ (r for r in result if r["endpoint"] == "login"), None
72
+ )
73
+
74
+ self.assertIsNotNone(signup_resource)
75
+ self.assertIsNotNone(login_resource)
76
+ self.assertEqual(
77
+ signup_resource["urls"], CONDITIONAL_ENDPOINTS["signup"]
78
+ )
79
+ self.assertEqual(login_resource["urls"], CONDITIONAL_ENDPOINTS["login"])
80
+
81
+ def test_does_not_add_duplicate_endpoints(self):
82
+ """Test that get_resources does not add endpoints that already exist in base resources"""
83
+ with self.app.app_context():
84
+ mock_view_functions = {
85
+ "instance": MagicMock()
86
+ } # 'instance' already exists in base resources
87
+
88
+ with patch("flask.current_app.view_functions", mock_view_functions):
89
+ result = get_resources()
90
+ self.assertEqual(len(result), len(resources))
91
+
92
+ def test_ignores_non_conditional_endpoints(self):
93
+ """Test that get_resources ignores endpoints that are not in CONDITIONAL_ENDPOINTS"""
94
+ with self.app.app_context():
95
+ mock_view_functions = {"non_conditional_endpoint": MagicMock()}
96
+
97
+ with patch("flask.current_app.view_functions", mock_view_functions):
98
+ result = get_resources()
99
+ self.assertEqual(len(result), len(resources))
100
+
101
+
102
+ if __name__ == "__main__":
103
+ unittest.main()
@@ -1,18 +1,25 @@
1
1
  """
2
2
  Unit test for the sign-up endpoint
3
3
  """
4
- from cornflow.commands import access_init_command
5
- from cornflow.commands.dag import register_deployed_dags_command_test
6
4
 
7
5
  # Import from libraries
8
- from flask_testing import TestCase
6
+
7
+ # Import from internal modules
8
+
9
9
  import json
10
+ from unittest.mock import patch
11
+
12
+ # Import from libraries
13
+ from flask_testing import TestCase
10
14
 
11
15
  # Import from internal modules
12
16
  from cornflow.app import create_app
13
- from cornflow.models import UserModel, UserRoleModel
14
- from cornflow.shared.const import PLANNER_ROLE
17
+ from cornflow.commands import access_init_command
18
+ from cornflow.commands.dag import register_deployed_dags_command_test
19
+ from cornflow.models import UserModel, UserRoleModel, ViewModel, PermissionViewRoleModel
15
20
  from cornflow.shared import db
21
+ from cornflow.shared.authentication import Auth
22
+ from cornflow.shared.const import PLANNER_ROLE, ADMIN_ROLE, POST_ACTION, NO_SIGNUP, SIGNUP_WITH_AUTH
16
23
  from cornflow.tests.const import SIGNUP_URL
17
24
 
18
25
 
@@ -95,7 +102,7 @@ class TestSignUp(TestCase):
95
102
  class TestSignUpDeactivated(TestCase):
96
103
  def create_app(self):
97
104
  app = create_app("testing")
98
- app.config["SIGNUP_ACTIVATED"] = 0
105
+ app.config["SIGNUP_ACTIVATED"] = NO_SIGNUP
99
106
  return app
100
107
 
101
108
  def setUp(self):
@@ -120,3 +127,171 @@ class TestSignUpDeactivated(TestCase):
120
127
  )
121
128
 
122
129
  self.assertEqual(response.status_code, 400)
130
+
131
+
132
+ class TestSignUpAuthenticated(TestCase):
133
+ """Test the authenticated signup endpoint (SIGNUP_ACTIVATED=SIGNUP_WITH_AUTH)"""
134
+
135
+ def create_app(self):
136
+ with patch("cornflow.config.Testing.SIGNUP_ACTIVATED", SIGNUP_WITH_AUTH):
137
+ app = create_app("testing")
138
+ return app
139
+
140
+ def setUp(self):
141
+ db.create_all()
142
+ access_init_command(verbose=False)
143
+ register_deployed_dags_command_test(verbose=False)
144
+
145
+ # Create test data
146
+ self.data = {
147
+ "username": "testname",
148
+ "email": "test@test.com",
149
+ "password": "Testpassword1!",
150
+ }
151
+
152
+ # Create an admin user for testing
153
+ self.admin_user = UserModel(
154
+ {
155
+ "username": "admin",
156
+ "email": "admin@test.com",
157
+ "password": "Adminpassword1!",
158
+ }
159
+ )
160
+ self.admin_user.save()
161
+
162
+ # Assign admin role to the user
163
+ admin_role = UserRoleModel(
164
+ {"user_id": self.admin_user.id, "role_id": ADMIN_ROLE}
165
+ )
166
+ admin_role.save()
167
+
168
+ # Create a regular user for testing
169
+ self.regular_user = UserModel(
170
+ {
171
+ "username": "regular",
172
+ "email": "regular@test.com",
173
+ "password": "Regularpassword1!",
174
+ }
175
+ )
176
+ self.regular_user.save()
177
+
178
+ # Assign planner role to the user
179
+ planner_role = UserRoleModel(
180
+ {"user_id": self.regular_user.id, "role_id": PLANNER_ROLE}
181
+ )
182
+ planner_role.save()
183
+
184
+ def tearDown(self):
185
+ db.session.remove()
186
+ db.drop_all()
187
+
188
+ def get_auth_token(self, user):
189
+ """Helper method to get authentication token for a user"""
190
+ auth = Auth()
191
+ return auth.generate_token(user.id)
192
+
193
+ def test_authenticated_signup_admin_can_register(self):
194
+ """Test that admin users can register new users"""
195
+ payload = self.data
196
+ admin_token = self.get_auth_token(self.admin_user)
197
+
198
+ response = self.client.post(
199
+ SIGNUP_URL,
200
+ data=json.dumps(payload),
201
+ follow_redirects=True,
202
+ headers={
203
+ "Content-Type": "application/json",
204
+ "Authorization": f"Bearer {admin_token}",
205
+ },
206
+ )
207
+
208
+ self.assertEqual(201, response.status_code)
209
+ self.assertEqual(str, type(response.json["token"]))
210
+ self.assertEqual(int, type(response.json["id"]))
211
+ self.assertEqual(
212
+ PLANNER_ROLE,
213
+ UserRoleModel.query.filter_by(user_id=response.json["id"]).first().role_id,
214
+ )
215
+ self.assertNotEqual(None, UserModel.get_one_user_by_email(self.data["email"]))
216
+
217
+ def test_authenticated_signup_regular_user_cannot_register(self):
218
+ """Test that regular users cannot register new users"""
219
+ payload = self.data
220
+ regular_token = self.get_auth_token(self.regular_user)
221
+
222
+ response = self.client.post(
223
+ SIGNUP_URL,
224
+ data=json.dumps(payload),
225
+ follow_redirects=True,
226
+ headers={
227
+ "Content-Type": "application/json",
228
+ "Authorization": f"Bearer {regular_token}",
229
+ },
230
+ )
231
+
232
+ # Should return 403 Forbidden due to insufficient permissions
233
+ self.assertEqual(403, response.status_code)
234
+ self.assertIn("error", response.json)
235
+
236
+ def test_authenticated_signup_no_auth_header_fails(self):
237
+ """Test that signup fails without authentication header"""
238
+ payload = self.data
239
+
240
+ response = self.client.post(
241
+ SIGNUP_URL,
242
+ data=json.dumps(payload),
243
+ follow_redirects=True,
244
+ headers={"Content-Type": "application/json"},
245
+ )
246
+
247
+ # Should return 400 Bad Request due to missing authorization
248
+ self.assertEqual(400, response.status_code)
249
+ self.assertIn("error", response.json)
250
+
251
+ def test_authenticated_signup_invalid_token_fails(self):
252
+ """Test that signup fails with invalid token"""
253
+ payload = self.data
254
+
255
+ response = self.client.post(
256
+ SIGNUP_URL,
257
+ data=json.dumps(payload),
258
+ follow_redirects=True,
259
+ headers={
260
+ "Content-Type": "application/json",
261
+ "Authorization": "Bearer invalid_token",
262
+ },
263
+ )
264
+
265
+ # Should return 400 Bad Request due to invalid token
266
+ self.assertEqual(400, response.status_code)
267
+ self.assertIn("error", response.json)
268
+
269
+ def test_signup_permissions_are_registered_in_database(self):
270
+ """Test that signup permissions are properly registered in the database"""
271
+ # Check that the signup endpoint exists in the views table
272
+ signup_view = ViewModel.query.filter_by(name="signup").first()
273
+ self.assertIsNotNone(
274
+ signup_view, "Signup endpoint should be registered in views table"
275
+ )
276
+ self.assertEqual("/signup/", signup_view.url_rule)
277
+
278
+ # Check that admin role has permissions for signup endpoint
279
+ admin_permissions = PermissionViewRoleModel.query.filter_by(
280
+ role_id=ADMIN_ROLE, api_view_id=signup_view.id
281
+ ).all()
282
+
283
+ # Admin should have POST permission for signup endpoint
284
+ self.assertGreater(
285
+ len(admin_permissions),
286
+ 0,
287
+ "Admin should have permissions for signup endpoint",
288
+ )
289
+
290
+ # Verify that the permission is for POST action
291
+ post_permission = PermissionViewRoleModel.query.filter_by(
292
+ role_id=ADMIN_ROLE, api_view_id=signup_view.id, action_id=POST_ACTION
293
+ ).first()
294
+
295
+ self.assertIsNotNone(
296
+ post_permission, "Admin should have POST permission for signup endpoint"
297
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cornflow
3
- Version: 1.2.3a5
3
+ Version: 1.2.4
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
@@ -14,7 +14,7 @@ Requires-Dist: alembic==1.9.2
14
14
  Requires-Dist: apispec<=6.3.0
15
15
  Requires-Dist: cachetools==5.3.3
16
16
  Requires-Dist: click<=8.1.7
17
- Requires-Dist: cornflow-client>=1.2.3.a5
17
+ Requires-Dist: cornflow-client<=1.2.4
18
18
  Requires-Dist: cryptography<=44.0.1
19
19
  Requires-Dist: disposable-email-domains>=0.0.86
20
20
  Requires-Dist: Flask==2.3.2
@@ -37,7 +37,7 @@ Requires-Dist: PuLP<=2.9.0
37
37
  Requires-Dist: psycopg2<=2.9.9
38
38
  Requires-Dist: PyJWT<=2.8.0
39
39
  Requires-Dist: pytups>=0.86.2
40
- Requires-Dist: requests<=2.32.3
40
+ Requires-Dist: requests<=2.32.4
41
41
  Requires-Dist: SQLAlchemy==1.3.21
42
42
  Requires-Dist: webargs<=8.3.0
43
43
  Requires-Dist: Werkzeug==3.0.6
@@ -5,8 +5,8 @@ airflow_config/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
5
5
  airflow_config/plugins/XCom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  airflow_config/plugins/XCom/gce_xcom_backend.py,sha256=vCGvF2jbfZt5bOv-pk5Q_kUR6LomFUojIymimSJmj3o,1795
7
7
  cornflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- cornflow/app.py,sha256=5ebZuYTfUFKVQ-lxcTeS0hATPGn3zacYdQ7jkApwmrE,7698
9
- cornflow/config.py,sha256=c3CNu6wzm5mDJLF6GjrnBKQSNKsRr69S2vQ9jCSAhYw,5628
8
+ cornflow/app.py,sha256=Jov5TS85r_dKYqZ1tZ34JxavoHJbfBt0DdyDxh2V2tI,8008
9
+ cornflow/config.py,sha256=kOfQRJIUGfNlRhNu5bHEIFO9qEzTQAS3LtuAntt_Wmc,5725
10
10
  cornflow/gunicorn.py,sha256=uO-Yk7w7nvQSWh12iDxsVvlG-_2BiKIIjm2UiTk4P9E,480
11
11
  cornflow/cli/__init__.py,sha256=2QfFLxLcX5zYt3Ok3QKNWQvUvAeEnLsH7xiGN3GjwFU,853
12
12
  cornflow/cli/actions.py,sha256=-vm7hUrRTtJA6y0ZK2cIeTKFQi8Wl2Z6ico2px9Fiuk,524
@@ -16,7 +16,7 @@ cornflow/cli/migrations.py,sha256=nJbmulqwFxuaSPmo9hJiTl1pUSojeXfAdd9aGM_g9ic,24
16
16
  cornflow/cli/permissions.py,sha256=fyIp5M0ZpJWVf_tGZG0DN0Gnk1geruadkdKK11cmRk8,1116
17
17
  cornflow/cli/roles.py,sha256=1dvAq1Bp1iEAUUfXFeL8kYjI0ZchYlDwIBQpTtwV6vs,529
18
18
  cornflow/cli/schemas.py,sha256=s9IUJWa2G0kpqJaN6PcwbwZtGChTaqq451QqWEyWPBI,6197
19
- cornflow/cli/service.py,sha256=iAV-RvKpCoUg2Nr4BqatTD6he_2THWuhQdJrUoFlqaI,13303
19
+ cornflow/cli/service.py,sha256=69n4RT0-AApzz0jslbPttZVmG_uxwhLm1gAqQvRH3ec,13340
20
20
  cornflow/cli/users.py,sha256=VBUqOrS80qdp9E4XLn4ihocHVNWVhWSt19tp0o8mZII,2324
21
21
  cornflow/cli/utils.py,sha256=p54xJEnYWda6rqSQDoZU2qZrFu9kTs4FoF0y3pJLQvI,1377
22
22
  cornflow/cli/views.py,sha256=BjJRIQ5T3C8zl3-pVVkToVjQ6xdAGwnuT9LVEnn_jqM,746
@@ -30,15 +30,15 @@ cornflow/cli/tools/tools.py,sha256=Qm0X-wHN12vXYJNRHONGjqDZewwXyXy4R_j4UT_XMLs,9
30
30
  cornflow/commands/__init__.py,sha256=_7mi2Sd8bnaSujU-L78z8Zrswz68NJ2xoocYpsEYmPM,544
31
31
  cornflow/commands/access.py,sha256=4as74PKwcq5VoPnT9Hh2uUEDQLOQo6jAelwmeBIPW3c,1013
32
32
  cornflow/commands/actions.py,sha256=4AwgAmyI6VeaugkISvTlNGrIzMMU_-ZB3MhwDD_CIEA,1544
33
- cornflow/commands/auxiliar.py,sha256=r73V9Mo43ZvkgTuiz8Hxbx-O9Sfc45vOagdIrmSABjw,3683
33
+ cornflow/commands/auxiliar.py,sha256=rM_PENCXDKbPSpE5TCvsDFE3riwDwwQiFg019Qms7OU,3764
34
34
  cornflow/commands/cleanup.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
35
  cornflow/commands/dag.py,sha256=AtagFGnB_ucfO0qUawDgd4iRoBCVc-RiOs08DqXSwXM,3786
36
- cornflow/commands/permissions.py,sha256=5TgKvDotRYciVfN_XKZWFOV5e1rWiztaauVOZdFM-3A,10507
36
+ cornflow/commands/permissions.py,sha256=x50jd2HbzVUDZ17bZKkDeDlLeWmFXXqsY9ocFnqDOf0,10544
37
37
  cornflow/commands/roles.py,sha256=MHMn-nNSgvRww5RNblxXvdG9BBCtMvPtc-KD5EMwseg,1605
38
38
  cornflow/commands/schemas.py,sha256=40dZSJ2nEqBa7Crb6DbFmnclT5e8ljAIjscOgHr9lhk,1970
39
39
  cornflow/commands/users.py,sha256=2YTbNYY5kZL6ujxGP4fyYgqtv5uuVGdkLR31n7OFFaE,2477
40
- cornflow/commands/views.py,sha256=yg3vuKoJwusBWm0v9PCGYr1xAbL-dWAi4aDV0aGpMnA,6978
41
- cornflow/endpoints/__init__.py,sha256=ZlwhY8MiynQ0BdATkrsikGM2Kqo4DPxkVTc3faNfzRY,7492
40
+ cornflow/commands/views.py,sha256=hNm1duq3EdKBB3qOP5GU772EIqPR8bGfScgurhoPp4E,7074
41
+ cornflow/endpoints/__init__.py,sha256=NNKkdqA7NnvqsqiFhEY1ZQ0NRIoAliHJ-Hdf9SottNU,8433
42
42
  cornflow/endpoints/action.py,sha256=ksHK3F919cjkONLcFV2tUIbG-eZw5XbYkqVjYx9iq5I,1359
43
43
  cornflow/endpoints/alarms.py,sha256=M-fpRAm5ZgYyZXvcgS0NHkeMGnvcbfQh2O5qQJUaeoM,4372
44
44
  cornflow/endpoints/apiview.py,sha256=cpxZFkWy6yrRHiAq2tseyVAK1r8uvjnFuOgJjGT0rKI,1370
@@ -53,10 +53,10 @@ cornflow/endpoints/licenses.py,sha256=82hHWGYvVIiyw9mlwGtMwJMDJ-ShHOi9rvuM6KvfE4
53
53
  cornflow/endpoints/login.py,sha256=ini7w9kFNoVw981UOvlFIaf45rsnpx6nKFnKrM2uDpo,10698
54
54
  cornflow/endpoints/main_alarms.py,sha256=GUB-UdnvEFi7n6FGFKO9VtZeZb4Ox3NvBMhB7rdqNyI,2006
55
55
  cornflow/endpoints/meta_resource.py,sha256=hqzOgmYFAzGsXVo0BIb5rjS71uNKVMuyLi-QkAQnb34,8639
56
- cornflow/endpoints/permission.py,sha256=FpEBIucfUl89UaJ80SC0VR6pFAdqdSsS43SdNkcXWtI,3751
56
+ cornflow/endpoints/permission.py,sha256=qG-NTA1AdkGuGNb3Aqq2PkPT84nj6W1ym9gLQixVOnA,3751
57
57
  cornflow/endpoints/roles.py,sha256=54ra4MQ9JmrHDsiGczDAVqHgAT4zwhdTA1dLBOy66v8,6105
58
58
  cornflow/endpoints/schemas.py,sha256=lHyvSpPj0x7zVYDlEeRrz_Qqyp6WNimibs5gK4BHpKI,2933
59
- cornflow/endpoints/signup.py,sha256=4Xle2aTd6fiblb2pFcTaBP3ykXSuXsrc7qD0JjpqeZY,3513
59
+ cornflow/endpoints/signup.py,sha256=12bziq9qq9BMcrM0XmBrAurucbsHnBe5DbDaHZyw91U,3723
60
60
  cornflow/endpoints/tables.py,sha256=KI4sgkBHdiHbOnIRR_yoZ859ea3rnp_6ji_XvQbwsZ8,3071
61
61
  cornflow/endpoints/token.py,sha256=UEnsNNQAd6lJi2aF972d8uUWNJHT4ZcRr0eYpN458R4,1193
62
62
  cornflow/endpoints/user.py,sha256=FudDlINtgqJqqKaDcxCWEvYuL6QL_bZbK5YjGI0OLO0,11775
@@ -120,7 +120,7 @@ cornflow/schemas/user_role.py,sha256=e5y6RgdZZtLqD-h2B3sa5WokI5-pT78tWw85IG34I74
120
120
  cornflow/schemas/view.py,sha256=ctq9Y1TmjrWdyOqgDYeEx7qbbuNLKfSiNOlFTlXmpaw,429
121
121
  cornflow/shared/__init__.py,sha256=1ahcBwWOsSjGI4FEm77JBQjitBdBszOncKcEMjzwGYE,29
122
122
  cornflow/shared/compress.py,sha256=pohQaGs1xbH8CN6URIH6BAHA--pFq7Hmjz8oI3c3B5c,1347
123
- cornflow/shared/const.py,sha256=BzQpcx0jOZUqegVri2OszfvCzGj5aEVG_a6aa4dCTZc,3556
123
+ cornflow/shared/const.py,sha256=IjkQaUMTQSOwsbSX773DNLpFbjZLyfmGRZ1NMeU7UEQ,3863
124
124
  cornflow/shared/email.py,sha256=QNDDMv86LZObkevSCyUbLQeR2UD3zWScPIr82NDzYHQ,3437
125
125
  cornflow/shared/exceptions.py,sha256=E82488IiwTXCv8iwrnGvkTonhJcwbeE5ARO4Zsmhl2c,6966
126
126
  cornflow/shared/licenses.py,sha256=Lc71Jw2NxVTFWtoXdQ9wJX_o3BDfYg1xVoehDXvnCkQ,1328
@@ -131,7 +131,7 @@ cornflow/shared/utils_tables.py,sha256=JnyaQ-5d1alj230nir-6elC2i0QX-u-_32SAs2eDt
131
131
  cornflow/shared/validators.py,sha256=Iy2jaGzS9ojqlTE_-vKH4oQKD89GN5C0EPEZMg6UZgc,4761
132
132
  cornflow/shared/authentication/__init__.py,sha256=cJIChk5X6hbA_16usEvfHr8g4JDFI6WKo0GPVOOiYHA,137
133
133
  cornflow/shared/authentication/auth.py,sha256=SiXMCHwyAXHL7Krd5Ubht16NPNXqSvf-85xyiEB2DK4,18247
134
- cornflow/shared/authentication/decorators.py,sha256=_QpwOU1kYzpaK85Dl0Btdj5hG8Ps47PFgySp_gqhlgk,1276
134
+ cornflow/shared/authentication/decorators.py,sha256=KevnUCkfRFQXD42mOnydUUgY2X0vao8aHRLUTEIGfq4,2634
135
135
  cornflow/shared/authentication/ldap.py,sha256=QfdC2X_ZMcIJabKC5pYWDGMhS5pIOJJvdZXuuiruq-M,4853
136
136
  cornflow/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
137
137
  cornflow/tests/const.py,sha256=2TT-i8mB31rbo0U4GKWzUbPJya0hEURME_N3yzGhGM8,2635
@@ -145,17 +145,18 @@ cornflow/tests/ldap/test_ldap_authentication.py,sha256=6Gu1WkF7MQmcV_10IJkpo2qEl
145
145
  cornflow/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
146
146
  cornflow/tests/unit/test_actions.py,sha256=plgnzBJnDiZRdVxt1sNJNL2KbW5ijPZ6MHdIUWO0_As,3167
147
147
  cornflow/tests/unit/test_alarms.py,sha256=P1QsbT09HuRWCDOALsvPFux9UJg2PgWmGX6pb2hmTXA,4529
148
- cornflow/tests/unit/test_apiview.py,sha256=03M1GsQRVK7zqmslhOJXr4lLDLY2gMAgg86nk1lyaKU,7620
148
+ cornflow/tests/unit/test_apiview.py,sha256=F40cO4WSm4vKz7PJww2eK1T601lhmf7iQbewx95rwuc,7885
149
149
  cornflow/tests/unit/test_application.py,sha256=ZVmTQDUOkPRxHqt6mWU9G_lQ3jJNMJR0cx7IkLMFGrU,1715
150
150
  cornflow/tests/unit/test_cases.py,sha256=Ez9dxlZL-SUf9DW9b_A_qPowHqUZ-TA73DMOzeBeLIU,37718
151
- cornflow/tests/unit/test_cli.py,sha256=E2w-Lzgx_k__0mYwlbg2z80_z9nwPZKI0CbgyGmpQRY,18775
152
- cornflow/tests/unit/test_commands.py,sha256=kvO8Vn60rj3WBG2oXw7NpDSEYoGLNe806txbJPhtNJo,14722
151
+ cornflow/tests/unit/test_cli.py,sha256=18tjj2sNmRsgx6OK8Z-e96iLgsUz2hbJp6UZJfF-PAE,18887
152
+ cornflow/tests/unit/test_commands.py,sha256=bcs5DNzEOTjTHKM0ecdsx_u4Uc54ElZ6IX9eqEv_sEA,14762
153
153
  cornflow/tests/unit/test_dags.py,sha256=XsOi5bBJQdQz3DmYAVJf1myoAsRyBBdmku-xBr0Bku0,13386
154
154
  cornflow/tests/unit/test_data_checks.py,sha256=6s50d1iuRTUcAYn14oEcRS39ZZ6E9ussU4YpkpYhtC4,8612
155
155
  cornflow/tests/unit/test_example_data.py,sha256=D-Tgnqw7NZlnBXaDcUU0reNhAca5JlJP2Sdn3KdS4Sw,4127
156
156
  cornflow/tests/unit/test_executions.py,sha256=fC9kMRqU3qMGQ9eH6WXlOkU8CKJe6l-DlAKln4ImCSU,18192
157
157
  cornflow/tests/unit/test_external_role_creation.py,sha256=1YZ2Scj6HeoFxFO-KZr1M7pjyWqzjA77gAICSpiF-2g,31310
158
158
  cornflow/tests/unit/test_generate_from_schema.py,sha256=L1EdnASbDJ8SjrX1V4WnUKKwV0sRTwVnNYnxSpyeSeQ,15376
159
+ cornflow/tests/unit/test_get_resources.py,sha256=f4SSYjA6W48vQJw9MMqM-srvskzHFTnxnd5-IuUamGo,4231
159
160
  cornflow/tests/unit/test_health.py,sha256=xT0OcnpxbxE0QJlyg4r1qCYDn-hwuk0Ynw5JA4cJtsY,1205
160
161
  cornflow/tests/unit/test_instances.py,sha256=Mf9jijQOcDE3ylPfMTnVRocRegcugEdCnoMCqSmKKqQ,11083
161
162
  cornflow/tests/unit/test_instances_file.py,sha256=sbodxnuoT7n7jdELz-wpVXWt76E5UzUrQYyVpvnfbco,1986
@@ -166,13 +167,13 @@ cornflow/tests/unit/test_permissions.py,sha256=Jd-4PtqBcWGrSZAuTlJD82qE65D9ZtGWB
166
167
  cornflow/tests/unit/test_roles.py,sha256=1-EON_JsPAM3sVL8AUfgJYLLfb0s1FXL8LJS4nilF-g,18166
167
168
  cornflow/tests/unit/test_schema_from_models.py,sha256=7IfycOGO3U06baX8I-OPJfu-3ZAn5cv8RCdj9wvalMk,4421
168
169
  cornflow/tests/unit/test_schemas.py,sha256=6SpkeYsS3oWntUZEF3GldLqmNa-hpxg-WrKJVTgc-B4,7468
169
- cornflow/tests/unit/test_sign_up.py,sha256=-i6VO9z1FwqRHFvaSrpWAzOZx6qa8mHUEmmsjuMXjn8,3481
170
+ cornflow/tests/unit/test_sign_up.py,sha256=6LSlME3tB8t9jtvJD-Lut3ia9UVHzqqET_3EQMcZQ7s,9486
170
171
  cornflow/tests/unit/test_tables.py,sha256=SW_K8LRLwR1nB0uH8CPQCjeN8Gei-TasAgkOinnAjLk,8969
171
172
  cornflow/tests/unit/test_token.py,sha256=PZ11b46UCQpCESsRiAPhpgWkGAsAwKCVNxVQai_kxXM,4199
172
173
  cornflow/tests/unit/test_users.py,sha256=N5tcF5nSncD0F_ZlBxGuS87p6kNS4hUzRLr3_AcnK-o,22802
173
174
  cornflow/tests/unit/tools.py,sha256=ag3sWv2WLi498R1GL5AOUnXqSsszD3UugzLZLC5NqAw,585
174
- cornflow-1.2.3a5.dist-info/METADATA,sha256=1e_i9SiQmCi44zJijIdeZFVNtJ5iF0rW_0qS2WFS_XY,9532
175
- cornflow-1.2.3a5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
176
- cornflow-1.2.3a5.dist-info/entry_points.txt,sha256=q9cPKAFBsmHkERCqQ2JcOTM-tVBLHTl-DGxwCXowAWM,46
177
- cornflow-1.2.3a5.dist-info/top_level.txt,sha256=Qj9kLFJW1PLb-ZV2s_aCkQ-Wi5W6KC6fFR-LTBrx-rU,24
178
- cornflow-1.2.3a5.dist-info/RECORD,,
175
+ cornflow-1.2.4.dist-info/METADATA,sha256=42cwpvGnN2xjCEOwxWZsWGMcora3CNzQtB2haM-jaJM,9527
176
+ cornflow-1.2.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
177
+ cornflow-1.2.4.dist-info/entry_points.txt,sha256=q9cPKAFBsmHkERCqQ2JcOTM-tVBLHTl-DGxwCXowAWM,46
178
+ cornflow-1.2.4.dist-info/top_level.txt,sha256=Qj9kLFJW1PLb-ZV2s_aCkQ-Wi5W6KC6fFR-LTBrx-rU,24
179
+ cornflow-1.2.4.dist-info/RECORD,,