cornflow 1.2.3a5__py3-none-any.whl → 1.3.0rc1__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 +93 -43
- cornflow/commands/auxiliar.py +4 -1
- cornflow/commands/dag.py +7 -7
- cornflow/commands/permissions.py +13 -7
- cornflow/commands/views.py +3 -0
- cornflow/config.py +26 -6
- cornflow/endpoints/__init__.py +27 -0
- cornflow/endpoints/case.py +37 -21
- cornflow/endpoints/dag.py +5 -5
- cornflow/endpoints/data_check.py +8 -7
- cornflow/endpoints/example_data.py +4 -2
- cornflow/endpoints/execution.py +215 -127
- cornflow/endpoints/health.py +30 -11
- cornflow/endpoints/instance.py +3 -3
- cornflow/endpoints/login.py +9 -2
- cornflow/endpoints/permission.py +1 -2
- cornflow/endpoints/schemas.py +3 -3
- cornflow/endpoints/signup.py +17 -11
- cornflow/migrations/versions/999b98e24225.py +34 -0
- cornflow/migrations/versions/cef1df240b27_.py +34 -0
- cornflow/models/__init__.py +2 -1
- cornflow/models/dag.py +8 -9
- cornflow/models/dag_permissions.py +3 -3
- cornflow/models/execution.py +2 -3
- cornflow/models/permissions.py +1 -0
- cornflow/models/user.py +1 -1
- cornflow/schemas/execution.py +14 -1
- cornflow/schemas/health.py +1 -1
- cornflow/shared/authentication/auth.py +14 -1
- cornflow/shared/authentication/decorators.py +32 -2
- cornflow/shared/const.py +58 -1
- cornflow/shared/exceptions.py +2 -1
- cornflow/tests/base_test_execution.py +798 -0
- cornflow/tests/const.py +1 -0
- cornflow/tests/integration/test_commands.py +2 -2
- cornflow/tests/integration/test_cornflowclient.py +2 -1
- cornflow/tests/unit/test_apiview.py +7 -1
- cornflow/tests/unit/test_cases.py +1 -1
- cornflow/tests/unit/test_cli.py +6 -3
- cornflow/tests/unit/test_commands.py +7 -6
- cornflow/tests/unit/test_dags.py +3 -3
- cornflow/tests/unit/test_example_data.py +1 -1
- cornflow/tests/unit/test_executions.py +115 -535
- cornflow/tests/unit/test_get_resources.py +103 -0
- cornflow/tests/unit/test_health.py +84 -3
- cornflow/tests/unit/test_main_alarms.py +1 -1
- cornflow/tests/unit/test_roles.py +2 -1
- cornflow/tests/unit/test_schema_from_models.py +1 -1
- cornflow/tests/unit/test_schemas.py +1 -1
- cornflow/tests/unit/test_sign_up.py +181 -6
- cornflow/tests/unit/tools.py +93 -10
- {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/METADATA +3 -3
- {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/RECORD +57 -53
- {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/WHEEL +0 -0
- {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/entry_points.txt +0 -0
- {cornflow-1.2.3a5.dist-info → cornflow-1.3.0rc1.dist-info}/top_level.txt +0 -0
cornflow/endpoints/instance.py
CHANGED
@@ -16,7 +16,7 @@ from werkzeug.utils import secure_filename
|
|
16
16
|
|
17
17
|
# Import from internal modules
|
18
18
|
from cornflow.endpoints.meta_resource import BaseMetaResource
|
19
|
-
from cornflow.models import InstanceModel,
|
19
|
+
from cornflow.models import InstanceModel, DeployedWorkflow
|
20
20
|
from cornflow.schemas.instance import (
|
21
21
|
InstanceSchema,
|
22
22
|
InstanceEndpointResponse,
|
@@ -91,7 +91,7 @@ class InstanceEndpoint(BaseMetaResource):
|
|
91
91
|
# We validate the instance data
|
92
92
|
config = current_app.config
|
93
93
|
|
94
|
-
instance_schema =
|
94
|
+
instance_schema = DeployedWorkflow.get_one_schema(
|
95
95
|
config, data_schema, INSTANCE_SCHEMA
|
96
96
|
)
|
97
97
|
instance_errors = json_schema_validate_as_string(
|
@@ -166,7 +166,7 @@ class InstanceDetailsEndpoint(InstanceDetailsEndpointBase):
|
|
166
166
|
|
167
167
|
config = current_app.config
|
168
168
|
|
169
|
-
instance_schema =
|
169
|
+
instance_schema = DeployedWorkflow.get_one_schema(
|
170
170
|
config, schema, INSTANCE_SCHEMA
|
171
171
|
)
|
172
172
|
instance_errors = json_schema_validate_as_string(
|
cornflow/endpoints/login.py
CHANGED
@@ -188,8 +188,15 @@ class LoginBaseEndpoint(BaseMetaResource):
|
|
188
188
|
)
|
189
189
|
|
190
190
|
email = decoded_token.get("email", f"{username}@cornflow.org")
|
191
|
-
|
192
|
-
|
191
|
+
|
192
|
+
email_names = email.split("@")[0]
|
193
|
+
if "." in email_names:
|
194
|
+
default_first_name, default_last_name = email_names.split(".")[0:2]
|
195
|
+
else:
|
196
|
+
default_first_name, default_last_name = email_names, ""
|
197
|
+
|
198
|
+
first_name = decoded_token.get("given_name", default_first_name)
|
199
|
+
last_name = decoded_token.get("family_name", default_last_name)
|
193
200
|
|
194
201
|
data = {
|
195
202
|
"username": username,
|
cornflow/endpoints/permission.py
CHANGED
cornflow/endpoints/schemas.py
CHANGED
@@ -8,7 +8,7 @@ from flask_apispec import marshal_with, doc
|
|
8
8
|
|
9
9
|
# Import from internal modules
|
10
10
|
from cornflow.endpoints.meta_resource import BaseMetaResource
|
11
|
-
from cornflow.models import PermissionsDAG,
|
11
|
+
from cornflow.models import PermissionsDAG, DeployedWorkflow
|
12
12
|
from cornflow.schemas.schemas import SchemaOneApp, SchemaListApp
|
13
13
|
from cornflow.shared.authentication import Auth, authenticate
|
14
14
|
from cornflow.shared.const import ALL_DEFAULT_ROLES
|
@@ -63,7 +63,7 @@ class SchemaDetailsEndpoint(BaseMetaResource):
|
|
63
63
|
)
|
64
64
|
|
65
65
|
if permission:
|
66
|
-
deployed_dag =
|
66
|
+
deployed_dag = DeployedWorkflow.get_one_object(dag_name)
|
67
67
|
current_app.logger.info("User gets schema {}".format(dag_name))
|
68
68
|
return {
|
69
69
|
"instance": deployed_dag.instance_schema,
|
@@ -71,7 +71,7 @@ class SchemaDetailsEndpoint(BaseMetaResource):
|
|
71
71
|
"instance_checks": deployed_dag.instance_checks_schema,
|
72
72
|
"solution_checks": deployed_dag.solution_checks_schema,
|
73
73
|
"config": deployed_dag.config_schema,
|
74
|
-
"name": dag_name
|
74
|
+
"name": dag_name,
|
75
75
|
}, 200
|
76
76
|
else:
|
77
77
|
err = "User does not have permission to access this dag"
|
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
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"""
|
2
|
+
Rename dag_run_id column to run_id in executions table
|
3
|
+
|
4
|
+
Revision ID: 999b98e24225
|
5
|
+
Revises: 991b98e24225
|
6
|
+
Create Date: 2024-04-08 12:00:00.000000
|
7
|
+
|
8
|
+
"""
|
9
|
+
|
10
|
+
from alembic import op
|
11
|
+
import sqlalchemy as sa
|
12
|
+
|
13
|
+
|
14
|
+
# revision identifiers, used by Alembic.
|
15
|
+
revision = "abc123456789"
|
16
|
+
down_revision = "991b98e24225"
|
17
|
+
branch_labels = None
|
18
|
+
depends_on = None
|
19
|
+
|
20
|
+
|
21
|
+
def upgrade():
|
22
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
23
|
+
with op.batch_alter_table("executions", schema=None) as batch_op:
|
24
|
+
batch_op.alter_column("dag_run_id", new_column_name="run_id")
|
25
|
+
|
26
|
+
# ### end Alembic commands ###
|
27
|
+
|
28
|
+
|
29
|
+
def downgrade():
|
30
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
31
|
+
with op.batch_alter_table("executions", schema=None) as batch_op:
|
32
|
+
batch_op.alter_column("run_id", new_column_name="dag_run_id")
|
33
|
+
|
34
|
+
# ### end Alembic commands ###
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"""empty message
|
2
|
+
|
3
|
+
Revision ID: cef1df240b27
|
4
|
+
Revises: abc123456789
|
5
|
+
Create Date: 2025-07-22 19:05:11.146449
|
6
|
+
|
7
|
+
"""
|
8
|
+
|
9
|
+
from alembic import op
|
10
|
+
import sqlalchemy as sa
|
11
|
+
|
12
|
+
|
13
|
+
# revision identifiers, used by Alembic.
|
14
|
+
revision = 'cef1df240b27'
|
15
|
+
down_revision = 'abc123456789'
|
16
|
+
branch_labels = None
|
17
|
+
depends_on = None
|
18
|
+
|
19
|
+
|
20
|
+
def upgrade():
|
21
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
22
|
+
|
23
|
+
op.rename_table("deployed_dags", "deployed_workflows")
|
24
|
+
|
25
|
+
# ### end Alembic commands ###
|
26
|
+
|
27
|
+
|
28
|
+
def downgrade():
|
29
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
30
|
+
|
31
|
+
# Rename table back from deployed_workflows to deployed_dags
|
32
|
+
op.rename_table("deployed_workflows", "deployed_dags")
|
33
|
+
|
34
|
+
# ### end Alembic commands ###
|
cornflow/models/__init__.py
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
"""
|
2
2
|
Initialization file for the models module
|
3
3
|
"""
|
4
|
+
|
4
5
|
from .action import ActionModel
|
5
6
|
from .alarms import AlarmsModel
|
6
7
|
from .case import CaseModel
|
7
|
-
from .dag import
|
8
|
+
from .dag import DeployedWorkflow
|
8
9
|
from .dag_permissions import PermissionsDAG
|
9
10
|
from .execution import ExecutionModel
|
10
11
|
from .instance import InstanceModel
|
cornflow/models/dag.py
CHANGED
@@ -1,13 +1,12 @@
|
|
1
|
-
"""
|
1
|
+
""" """
|
2
2
|
|
3
|
-
"""
|
4
3
|
# Import from libraries
|
5
4
|
from cornflow_client.airflow.api import Airflow
|
6
5
|
from cornflow_client.constants import (
|
7
6
|
INSTANCE_SCHEMA,
|
8
7
|
SOLUTION_SCHEMA,
|
9
8
|
INSTANCE_CHECKS_SCHEMA,
|
10
|
-
SOLUTION_CHECKS_SCHEMA
|
9
|
+
SOLUTION_CHECKS_SCHEMA,
|
11
10
|
)
|
12
11
|
from sqlalchemy.dialects.postgresql import TEXT, JSON
|
13
12
|
|
@@ -17,12 +16,12 @@ from cornflow.shared import db
|
|
17
16
|
from cornflow.shared.exceptions import ObjectDoesNotExist
|
18
17
|
|
19
18
|
|
20
|
-
class
|
19
|
+
class DeployedWorkflow(TraceAttributesModel):
|
21
20
|
"""
|
22
21
|
This model contains the registry of the DAGs that are deployed on the corresponding Airflow server
|
23
22
|
"""
|
24
23
|
|
25
|
-
__tablename__ = "
|
24
|
+
__tablename__ = "deployed_workflows"
|
26
25
|
id = db.Column(db.String(128), primary_key=True)
|
27
26
|
description = db.Column(TEXT, nullable=True)
|
28
27
|
instance_schema = db.Column(JSON, nullable=True)
|
@@ -34,8 +33,8 @@ class DeployedDAG(TraceAttributesModel):
|
|
34
33
|
dag_permissions = db.relationship(
|
35
34
|
"PermissionsDAG",
|
36
35
|
cascade="all,delete",
|
37
|
-
backref="
|
38
|
-
primaryjoin="and_(
|
36
|
+
backref="deployed_workflows",
|
37
|
+
primaryjoin="and_(DeployedWorkflow.id==PermissionsDAG.dag_id)",
|
39
38
|
)
|
40
39
|
|
41
40
|
def __init__(self, data):
|
@@ -53,14 +52,14 @@ class DeployedDAG(TraceAttributesModel):
|
|
53
52
|
|
54
53
|
@staticmethod
|
55
54
|
def get_one_schema(config, dag_name, schema=INSTANCE_SCHEMA):
|
56
|
-
item =
|
55
|
+
item = DeployedWorkflow.get_one_object(dag_name)
|
57
56
|
|
58
57
|
if item is None:
|
59
58
|
err = f"The DAG {dag_name} does not exist in the database."
|
60
59
|
raise ObjectDoesNotExist(
|
61
60
|
err,
|
62
61
|
log_txt=f"Error while user tries to register data for DAG {dag_name} "
|
63
|
-
|
62
|
+
f"from instance and execution. " + err,
|
64
63
|
)
|
65
64
|
|
66
65
|
if schema == INSTANCE_SCHEMA:
|
@@ -1,4 +1,4 @@
|
|
1
|
-
from cornflow.models.dag import
|
1
|
+
from cornflow.models.dag import DeployedWorkflow
|
2
2
|
from cornflow.models.meta_models import TraceAttributesModel
|
3
3
|
from cornflow.shared import db
|
4
4
|
|
@@ -10,7 +10,7 @@ class PermissionsDAG(TraceAttributesModel):
|
|
10
10
|
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
|
11
11
|
|
12
12
|
dag_id = db.Column(
|
13
|
-
db.String(128), db.ForeignKey("
|
13
|
+
db.String(128), db.ForeignKey("deployed_workflows.id"), nullable=False
|
14
14
|
)
|
15
15
|
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
|
16
16
|
user = db.relationship("UserModel", viewonly=True)
|
@@ -29,7 +29,7 @@ class PermissionsDAG(TraceAttributesModel):
|
|
29
29
|
|
30
30
|
@staticmethod
|
31
31
|
def add_all_permissions_to_user(user_id):
|
32
|
-
dags =
|
32
|
+
dags = DeployedWorkflow.get_all_objects()
|
33
33
|
permissions = [
|
34
34
|
PermissionsDAG({"dag_id": dag.id, "user_id": user_id}) for dag in dags
|
35
35
|
]
|
cornflow/models/execution.py
CHANGED
@@ -55,7 +55,7 @@ class ExecutionModel(BaseDataModel):
|
|
55
55
|
db.String(256), db.ForeignKey("instances.id"), nullable=False
|
56
56
|
)
|
57
57
|
config = db.Column(JSON, nullable=False)
|
58
|
-
|
58
|
+
run_id = db.Column(db.String(256), nullable=True)
|
59
59
|
log_text = db.Column(TEXT, nullable=True)
|
60
60
|
log_json = db.Column(JSON, nullable=True)
|
61
61
|
state = db.Column(db.SmallInteger, default=DEFAULT_EXECUTION_CODE, nullable=False)
|
@@ -78,8 +78,7 @@ class ExecutionModel(BaseDataModel):
|
|
78
78
|
+ str(self.instance_id)
|
79
79
|
).encode()
|
80
80
|
).hexdigest()
|
81
|
-
|
82
|
-
self.dag_run_id = data.get("dag_run_id")
|
81
|
+
self.run_id = data.get("run_id")
|
83
82
|
self.state = data.get("state", DEFAULT_EXECUTION_CODE)
|
84
83
|
self.state_message = EXECUTION_STATE_MESSAGE_DICT[self.state]
|
85
84
|
self.config = data.get("config")
|
cornflow/models/permissions.py
CHANGED
cornflow/models/user.py
CHANGED
@@ -5,7 +5,7 @@ This file contains the UserModel
|
|
5
5
|
# Imports from external libraries
|
6
6
|
import random
|
7
7
|
import string
|
8
|
-
from datetime import datetime, timezone
|
8
|
+
from datetime import datetime, timezone
|
9
9
|
|
10
10
|
# Imports from internal modules
|
11
11
|
from cornflow.models.meta_models import TraceAttributesModel
|
cornflow/schemas/execution.py
CHANGED
@@ -43,7 +43,7 @@ class ExecutionSchema(Schema):
|
|
43
43
|
name = fields.Str()
|
44
44
|
description = fields.Str()
|
45
45
|
schema = fields.Str(required=False)
|
46
|
-
|
46
|
+
run_id = fields.Str(required=False, dump_only=True)
|
47
47
|
config = fields.Nested(ConfigSchema, required=True)
|
48
48
|
data = fields.Raw(dump_only=True)
|
49
49
|
checks = fields.Raw(required=False, allow_none=True)
|
@@ -78,6 +78,7 @@ class ExecutionEditRequest(Schema):
|
|
78
78
|
name = fields.Str()
|
79
79
|
description = fields.Str()
|
80
80
|
data = fields.Raw()
|
81
|
+
instance_id = fields.Str(required=False)
|
81
82
|
|
82
83
|
|
83
84
|
class ExecutionDagRequest(Schema):
|
@@ -119,7 +120,19 @@ class ExecutionDetailsEndpointWithIndicatorsResponse(ExecutionDetailsEndpointRes
|
|
119
120
|
return obj.user.username
|
120
121
|
return None
|
121
122
|
|
123
|
+
def get_first_name(self, obj):
|
124
|
+
if hasattr(obj, "user") and obj.user is not None:
|
125
|
+
return obj.user.first_name
|
126
|
+
return None
|
127
|
+
|
128
|
+
def get_last_name(self, obj):
|
129
|
+
if hasattr(obj, "user") and obj.user is not None:
|
130
|
+
return obj.user.last_name
|
131
|
+
return None
|
132
|
+
|
122
133
|
username = fields.Method("get_username")
|
134
|
+
first_name = fields.Method("get_first_name")
|
135
|
+
last_name = fields.Method("get_last_name")
|
123
136
|
|
124
137
|
|
125
138
|
class ExecutionDetailsWithIndicatorsAndLogResponse(
|
cornflow/schemas/health.py
CHANGED
@@ -25,6 +25,7 @@ from cornflow.shared.const import (
|
|
25
25
|
AUTH_OID,
|
26
26
|
PERMISSION_METHOD_MAP,
|
27
27
|
INTERNAL_TOKEN_ISSUER,
|
28
|
+
OID_PROVIDER_AZURE,
|
28
29
|
)
|
29
30
|
from cornflow.shared.exceptions import (
|
30
31
|
CommunicationError,
|
@@ -300,7 +301,19 @@ class Auth:
|
|
300
301
|
:rtype: dict
|
301
302
|
"""
|
302
303
|
# Fetch keys from provider
|
303
|
-
|
304
|
+
# For Azure AD, we need to use the discovery endpoint to get the jwks_uri
|
305
|
+
oid_provider = current_app.config["OID_PROVIDER"]
|
306
|
+
if oid_provider == OID_PROVIDER_AZURE:
|
307
|
+
tenant_id = provider_url.split("/")[
|
308
|
+
3
|
309
|
+
] # Extract tenant ID from provider URL
|
310
|
+
jwks_url = (
|
311
|
+
f"https://login.microsoftonline.com/{tenant_id}/discovery/v2.0/keys"
|
312
|
+
)
|
313
|
+
else:
|
314
|
+
# For other providers, use the standard well-known endpoint
|
315
|
+
jwks_url = f"{provider_url.rstrip('/')}/.well-known/jwks.json"
|
316
|
+
|
304
317
|
try:
|
305
318
|
response = requests.get(jwks_url)
|
306
319
|
response.raise_for_status()
|
@@ -1,17 +1,23 @@
|
|
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
|
7
9
|
|
8
10
|
|
9
|
-
def authenticate(
|
11
|
+
def authenticate(
|
12
|
+
auth_class: Auth, optional_auth: str = None, no_auth_list: list = None
|
13
|
+
):
|
10
14
|
"""
|
11
15
|
This is the decorator used for the authentication
|
12
16
|
|
13
17
|
:param auth_class: the class used for the authentication. It should be `BaseAuth` or a class that inherits from it
|
14
18
|
and that has a authenticate method.
|
19
|
+
:param optional_auth: the env variable that indicates if authentication should be deactivated
|
20
|
+
:param no_auth_list: the list of the values that indicates if authentication should be deactivated
|
15
21
|
:type auth_class: `BaseAuth`
|
16
22
|
:return: the wrapped function
|
17
23
|
"""
|
@@ -32,11 +38,35 @@ def authenticate(auth_class: Auth):
|
|
32
38
|
:param kwargs: the original kwargs sent to the decorated function
|
33
39
|
:return: the result of the call to the function
|
34
40
|
"""
|
41
|
+
if endpoint_qualified_for_no_auth(no_auth_list, optional_auth):
|
42
|
+
# Authentication is deactivated for this value
|
43
|
+
return func(*args, **kwargs)
|
44
|
+
|
35
45
|
if auth_class.authenticate():
|
36
46
|
return func(*args, **kwargs)
|
37
47
|
else:
|
38
48
|
raise InvalidCredentials("Unable to authenticate the user")
|
39
|
-
|
40
49
|
return wrapper
|
41
50
|
|
42
51
|
return decorator
|
52
|
+
|
53
|
+
def endpoint_qualified_for_no_auth(no_auth_list, optional_auth):
|
54
|
+
"""
|
55
|
+
This function is used to check if the endpoint is qualified for no authentication.
|
56
|
+
|
57
|
+
:param no_auth_list: the list of the values that indicates if authentication should be deactivated
|
58
|
+
:param optional_auth: the env variable that indicates if authentication should be deactivated
|
59
|
+
:type no_auth_list: list
|
60
|
+
:type optional_auth: str
|
61
|
+
:return: True if the endpoint is qualified for no authentication, False otherwise
|
62
|
+
"""
|
63
|
+
if optional_auth is None:
|
64
|
+
return False
|
65
|
+
if no_auth_list is None:
|
66
|
+
raise InvalidCredentials(
|
67
|
+
"The list of the values that indicates if authentication should be deactivated is not defined"
|
68
|
+
)
|
69
|
+
env_variable = current_app.config[optional_auth]
|
70
|
+
if env_variable in no_auth_list:
|
71
|
+
return True
|
72
|
+
return False
|
cornflow/shared/const.py
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
"""
|
2
2
|
In this file we import the values for different constants on cornflow server
|
3
3
|
"""
|
4
|
-
|
4
|
+
|
5
|
+
# CORNFLOW BACKEND
|
6
|
+
AIRFLOW_BACKEND = 1
|
7
|
+
DATABRICKS_BACKEND = 2
|
8
|
+
|
9
|
+
|
10
|
+
CORNFLOW_VERSION = "1.3.0rc1"
|
5
11
|
INTERNAL_TOKEN_ISSUER = "cornflow"
|
6
12
|
|
7
13
|
# endpoints responses for health check
|
@@ -44,7 +50,32 @@ AIRFLOW_TO_STATE_MAP = dict(
|
|
44
50
|
failed=EXEC_STATE_ERROR,
|
45
51
|
queued=EXEC_STATE_QUEUED,
|
46
52
|
)
|
53
|
+
# SIGNUP OPTIONS
|
54
|
+
# NO_SIGNUP: no signup endpoint
|
55
|
+
# SIGNUP_WITH_NO_AUTH: signup endpoint with no auth
|
56
|
+
# SIGNUP_WITH_AUTH: signup endpoint with auth
|
57
|
+
NO_SIGNUP = 0
|
58
|
+
SIGNUP_WITH_NO_AUTH = 1
|
59
|
+
SIGNUP_WITH_AUTH = 2
|
60
|
+
|
61
|
+
DATABRICKS_TO_STATE_MAP = dict(
|
62
|
+
BLOCKED=EXEC_STATE_QUEUED,
|
63
|
+
PENDING=EXEC_STATE_QUEUED,
|
64
|
+
QUEUED=EXEC_STATE_QUEUED,
|
65
|
+
RUNNING=EXEC_STATE_RUNNING,
|
66
|
+
TERMINATING=EXEC_STATE_RUNNING,
|
67
|
+
SUCCESS=EXEC_STATE_CORRECT,
|
68
|
+
USER_CANCELED=EXEC_STATE_STOPPED,
|
69
|
+
OTHER_FINISH_ERROR=EXEC_STATE_ERROR,
|
70
|
+
RUN_EXECUTION_ERROR=EXEC_STATE_ERROR,
|
71
|
+
)
|
72
|
+
|
73
|
+
DATABRICKS_FINISH_TO_STATE_MAP = dict(
|
74
|
+
SUCCESS=EXEC_STATE_CORRECT,
|
75
|
+
USER_CANCELED=EXEC_STATE_STOPPED,
|
76
|
+
)
|
47
77
|
|
78
|
+
DATABRICKS_TERMINATE_STATE = "TERMINATED"
|
48
79
|
# These codes and names are inherited from flask app builder in order to have the same names and values
|
49
80
|
# as this library that is the base of airflow
|
50
81
|
AUTH_DB = 1
|
@@ -52,6 +83,11 @@ AUTH_LDAP = 2
|
|
52
83
|
AUTH_OAUTH = 4
|
53
84
|
AUTH_OID = 0
|
54
85
|
|
86
|
+
# OID possible providers
|
87
|
+
OID_PROVIDER_AWS = 0
|
88
|
+
OID_PROVIDER_AZURE = 1
|
89
|
+
OID_OTHER = 2
|
90
|
+
|
55
91
|
GET_ACTION = 1
|
56
92
|
PATCH_ACTION = 2
|
57
93
|
POST_ACTION = 3
|
@@ -121,3 +157,24 @@ AIRFLOW_NOT_REACHABLE_MSG = "Airflow is not reachable"
|
|
121
157
|
DAG_PAUSED_MSG = "The dag exists but it is paused in airflow"
|
122
158
|
AIRFLOW_ERROR_MSG = "Airflow responded with an error:"
|
123
159
|
DATA_DOES_NOT_EXIST_MSG = "The data entity does not exist on the database"
|
160
|
+
|
161
|
+
# Conditional endpoints
|
162
|
+
CONDITIONAL_ENDPOINTS = {
|
163
|
+
"signup": "/signup/",
|
164
|
+
"login": "/login/",
|
165
|
+
}
|
166
|
+
|
167
|
+
|
168
|
+
# Orchestrator constants
|
169
|
+
config_orchestrator = {
|
170
|
+
"airflow": {
|
171
|
+
"name": "Airflow",
|
172
|
+
"def_schema": "solve_model_dag",
|
173
|
+
"run_id": "dag_run_id",
|
174
|
+
},
|
175
|
+
"databricks": {
|
176
|
+
"name": "Databricks",
|
177
|
+
"def_schema": "979073949072767",
|
178
|
+
"run_id": "run_id",
|
179
|
+
},
|
180
|
+
}
|
cornflow/shared/exceptions.py
CHANGED
@@ -5,7 +5,7 @@ on a flask REST API server
|
|
5
5
|
|
6
6
|
from flask import jsonify
|
7
7
|
from webargs.flaskparser import parser
|
8
|
-
from cornflow_client.constants import AirflowError
|
8
|
+
from cornflow_client.constants import AirflowError, DatabricksError
|
9
9
|
from werkzeug.exceptions import HTTPException
|
10
10
|
import traceback
|
11
11
|
|
@@ -148,6 +148,7 @@ def initialize_errorhandlers(app):
|
|
148
148
|
@app.errorhandler(InvalidCredentials)
|
149
149
|
@app.errorhandler(EndpointNotImplemented)
|
150
150
|
@app.errorhandler(AirflowError)
|
151
|
+
@app.errorhandler(DatabricksError)
|
151
152
|
@app.errorhandler(InvalidData)
|
152
153
|
@app.errorhandler(InvalidPatch)
|
153
154
|
@app.errorhandler(ConfigurationError)
|