cornflow 1.2.3a4__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 +24 -8
- cornflow/cli/service.py +2 -1
- cornflow/commands/auxiliar.py +12 -3
- cornflow/commands/permissions.py +32 -11
- cornflow/commands/roles.py +1 -1
- cornflow/commands/views.py +3 -0
- cornflow/config.py +4 -4
- cornflow/endpoints/__init__.py +27 -0
- cornflow/endpoints/permission.py +1 -2
- cornflow/endpoints/signup.py +17 -11
- cornflow/shared/authentication/decorators.py +33 -2
- cornflow/shared/const.py +15 -1
- cornflow/tests/unit/test_apiview.py +7 -1
- cornflow/tests/unit/test_cli.py +6 -3
- cornflow/tests/unit/test_commands.py +2 -1
- cornflow/tests/unit/test_external_role_creation.py +246 -0
- cornflow/tests/unit/test_get_resources.py +103 -0
- cornflow/tests/unit/test_sign_up.py +181 -6
- {cornflow-1.2.3a4.dist-info → cornflow-1.2.4.dist-info}/METADATA +3 -3
- {cornflow-1.2.3a4.dist-info → cornflow-1.2.4.dist-info}/RECORD +23 -22
- {cornflow-1.2.3a4.dist-info → cornflow-1.2.4.dist-info}/WHEEL +0 -0
- {cornflow-1.2.3a4.dist-info → cornflow-1.2.4.dist-info}/entry_points.txt +0 -0
- {cornflow-1.2.3a4.dist-info → cornflow-1.2.4.dist-info}/top_level.txt +0 -0
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
|
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
|
99
|
-
api.add_resource(
|
100
|
-
|
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(
|
114
|
+
api.add_resource(
|
115
|
+
LoginEndpoint, CONDITIONAL_ENDPOINTS["login"], endpoint="login"
|
116
|
+
)
|
103
117
|
elif auth_type == AUTH_OID:
|
104
|
-
api.add_resource(
|
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",
|
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)
|
cornflow/commands/auxiliar.py
CHANGED
@@ -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
|
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,
|
@@ -14,12 +14,16 @@ from cornflow.shared.const import ROLES_MAP
|
|
14
14
|
|
15
15
|
def get_all_external(external_app):
|
16
16
|
"""
|
17
|
-
Get all resources and
|
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
|
26
|
+
custom_roles_actions = {}
|
23
27
|
if current_app.config["ALARMS_ENDPOINTS"]:
|
24
28
|
resources_to_register = resources + alarms_resources
|
25
29
|
else:
|
@@ -33,13 +37,18 @@ def get_all_external(external_app):
|
|
33
37
|
except AttributeError:
|
34
38
|
extra_permissions = EXTRA_PERMISSION_ASSIGNATION
|
35
39
|
|
40
|
+
try:
|
41
|
+
custom_roles_actions = external_module.shared.const.CUSTOM_ROLES_ACTIONS
|
42
|
+
except AttributeError:
|
43
|
+
custom_roles_actions = {}
|
44
|
+
|
36
45
|
if current_app.config["ALARMS_ENDPOINTS"]:
|
37
46
|
resources_to_register = (
|
38
47
|
external_module.endpoints.resources + resources + alarms_resources
|
39
48
|
)
|
40
49
|
else:
|
41
50
|
resources_to_register = external_module.endpoints.resources + resources
|
42
|
-
return resources_to_register, extra_permissions
|
51
|
+
return resources_to_register, extra_permissions, custom_roles_actions
|
43
52
|
|
44
53
|
|
45
54
|
def get_all_resources(resources_to_register):
|
cornflow/commands/permissions.py
CHANGED
@@ -19,15 +19,22 @@ def register_base_permissions_command(external_app: str = None, verbose: bool =
|
|
19
19
|
external_app: If provided, it will register the permissions for the external app.
|
20
20
|
verbose: If True, it will print the permissions that are being registered.
|
21
21
|
"""
|
22
|
-
# Get all resources and
|
23
|
-
resources_to_register, extra_permissions = get_all_external(
|
22
|
+
# Get all resources, extra permissions, and custom roles actions
|
23
|
+
resources_to_register, extra_permissions, custom_roles_actions = get_all_external(
|
24
|
+
external_app
|
25
|
+
)
|
26
|
+
|
24
27
|
# Get all views in the database
|
25
28
|
views_in_db = {view.name: view.id for view in ViewModel.get_all_objects()}
|
26
29
|
permissions_in_db, permissions_in_db_keys = get_db_permissions()
|
30
|
+
|
27
31
|
# Get all resources and roles with access
|
28
32
|
resources_roles_with_access = get_all_resources(resources_to_register)
|
33
|
+
|
29
34
|
# Get the new roles and base permissions assignation
|
30
|
-
base_permissions_assignation = get_base_permissions(
|
35
|
+
base_permissions_assignation = get_base_permissions(
|
36
|
+
resources_roles_with_access, custom_roles_actions
|
37
|
+
)
|
31
38
|
# Get the permissions to register and delete
|
32
39
|
permissions_tuples = get_permissions_in_code_as_tuples(
|
33
40
|
resources_to_register,
|
@@ -46,12 +53,14 @@ def register_base_permissions_command(external_app: str = None, verbose: bool =
|
|
46
53
|
save_and_delete_permissions(permissions_to_register, permissions_to_delete)
|
47
54
|
|
48
55
|
if len(permissions_to_register) > 0:
|
49
|
-
current_app.logger.info(
|
56
|
+
current_app.logger.info(
|
57
|
+
f"Permissions registered: {len(permissions_to_register)}"
|
58
|
+
)
|
50
59
|
else:
|
51
60
|
current_app.logger.info("No new permissions to register")
|
52
61
|
|
53
62
|
if len(permissions_to_delete) > 0:
|
54
|
-
current_app.logger.info(f"Permissions deleted: {permissions_to_delete}")
|
63
|
+
current_app.logger.info(f"Permissions deleted: {len(permissions_to_delete)}")
|
55
64
|
else:
|
56
65
|
current_app.logger.info("No permissions to delete")
|
57
66
|
|
@@ -165,11 +174,11 @@ def get_permissions_in_code_as_tuples(
|
|
165
174
|
return permissions_tuples
|
166
175
|
|
167
176
|
|
168
|
-
def get_base_permissions(resources_roles_with_access):
|
177
|
+
def get_base_permissions(resources_roles_with_access, custom_roles_actions):
|
169
178
|
"""
|
170
179
|
Get the new roles and base permissions assignation.
|
171
|
-
new_roles_to_add: List of new roles to add.
|
172
180
|
resources_roles_with_access: Dictionary of resources and roles with access.
|
181
|
+
custom_roles_actions: Dictionary mapping custom roles to their allowed actions.
|
173
182
|
"""
|
174
183
|
# Get all custom roles (both new and existing) that appear in ROLES_WITH_ACCESS
|
175
184
|
all_custom_roles_in_access = set(
|
@@ -181,12 +190,24 @@ def get_base_permissions(resources_roles_with_access):
|
|
181
190
|
]
|
182
191
|
)
|
183
192
|
|
193
|
+
# Validate that all custom roles are defined in custom_roles_actions
|
194
|
+
undefined_roles = all_custom_roles_in_access - set(custom_roles_actions.keys())
|
195
|
+
if undefined_roles:
|
196
|
+
raise ValueError(
|
197
|
+
f"The following custom roles are used in code but not defined in CUSTOM_ROLES_ACTIONS: {undefined_roles}. "
|
198
|
+
f"Please define their allowed actions in the CUSTOM_ROLES_ACTIONS dictionary in shared/const.py."
|
199
|
+
)
|
200
|
+
|
184
201
|
# Create extended permission assignation including all custom roles
|
185
|
-
# For custom roles
|
186
|
-
|
187
|
-
(custom_role,
|
202
|
+
# For custom roles, use the actions defined in custom_roles_actions
|
203
|
+
custom_permissions = [
|
204
|
+
(custom_role, action)
|
205
|
+
for custom_role in all_custom_roles_in_access
|
206
|
+
for action in custom_roles_actions[custom_role]
|
188
207
|
]
|
189
208
|
|
209
|
+
base_permissions_assignation = BASE_PERMISSION_ASSIGNATION + custom_permissions
|
210
|
+
|
190
211
|
return base_permissions_assignation
|
191
212
|
|
192
213
|
|
@@ -277,7 +298,7 @@ def register_dag_permissions_command(
|
|
277
298
|
|
278
299
|
if verbose:
|
279
300
|
if len(permissions) > 1:
|
280
|
-
current_app.logger.info(f"DAG permissions registered: {permissions}")
|
301
|
+
current_app.logger.info(f"DAG permissions registered: {len(permissions)}")
|
281
302
|
else:
|
282
303
|
current_app.logger.info("No new DAG permissions")
|
283
304
|
|
cornflow/commands/roles.py
CHANGED
@@ -10,7 +10,7 @@ def register_roles_command(external_app: str = None, verbose: bool = True):
|
|
10
10
|
get_new_roles_to_add,
|
11
11
|
)
|
12
12
|
|
13
|
-
resources_to_register, extra_permissions = get_all_external(external_app)
|
13
|
+
resources_to_register, extra_permissions, _ = get_all_external(external_app)
|
14
14
|
resources_roles_with_access = get_all_resources(resources_to_register)
|
15
15
|
new_roles_to_add = get_new_roles_to_add(
|
16
16
|
extra_permissions, resources_roles_with_access
|
cornflow/commands/views.py
CHANGED
@@ -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",
|
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
|
}
|
cornflow/endpoints/__init__.py
CHANGED
@@ -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
|
cornflow/endpoints/permission.py
CHANGED
cornflow/endpoints/signup.py
CHANGED
@@ -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),
|
98
|
-
|
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(
|
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
|
-
|
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,
|
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()
|
cornflow/tests/unit/test_cli.py
CHANGED
@@ -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
|
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),
|
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),
|
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
|
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"])
|